Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix a crash when RPC encryption starts and callback when RPCs fail due to RPC encryption #1972

Merged
merged 20 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions SmartDeviceLink-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7903,7 +7903,7 @@
TargetAttributes = {
5D4019AE1A76EC350006B0C2 = {
CreatedOnToolsVersion = 6.1.1;
ProvisioningStyle = Manual;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
Expand All @@ -7916,6 +7916,7 @@
5D61FA1B1A84237100846EE7 = {
CreatedOnToolsVersion = 6.1.1;
LastSwiftMigration = 1210;
ProvisioningStyle = Automatic;
};
5D61FA251A84237100846EE7 = {
CreatedOnToolsVersion = 6.1.1;
Expand Down Expand Up @@ -9375,14 +9376,16 @@
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;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SDLTestApp;
PRODUCT_NAME = "SDL Example";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Debug;
Expand All @@ -9391,14 +9394,16 @@
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;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 7.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SDLTestApp;
PRODUCT_NAME = "SDL Example";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
Expand All @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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";
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions SmartDeviceLink/private/SDLError.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 12 additions & 0 deletions SmartDeviceLink/private/SDLError.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ + (NSError *)sdl_encryption_lifecycle_nak {

}

+ (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 {
Expand Down
46 changes: 22 additions & 24 deletions SmartDeviceLink/private/SDLLifecycleManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ - (void)didEnterStateConnected {
__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);
Expand Down Expand Up @@ -626,7 +626,7 @@ - (void)didEnterStateUnregistering {
__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);
}

Expand All @@ -647,12 +647,11 @@ - (void)sdl_sendAppIcon:(nullable SDLFile *)appIcon withCompletion:(void (^)(voi
[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();
}
}

Expand All @@ -666,8 +665,7 @@ - (void)sdl_sendAppIcon:(nullable SDLFile *)appIcon withCompletion:(void (^)(voi
SDLLogW(@"Error setting up app icon: %@", error);
}

// We've succeeded or failed
completion();
return completion();
}];
}];
}
Expand Down Expand Up @@ -753,19 +751,6 @@ - (void)sendConnectionRequest:(__kindof SDLRPCRequest *)request withResponseHand

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];
Expand Down Expand Up @@ -799,20 +784,29 @@ - (void)sdl_sendConnectionRequest:(__kindof SDLRPCMessage *)request withResponse
}

// 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;
}
}
}

Expand Down Expand Up @@ -869,10 +863,14 @@ - (void)sdl_rpcServiceDidConnect {
// 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);
}
});
}
}
Expand Down
4 changes: 3 additions & 1 deletion SmartDeviceLink/private/SDLProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 28 additions & 15 deletions SmartDeviceLink/private/SDLProtocol.m
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -280,25 +281,33 @@ - (void)registerSecondaryTransport {

#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;
Expand Down Expand Up @@ -333,27 +342,31 @@ - (BOOL)sendRPC:(SDLRPCMessage *)message encrypted:(BOOL)encryption error:(NSErr
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);
Expand Down
2 changes: 1 addition & 1 deletion SmartDeviceLink/private/SDLUploadFileOperation.m
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ - (void)sdl_sendFile:(SDLFile *)file mtuSize:(NSUInteger)mtuSize withCompletion:
}

// 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);
Expand Down
5 changes: 4 additions & 1 deletion SmartDeviceLink/public/SDLErrorConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

/**
Expand Down
Loading