summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2020-02-18 13:20:41 -0500
committerGitHub <noreply@github.com>2020-02-18 13:20:41 -0500
commitb32a989be79254bea4e26ad3535a9553a54af700 (patch)
tree89911cd3083b15306e829f631b9638ff96890637
parente981200dc95cf116c710e04e8b61a188db7d0006 (diff)
parentbaf0c06459abd22e87e98156ce614e3f33c05e04 (diff)
downloadsdl_ios-b32a989be79254bea4e26ad3535a9553a54af700.tar.gz
Merge pull request #1555 from smartdevicelink/feature/issue-1145-Secondary-Transport-Optimization
Implement SDL-0214 Secondary Transport Optimization
-rw-r--r--SmartDeviceLink/SDLLifecycleManager.m10
-rw-r--r--SmartDeviceLink/SDLLogFileModuleMap.m26
-rw-r--r--SmartDeviceLink/SDLSecondaryTransportManager.m350
-rw-r--r--SmartDeviceLink/SDLTCPTransport.m12
-rw-r--r--SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m53
5 files changed, 257 insertions, 194 deletions
diff --git a/SmartDeviceLink/SDLLifecycleManager.m b/SmartDeviceLink/SDLLifecycleManager.m
index 5d258ba4b..05a09dd9b 100644
--- a/SmartDeviceLink/SDLLifecycleManager.m
+++ b/SmartDeviceLink/SDLLifecycleManager.m
@@ -244,8 +244,14 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
} else if (self.configuration.lifecycleConfig.allowedSecondaryTransports == SDLSecondaryTransportsNone) {
self.proxy = [SDLProxy iapProxyWithListener:self.notificationDispatcher secondaryTransportManager:nil encryptionLifecycleManager:self.encryptionLifecycleManager];
} else {
- // We reuse our queue to run secondary transport manager's state machine
- self.secondaryTransportManager = [[SDLSecondaryTransportManager alloc] initWithStreamingProtocolDelegate:self serialQueue:self.lifecycleQueue];
+ if([self.configuration.lifecycleConfig.appType isEqualToEnum:SDLAppHMITypeNavigation] ||
+ [self.configuration.lifecycleConfig.appType isEqualToEnum:SDLAppHMITypeProjection] ||
+ [self.configuration.lifecycleConfig.additionalAppTypes containsObject:SDLAppHMITypeNavigation] ||
+ [self.configuration.lifecycleConfig.additionalAppTypes containsObject:SDLAppHMITypeProjection]) {
+ // We reuse our queue to run secondary transport manager's state machine
+ self.secondaryTransportManager = [[SDLSecondaryTransportManager alloc] initWithStreamingProtocolDelegate:self serialQueue:self.lifecycleQueue];
+ }
+
self.proxy = [SDLProxy iapProxyWithListener:self.notificationDispatcher secondaryTransportManager:self.secondaryTransportManager encryptionLifecycleManager:self.encryptionLifecycleManager];
}
#pragma clang diagnostic pop
diff --git a/SmartDeviceLink/SDLLogFileModuleMap.m b/SmartDeviceLink/SDLLogFileModuleMap.m
index 4e8ed5808..9195840e0 100644
--- a/SmartDeviceLink/SDLLogFileModuleMap.m
+++ b/SmartDeviceLink/SDLLogFileModuleMap.m
@@ -14,6 +14,9 @@
+ (NSSet<SDLLogFileModule *> *)sdlModuleMap {
return [NSSet setWithArray:@[[self sdl_transportModule],
+ [self sdl_tcpTransportModule],
+ [self sdl_iapTransportModule],
+ [self sdl_secondaryTransportModule],
[self sdl_proxyModule],
[self sdl_protocolModule],
[self sdl_rpcModule],
@@ -33,10 +36,26 @@
[self sdl_utilitiesModule]]];
}
+#pragma mark Transport
+
+ (SDLLogFileModule *)sdl_transportModule {
- return [SDLLogFileModule moduleWithName:@"Transport" files:[NSSet setWithArray:@[@"SDLIAPSession", @"SDLIAPTransport", @"SDLIAPDataSession", @"SDLIAPControlSession", @"SDLSecondaryTransportManager", @"SDLSecondaryTransportPrimaryProtocolHandler", @"SDLStreamDelegate", @"SDLTCPTransport"]]];
+ return [SDLLogFileModule moduleWithName:@"Transport" files:[NSSet setWithArray:@[@"SDLStreamDelegate"]]];
+}
+
++ (SDLLogFileModule *)sdl_tcpTransportModule {
+ return [SDLLogFileModule moduleWithName:@"Transport/TCP" files:[NSSet setWithArray:@[@"SDLTCPTransport"]]];
+}
+
++ (SDLLogFileModule *)sdl_iapTransportModule {
+ return [SDLLogFileModule moduleWithName:@"Transport/IAP" files:[NSSet setWithArray:@[@"SDLIAPSession", @"SDLIAPTransport", @"SDLIAPDataSession", @"SDLIAPControlSession"]]];
+}
+
++ (SDLLogFileModule *)sdl_secondaryTransportModule {
+ return [SDLLogFileModule moduleWithName:@"Transport/Secondary" files:[NSSet setWithArray:@[@"SDLSecondaryTransportManager", @"SDLSecondaryTransportPrimaryProtocolHandler"]]];
}
+#pragma mark Low-Level
+
+ (SDLLogFileModule *)sdl_proxyModule {
return [SDLLogFileModule moduleWithName:@"Proxy" files:[NSSet setWithArray:@[@"SDLProxy", @"SDLPolicyDataParser"]]];
}
@@ -49,13 +68,12 @@
return [SDLLogFileModule moduleWithName:@"RPC" files:[NSSet setWithArray:@[@"SDLRPCPayload", @"NSMutableDictionary+Store"]]];
}
+#pragma mark Managers
+
+ (SDLLogFileModule *)sdl_dispatcherModule {
return [SDLLogFileModule moduleWithName:@"Dispatcher" files:[NSSet setWithArray:@[@"SDLNotificationDispatcher", @"SDLResponseDispatcher"]]];
}
-
-#pragma mark Managers
-
+ (SDLLogFileModule *)sdl_fileManagerModule {
return [SDLLogFileModule moduleWithName:@"File" files:[NSSet setWithArray:@[@"SDLFileManager", @"SDLFile", @"SDLArtwork", @"SDLListFilesOperation", @"SDLUploadFileOperation", @"SDLDeleteFileOperation"]]];
}
diff --git a/SmartDeviceLink/SDLSecondaryTransportManager.m b/SmartDeviceLink/SDLSecondaryTransportManager.m
index 860abcbaf..9cd1af232 100644
--- a/SmartDeviceLink/SDLSecondaryTransportManager.m
+++ b/SmartDeviceLink/SDLSecondaryTransportManager.m
@@ -16,9 +16,11 @@
#import "SDLControlFramePayloadTransportEventUpdate.h"
#import "SDLIAPTransport.h"
#import "SDLLogMacros.h"
+#import "SDLOnHMIStatus.h"
#import "SDLProtocol.h"
#import "SDLProtocolHeader.h"
#import "SDLNotificationConstants.h"
+#import "SDLRPCNotificationNotification.h"
#import "SDLSecondaryTransportPrimaryProtocolHandler.h"
#import "SDLStateMachine.h"
#import "SDLTCPTransport.h"
@@ -75,9 +77,9 @@ static const int TCPPortUnspecified = -1;
// Selected type of secondary transport. If 'SDLSecondaryTransportTypeDisabled' then secondary transport is disabled.
@property (assign, nonatomic) SDLSecondaryTransportType secondaryTransportType;
// Instance of the transport for secondary transport.
-@property (nullable, strong, nonatomic) id<SDLTransportType> secondaryTransport;
+@property (strong, nonatomic, nullable) id<SDLTransportType> secondaryTransport;
// Instance of the protocol that runs on secondary transport.
-@property (nullable, strong, nonatomic) SDLProtocol *secondaryProtocol;
+@property (strong, nonatomic, nullable) SDLProtocol *secondaryProtocol;
// Timer to check Register Secondary Transport ACK response on secondary transport.
@property (strong, nonatomic, nullable) SDLTimer *registerTransportTimer;
@@ -85,9 +87,9 @@ static const int TCPPortUnspecified = -1;
@property (weak, nonatomic) id<SDLStreamingProtocolDelegate> streamingProtocolDelegate;
// Configuration sent by system; list of transports that are allowed to carry audio service
-@property (strong, nonatomic, nonnull) NSArray<SDLTransportClassBox *> *transportsForAudioService;
+@property (strong, nonatomic) NSArray<SDLTransportClassBox *> *transportsForAudioService;
// Configuration sent by system; list of transports that are allowed to carry video service
-@property (strong, nonatomic, nonnull) NSArray<SDLTransportClassBox *> *transportsForVideoService;
+@property (strong, nonatomic) NSArray<SDLTransportClassBox *> *transportsForVideoService;
// A map to remember which service is currently running on which transport
@property (strong, nonatomic) NSMutableDictionary<SDLServiceTypeBox *, SDLTransportClassBox *> *streamingServiceTransportMap;
@@ -95,8 +97,8 @@ static const int TCPPortUnspecified = -1;
@property (strong, nonatomic, nullable) NSString *ipAddress;
// TCP port number of SDL Core. If the information isn't available then TCPPortUnspecified is stored.
@property (assign, nonatomic) int tcpPort;
-// App is ready to set security manager to secondary protocol
-@property (assign, nonatomic, getter=isAppReady) BOOL appReady;
+
+@property (strong, nonatomic, nullable) SDLHMILevel currentHMILevel;
@end
@@ -122,7 +124,7 @@ static const int TCPPortUnspecified = -1;
@(SDLServiceTypeVideo):@(SDLTransportClassInvalid)} mutableCopy];
_tcpPort = TCPPortUnspecified;
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomeReady) name:SDLDidBecomeReady object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_hmiStatusDidChange:) name:SDLDidChangeHMIStatusNotification object:nil];
return self;
}
@@ -161,50 +163,76 @@ static const int TCPPortUnspecified = -1;
[self.stateMachine transitionToState:SDLSecondaryTransportStateStopped];
}
-// called from SDLProtocol's _receiveQueue of "primary" transport
-- (void)onStartServiceAckReceived:(SDLControlFramePayloadRPCStartServiceAck *)payload {
- NSMutableArray<SDLSecondaryTransportTypeBox *> *secondaryTransports = nil;
- if (payload.secondaryTransports != nil) {
- secondaryTransports = [NSMutableArray array];
- for (NSString *transportString in payload.secondaryTransports) {
- SDLSecondaryTransportType transport = [self sdl_convertTransportType:transportString];
- [secondaryTransports addObject:@(transport)];
- }
- }
- NSArray<SDLTransportClassBox *> *transportsForAudio = [self sdl_convertServiceTransports:payload.audioServiceTransports];
- NSArray<SDLTransportClassBox *> *transportsForVideo = [self sdl_convertServiceTransports:payload.videoServiceTransports];
+#pragma mark - Manager Lifecycle
- SDLLogV(@"Secondary transports: %@, transports for audio: %@, transports for video: %@", secondaryTransports, transportsForAudio, transportsForVideo);
+- (void)sdl_startManager {
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_onAppStateUpdated:) name:UIApplicationDidBecomeActiveNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_onAppStateUpdated:) name:UIApplicationWillResignActiveNotification object:nil];
- dispatch_async(_stateMachineQueue, ^{
- [self sdl_configureManager:secondaryTransports availableTransportsForAudio:transportsForAudio availableTransportsForVideo:transportsForVideo];
- });
+ [self.primaryProtocolHandler start];
}
-// called from SDLProtocol's _receiveQueue of "primary" transport
-- (void)onTransportEventUpdateReceived:(SDLControlFramePayloadTransportEventUpdate *)payload {
- dispatch_async(_stateMachineQueue, ^{
- BOOL updated = NO;
+- (void)sdl_stopManager {
+ SDLLogD(@"SDLSecondaryTransportManager stop");
- if (payload.tcpIpAddress != nil) {
- if (![self.ipAddress isEqualToString:payload.tcpIpAddress]) {
- self.ipAddress = payload.tcpIpAddress;
- updated = YES;
- SDLLogD(@"TCP transport IP address updated: %@", self.ipAddress);
- }
- }
- if (payload.tcpPort != SDLControlFrameInt32NotFound) {
- if (self.tcpPort != payload.tcpPort) {
- self.tcpPort = payload.tcpPort;
- updated = YES;
- SDLLogD(@"TCP transport port number updated: %d", self.tcpPort);
- }
- }
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
- if (updated) {
- [self sdl_handleTransportEventUpdate];
- }
- });
+ [self.primaryProtocolHandler stop];
+
+ self.streamingServiceTransportMap = [@{@(SDLServiceTypeAudio):@(SDLTransportClassInvalid),
+ @(SDLServiceTypeVideo):@(SDLTransportClassInvalid)} mutableCopy];
+ self.secondaryTransportType = SDLSecondaryTransportTypeDisabled;
+ self.transportsForAudioService = @[];
+ self.transportsForVideoService = @[];
+
+ self.ipAddress = nil;
+ self.tcpPort = TCPPortUnspecified;
+ self.currentHMILevel = nil;
+}
+
+- (void)sdl_configureManager:(nullable NSArray<SDLSecondaryTransportTypeBox *> *)availableSecondaryTransports
+ availableTransportsForAudio:(nullable NSArray<SDLTransportClassBox *> *)availableTransportsForAudio
+ availableTransportsForVideo:(nullable NSArray<SDLTransportClassBox *> *)availableTransportsForVideo {
+ if (![self.stateMachine isCurrentState:SDLSecondaryTransportStateStarted]) {
+ SDLLogW(@"SecondaryTransportManager ignores duplicate Start Service ACK frame");
+ return;
+ }
+
+ // default values
+ self.secondaryTransportType = SDLSecondaryTransportTypeDisabled;
+ self.transportsForAudioService = @[@(SDLTransportClassPrimary)]; // If SDL Core did not send a transport list for the service, start it on Primary Transport.
+ self.transportsForVideoService = @[@(SDLTransportClassPrimary)];
+ BOOL validConfig = YES;
+
+ if (availableSecondaryTransports.count > 0) {
+ // current proposal says the list should contain only one element
+ SDLSecondaryTransportTypeBox *transportType = availableSecondaryTransports[0];
+ self.secondaryTransportType = [transportType integerValue];
+ } else {
+ SDLLogW(@"Did not receive secondary transport type from system. Secondary transport is disabled.");
+ }
+
+ SDLSecondaryTransportType primaryTransportType = [self sdl_getTransportTypeFromProtocol:self.primaryProtocol];
+ if (self.secondaryTransportType == primaryTransportType) {
+ SDLLogW(@"Same transport is specified for both primary and secondary transport. Secondary transport is disabled.");
+ self.secondaryTransportType = SDLSecondaryTransportTypeDisabled;
+ validConfig = NO; // let audio and video services start on primary transport
+ } else if (self.secondaryTransportType == SDLSecondaryTransportTypeIAP) {
+ SDLLogW(@"Starting IAP as secondary transport, which does not usually happen");
+ }
+
+ if (availableTransportsForAudio != nil && validConfig) {
+ self.transportsForAudioService = availableTransportsForAudio;
+ }
+ if (availableTransportsForVideo != nil && validConfig) {
+ self.transportsForVideoService = availableTransportsForVideo;
+ }
+
+ // this will trigger audio / video streaming if they are allowed on primary transport
+ [self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
+
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
}
#pragma mark - State machine
@@ -221,21 +249,28 @@ static const int TCPPortUnspecified = -1;
}
- (void)didEnterStateStopped {
+ SDLLogD(@"Secondary transport manager stopped");
[self sdl_stopManager];
}
- (void)didEnterStateStarted {
+ SDLLogD(@"Secondary transport manager started");
[self sdl_startManager];
}
- (void)didEnterStateConfigured {
- if ((self.secondaryTransportType == SDLSecondaryTransportTypeTCP && [self sdl_isTCPReady] && self.isAppReady)
- || (self.secondaryTransportType == SDLSecondaryTransportTypeIAP && self.isAppReady)) {
+ SDLLogD(@"Secondary transport manager is configured");
+ // If this is a TCP transport, check if it's ready. If it's IAP, we can just continue. In both cases, check if HMI level is Non-NONE
+ // https://github.com/smartdevicelink/sdl_evolution/blob/master/proposals/0214-secondary-transport-optimization.md
+ if (((self.secondaryTransportType == SDLSecondaryTransportTypeTCP && self.sdl_isTCPReady)
+ || self.secondaryTransportType == SDLSecondaryTransportTypeIAP)
+ && self.sdl_isHMILevelNonNone) {
[self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
}
}
- (void)didEnterStateConnecting {
+ SDLLogD(@"Secondary transport is connecting");
[self sdl_connectSecondaryTransport];
}
@@ -258,6 +293,7 @@ static const int TCPPortUnspecified = -1;
}
- (void)didEnterStateRegistered {
+ SDLLogD(@"Secondary transport is registered");
[self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:YES];
}
@@ -282,114 +318,16 @@ static const int TCPPortUnspecified = -1;
}
- (void)didEnterStateReconnecting {
- self.appReady = NO;
+ SDLLogD(@"Secondary transport is reconnecting");
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(RetryConnectionDelay * NSEC_PER_SEC)), _stateMachineQueue, ^{
if ([weakSelf.stateMachine isCurrentState:SDLSecondaryTransportStateReconnecting]) {
- SDLLogD(@"Retry secondary transport after disconnection or registration failure");
+ SDLLogD(@"Attempting reconnection");
[weakSelf.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
}
});
}
-- (void)sdl_startManager {
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_onAppStateUpdated:) name:UIApplicationDidBecomeActiveNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_onAppStateUpdated:) name:UIApplicationWillResignActiveNotification object:nil];
-
- [self.primaryProtocolHandler start];
-}
-
-- (void)sdl_stopManager {
- SDLLogD(@"SDLSecondaryTransportManager stop");
-
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
-
- [self.primaryProtocolHandler stop];
-
- self.streamingServiceTransportMap = [@{@(SDLServiceTypeAudio):@(SDLTransportClassInvalid),
- @(SDLServiceTypeVideo):@(SDLTransportClassInvalid)} mutableCopy];
- self.secondaryTransportType = SDLSecondaryTransportTypeDisabled;
- self.transportsForAudioService = @[];
- self.transportsForVideoService = @[];
-
- self.ipAddress = nil;
- self.tcpPort = TCPPortUnspecified;
-}
-
-- (void)sdl_configureManager:(nullable NSArray<SDLSecondaryTransportTypeBox *> *)availableSecondaryTransports
- availableTransportsForAudio:(nullable NSArray<SDLTransportClassBox *> *)availableTransportsForAudio
- availableTransportsForVideo:(nullable NSArray<SDLTransportClassBox *> *)availableTransportsForVideo {
- if (![self.stateMachine isCurrentState:SDLSecondaryTransportStateStarted]) {
- SDLLogW(@"SecondaryTransportManager ignores duplicate Start Service ACK frame");
- return;
- }
-
- // default values
- self.secondaryTransportType = SDLSecondaryTransportTypeDisabled;
- self.transportsForAudioService = @[@(SDLTransportClassPrimary)]; // If SDL Core did not send a transport list for the service, start it on Primary Transport.
- self.transportsForVideoService = @[@(SDLTransportClassPrimary)];
- BOOL validConfig = YES;
-
- if (availableSecondaryTransports.count > 0) {
- // current proposal says the list should contain only one element
- SDLSecondaryTransportTypeBox *transportType = availableSecondaryTransports[0];
- self.secondaryTransportType = [transportType integerValue];
- } else {
- SDLLogW(@"Did not receive secondary transport type from system. Secondary transport is disabled.");
- }
-
- SDLSecondaryTransportType primaryTransportType = [self sdl_getTransportTypeFromProtocol:self.primaryProtocol];
- if (self.secondaryTransportType == primaryTransportType) {
- SDLLogW(@"Same transport is specified for both primary and secondary transport. Secondary transport is disabled.");
- self.secondaryTransportType = SDLSecondaryTransportTypeDisabled;
- validConfig = NO; // let audio and video services start on primary transport
- } else if (self.secondaryTransportType == SDLSecondaryTransportTypeIAP) {
- SDLLogW(@"Starting IAP as secondary transport, which does not usually happen");
- }
-
- if (availableTransportsForAudio != nil && validConfig) {
- self.transportsForAudioService = availableTransportsForAudio;
- }
- if (availableTransportsForVideo != nil && validConfig) {
- self.transportsForVideoService = availableTransportsForVideo;
- }
-
- // this will trigger audio / video streaming if they are allowed on primary transport
- [self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
-
- [self.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
-}
-
-- (void)sdl_handleTransportEventUpdate {
- if ([self.stateMachine isCurrentState:SDLSecondaryTransportStateStarted]) {
- // The system sent Transport Event Update frame prior to Start Service ACK. Just keep the information and do nothing here.
- SDLLogV(@"Received TCP transport information prior to Start Service ACK");
- return;
- }
- if (self.secondaryTransportType != SDLSecondaryTransportTypeTCP) {
- SDLLogV(@"Received TCP transport information while the transport is not TCP");
- return;
- }
-
- if ([self.stateMachine isCurrentState:SDLSecondaryTransportStateConfigured] && [self sdl_isTCPReady] && self.isAppReady) {
- [self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
- } else if ([self sdl_isTransportOpened]) {
- // Disconnect current transport. If the IP address is available then we will reconnect immediately.
- SDLLogD(@"TCP transport information updated, disconnecting current transport");
- [self.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
- } else if ([self.stateMachine isCurrentState:SDLSecondaryTransportStateReconnecting]) {
- SDLLogD(@"TCP transport information updated, aborting reconnection timer");
- [self.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
- }
-}
-
-- (BOOL)sdl_isTransportOpened {
- return [self.stateMachine isCurrentState:SDLSecondaryTransportStateConnecting]
- || [self.stateMachine isCurrentState:SDLSecondaryTransportStateRegistered];
-}
-
-
#pragma mark - Starting / Stopping / Restarting services
- (void)sdl_handleTransportUpdateWithPrimaryAvailable:(BOOL)primaryAvailable secondaryAvailable:(BOOL)secondaryAvailable {
@@ -400,7 +338,7 @@ static const int TCPPortUnspecified = -1;
secondaryAvailable:secondaryAvailable
transportUpdatedBlock:^(SDLProtocol * _Nullable oldProtocol, SDLProtocol * _Nullable newProtocol) {
[self.streamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:oldProtocol toNewProtocol:newProtocol];
- }];
+ }];
// update video service
[self sdl_updateService:SDLServiceTypeVideo
@@ -409,7 +347,7 @@ static const int TCPPortUnspecified = -1;
secondaryAvailable:secondaryAvailable
transportUpdatedBlock:^(SDLProtocol * _Nullable oldProtocol, SDLProtocol * _Nullable newProtocol) {
[self.streamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:oldProtocol toNewProtocol:newProtocol];
- }];
+ }];
}
- (void)sdl_updateService:(UInt8)service
@@ -565,8 +503,8 @@ static const int TCPPortUnspecified = -1;
return NO;
}
- if ([self sdl_getAppState] != UIApplicationStateActive) {
- SDLLogD(@"App state is not Active, abort starting TCP transport");
+ if (self.sdl_getAppState != UIApplicationStateActive) {
+ SDLLogD(@"App state is not Active, TCP transport is not ready");
return NO;
}
@@ -645,6 +583,76 @@ static const int TCPPortUnspecified = -1;
});
}
+// called from SDLProtocol's _receiveQueue of "primary" transport
+- (void)onStartServiceAckReceived:(SDLControlFramePayloadRPCStartServiceAck *)payload {
+ NSMutableArray<SDLSecondaryTransportTypeBox *> *secondaryTransports = nil;
+ if (payload.secondaryTransports != nil) {
+ secondaryTransports = [NSMutableArray array];
+ for (NSString *transportString in payload.secondaryTransports) {
+ SDLSecondaryTransportType transport = [self sdl_convertTransportType:transportString];
+ [secondaryTransports addObject:@(transport)];
+ }
+ }
+
+ NSArray<SDLTransportClassBox *> *transportsForAudio = [self sdl_convertServiceTransports:payload.audioServiceTransports];
+ NSArray<SDLTransportClassBox *> *transportsForVideo = [self sdl_convertServiceTransports:payload.videoServiceTransports];
+
+ SDLLogV(@"Secondary transports: %@, transports for audio: %@, transports for video: %@", secondaryTransports, transportsForAudio, transportsForVideo);
+
+ dispatch_async(_stateMachineQueue, ^{
+ [self sdl_configureManager:secondaryTransports availableTransportsForAudio:transportsForAudio availableTransportsForVideo:transportsForVideo];
+ });
+}
+
+// called from SDLProtocol's _receiveQueue of "primary" transport
+- (void)onTransportEventUpdateReceived:(SDLControlFramePayloadTransportEventUpdate *)payload {
+ dispatch_async(_stateMachineQueue, ^{
+ BOOL updated = NO;
+
+ if (payload.tcpIpAddress != nil) {
+ if (![self.ipAddress isEqualToString:payload.tcpIpAddress]) {
+ self.ipAddress = payload.tcpIpAddress;
+ updated = YES;
+ SDLLogD(@"TCP transport IP address updated: %@", self.ipAddress);
+ }
+ }
+ if (payload.tcpPort != SDLControlFrameInt32NotFound) {
+ if (self.tcpPort != payload.tcpPort) {
+ self.tcpPort = payload.tcpPort;
+ updated = YES;
+ SDLLogD(@"TCP transport port number updated: %d", self.tcpPort);
+ }
+ }
+
+ if (updated) {
+ [self sdl_handleTransportEventUpdate];
+ }
+ });
+}
+
+- (void)sdl_handleTransportEventUpdate {
+ if ([self.stateMachine isCurrentState:SDLSecondaryTransportStateStarted]) {
+ // The system sent Transport Event Update frame prior to Start Service ACK. Just keep the information and do nothing here.
+ SDLLogV(@"Received TCP transport information prior to Start Service ACK");
+ return;
+ }
+ if (self.secondaryTransportType != SDLSecondaryTransportTypeTCP) {
+ SDLLogV(@"Received TCP transport information while the transport is not TCP");
+ return;
+ }
+
+ if ([self.stateMachine isCurrentState:SDLSecondaryTransportStateConfigured] && self.sdl_isTCPReady && self.sdl_isHMILevelNonNone) {
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
+ } else if ([self sdl_isTransportOpened]) {
+ // Disconnect current transport. If the IP address is available then we will reconnect immediately.
+ SDLLogD(@"TCP transport information updated, disconnecting current transport");
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
+ } else if ([self.stateMachine isCurrentState:SDLSecondaryTransportStateReconnecting]) {
+ SDLLogD(@"TCP transport information updated, aborting reconnection timer");
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
+ }
+}
+
#pragma mark - App state handling
- (void)sdl_onAppStateUpdated:(NSNotification *)notification {
@@ -655,9 +663,11 @@ static const int TCPPortUnspecified = -1;
[self.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
}
} else if (notification.name == UIApplicationDidBecomeActiveNotification) {
- if (([self.stateMachine isCurrentState:SDLSecondaryTransportStateConfigured])
- && self.secondaryTransportType == SDLSecondaryTransportTypeTCP && [self sdl_isTCPReady] && self.isAppReady) {
- SDLLogD(@"Resuming TCP transport since the app becomes foreground");
+ if ([self.stateMachine isCurrentState:SDLSecondaryTransportStateConfigured]
+ && self.secondaryTransportType == SDLSecondaryTransportTypeTCP
+ && [self sdl_isTCPReady]
+ && [self sdl_isHMILevelNonNone]) {
+ SDLLogD(@"Resuming TCP transport since the app came into the foreground");
[self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
}
}
@@ -713,11 +723,33 @@ static const int TCPPortUnspecified = -1;
}
}
-- (void)appDidBecomeReady {
- self.appReady = YES;
- if (([self.stateMachine.currentState isEqualToString:SDLSecondaryTransportStateConfigured] && self.tcpPort != SDLControlFrameInt32NotFound && self.ipAddress != nil)
- || self.secondaryTransportType == SDLSecondaryTransportTypeIAP) {
- [self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
+- (BOOL)sdl_isTransportOpened {
+ return [self.stateMachine isCurrentState:SDLSecondaryTransportStateConnecting] || [self.stateMachine isCurrentState:SDLSecondaryTransportStateRegistered];
+}
+
+- (BOOL)sdl_isHMILevelNonNone {
+ return (self.currentHMILevel != nil && ![self.currentHMILevel isEqualToEnum:SDLHMILevelNone]);
+}
+
+#pragma mark - RPC Notifications
+/// Check and track the HMI status to ensure that the secondary transport only attempts a connection in non-NONE HMI states
+///
+/// See: https://github.com/smartdevicelink/sdl_evolution/blob/master/proposals/0214-secondary-transport-optimization.md
+///
+/// @param notification The NSNotification containing the OnHMIStatus
+- (void)sdl_hmiStatusDidChange:(SDLRPCNotificationNotification *)notification {
+ if (![notification isNotificationMemberOfClass:[SDLOnHMIStatus class]]) {
+ return;
+ }
+
+ SDLOnHMIStatus *hmiStatus = notification.notification;
+ self.currentHMILevel = hmiStatus.hmiLevel;
+
+ // If the HMI level is non-NONE, and the state machine is currently waiting in the configured state, and _either_ we're using TCP and it's ready _or_ we're using IAP.
+ if (self.sdl_isHMILevelNonNone
+ && [self.stateMachine isCurrentState:SDLSecondaryTransportStateConfigured]
+ && ((self.secondaryTransportType == SDLSecondaryTransportTypeTCP && [self sdl_isTCPReady]) || (self.secondaryTransportType == SDLSecondaryTransportTypeIAP))) {
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
}
}
diff --git a/SmartDeviceLink/SDLTCPTransport.m b/SmartDeviceLink/SDLTCPTransport.m
index f56641fe7..fe642b9c0 100644
--- a/SmartDeviceLink/SDLTCPTransport.m
+++ b/SmartDeviceLink/SDLTCPTransport.m
@@ -309,24 +309,23 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
NSError *error;
switch (stream.streamError.code) {
case ECONNREFUSED: {
- SDLLogD(@"TCP connection error: ECONNREFUSED");
+ SDLLogE(@"TCP connection error: ECONNREFUSED (connection refused)");
error = [NSError sdl_transport_connectionRefusedError];
} break;
case ETIMEDOUT: {
- SDLLogD(@"TCP connection error: ETIMEDOUT");
+ SDLLogE(@"TCP connection error: ETIMEDOUT (connection timed out)");
error = [NSError sdl_transport_connectionTimedOutError];
} break;
case ENETDOWN: {
- SDLLogD(@"TCP connection error: ENETDOWN");
+ SDLLogE(@"TCP connection error: ENETDOWN (network down)");
error = [NSError sdl_transport_networkDownError];
} break;
case ENETUNREACH: {
- // This is just for safe. I did not observe ENETUNREACH error on iPhone.
- SDLLogD(@"TCP connection error: ENETUNREACH");
+ SDLLogE(@"TCP connection error: ENETUNREACH (network unreachable)");
error = [NSError sdl_transport_networkDownError];
} break;
default: {
- SDLLogD(@"TCP connection error: unknown error %ld", (long)stream.streamError.code);
+ SDLLogE(@"TCP connection error: unknown error %ld", (long)stream.streamError.code);
error = [NSError sdl_transport_unknownError];
} break;
}
@@ -338,6 +337,7 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
}
- (void)sdl_onStreamEnd:(NSStream *)stream {
+ SDLLogD(@"Stream ended");
NSAssert([[NSThread currentThread] isEqual:self.ioThread], @"sdl_onStreamEnd is called on a wrong thread!");
[self sdl_cancelIOThread];
diff --git a/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m b/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m
index d520a367e..2c4abf093 100644
--- a/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m
+++ b/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m
@@ -13,14 +13,18 @@
#import "SDLControlFramePayloadRegisterSecondaryTransportNak.h"
#import "SDLControlFramePayloadRPCStartServiceAck.h"
#import "SDLControlFramePayloadTransportEventUpdate.h"
+#import "SDLHMILevel.h"
#import "SDLIAPTransport.h"
#import "SDLNotificationConstants.h"
+#import "SDLRPCNotificationNotification.h"
#import "SDLProtocol.h"
#import "SDLSecondaryTransportManager.h"
#import "SDLStateMachine.h"
#import "SDLTCPTransport.h"
#import "SDLV2ProtocolMessage.h"
#import "SDLFakeSecurityManager.h"
+#import "SDLHMILevel.h"
+#import "SDLOnHMIStatus.h"
/* copied from SDLSecondaryTransportManager.m */
typedef NSNumber SDLServiceTypeBox;
@@ -55,7 +59,7 @@ static const int TCPPortUnspecified = -1;
@property (strong, nonatomic) NSMutableDictionary<SDLServiceTypeBox *, SDLTransportClassBox *> *streamingServiceTransportMap;
@property (strong, nonatomic, nullable) NSString *ipAddress;
@property (assign, nonatomic) int tcpPort;
-@property (assign, nonatomic, getter=isAppReady) BOOL appReady;
+@property (strong, nonatomic, nullable) SDLHMILevel currentHMILevel;
@end
@@ -137,6 +141,15 @@ describe(@"the secondary transport manager ", ^{
__block id<SDLTransportType> testPrimaryTransport = nil;
__block id testStreamingProtocolDelegate = nil;
+ __block void (^sendNotificationForHMILevel)(SDLHMILevel hmiLevel) = ^(SDLHMILevel hmiLevel) {
+ SDLOnHMIStatus *hmiStatus = [[SDLOnHMIStatus alloc] init];
+ hmiStatus.hmiLevel = hmiLevel;
+ SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:self rpcNotification:hmiStatus];
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ [NSThread sleepForTimeInterval:0.3];
+ };
+
beforeEach(^{
[SDLSecondaryTransportManager swapGetAppStateMethod];
[SDLTCPTransport swapConnectionMethods];
@@ -413,7 +426,7 @@ describe(@"the secondary transport manager ", ^{
testStartServiceACKPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMtu authToken:nil protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
testStartServiceACKMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testStartServiceACKHeader andPayload:testStartServiceACKPayload.data];
- manager.appReady = YES;
+ manager.currentHMILevel = SDLHMILevelFull;
});
it(@"should configure its properties and immediately transition to Connecting state", ^{
@@ -507,27 +520,23 @@ describe(@"the secondary transport manager ", ^{
});
});
- context(@"before the security manager is set by register app interface response", ^{
+ context(@"before the app context is HMI FULL", ^{
it(@"should stay in state Configured", ^{
expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- expect(manager.secondaryProtocol.securityManager).to(beNil());
- expect(manager.isAppReady).to(equal(NO));
+ expect(manager.currentHMILevel).to(beNil());
OCMVerifyAll(testStreamingProtocolDelegate);
});
});
- context(@"after the security manager is set by register app interface response", ^{
+ context(@"app becomes HMI FULL", ^{
beforeEach(^{
- testPrimaryProtocol.securityManager = OCMClassMock([SDLFakeSecurityManager class]);
- // By the time this notification is recieved the RAIR should have been sent and the security manager should exist if available
- [[NSNotificationCenter defaultCenter] postNotificationName:SDLDidBecomeReady object:nil];
+ sendNotificationForHMILevel(SDLHMILevelFull);
});
it(@"should transition to Connecting state", ^{
expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
- expect(manager.secondaryProtocol.securityManager).to(equal(testPrimaryProtocol.securityManager));
- expect(manager.isAppReady).to(equal(YES));
+ expect(manager.currentHMILevel).to(equal(SDLHMILevelFull));
OCMVerifyAll(testStreamingProtocolDelegate);
});
@@ -561,7 +570,7 @@ describe(@"the secondary transport manager ", ^{
});
});
- describe(@"and Transport Event Update is received", ^{
+ describe(@"and a Transport Event Update has been received", ^{
__block SDLProtocolHeader *testTransportEventUpdateHeader = nil;
__block SDLProtocolMessage *testTransportEventUpdateMessage = nil;
__block SDLControlFramePayloadTransportEventUpdate *testTransportEventUpdatePayload = nil;
@@ -584,24 +593,23 @@ describe(@"the secondary transport manager ", ^{
});
- context(@"before the security manager is set by register app interface response", ^{
+ context(@"before the app context is HMI FULL", ^{
it(@"should stay in Configured state", ^{
expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- expect(manager.secondaryProtocol.securityManager).to(beNil());
+ expect(manager.currentHMILevel).to(beNil());
+
OCMVerifyAll(testStreamingProtocolDelegate);
});
});
- context(@"after the security manager is set by register app interface response", ^{
+ context(@"app becomes HMI FULL", ^{
beforeEach(^{
- testPrimaryProtocol.securityManager = OCMClassMock([SDLFakeSecurityManager class]);
- // By the time this notification is recieved the RAIR should have been sent and the security manager should exist if available
- [[NSNotificationCenter defaultCenter] postNotificationName:SDLDidBecomeReady object:nil];
+ sendNotificationForHMILevel(SDLHMILevelFull);
});
it(@"should transition to Connecting", ^{
expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
- expect(manager.secondaryProtocol.securityManager).to(equal(testPrimaryProtocol.securityManager));
+ expect(manager.currentHMILevel).to(equal(SDLHMILevelFull));
OCMVerifyAll(testStreamingProtocolDelegate);
});
@@ -731,7 +739,6 @@ describe(@"the secondary transport manager ", ^{
[NSThread sleepForTimeInterval:0.1];
expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateReconnecting));
- expect(manager.isAppReady).to(equal(NO));
OCMVerifyAll(testStreamingProtocolDelegate);
});
});
@@ -776,7 +783,7 @@ describe(@"the secondary transport manager ", ^{
beforeEach(^{
testTcpIpAddress = @"172.16.12.34";
testTcpPort = 12345;
- manager.appReady = YES;
+ manager.currentHMILevel = SDLHMILevelFull;
testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
@@ -864,7 +871,7 @@ describe(@"the secondary transport manager ", ^{
manager.secondaryTransportType = SDLTransportSelectionTCP;
manager.ipAddress = @"192.168.1.1";
manager.tcpPort = 12345;
- manager.appReady = YES;
+ manager.currentHMILevel = SDLHMILevelFull;
testTransportEventUpdateHeader = [SDLProtocolHeader headerForVersion:5];
testTransportEventUpdateHeader.frameType = SDLFrameTypeControl;
@@ -1023,7 +1030,6 @@ describe(@"the secondary transport manager ", ^{
manager.secondaryTransportType = SDLTransportSelectionTCP;
manager.ipAddress = @"192.168.1.1";
manager.tcpPort = 12345;
- manager.appReady = YES;
testTransportEventUpdateHeader = [SDLProtocolHeader headerForVersion:5];
testTransportEventUpdateHeader.frameType = SDLFrameTypeControl;
@@ -1053,6 +1059,7 @@ describe(@"the secondary transport manager ", ^{
beforeEach(^{
testTcpIpAddress = @"172.16.12.34";
testTcpPort = 12345;
+ manager.currentHMILevel = SDLHMILevelFull;
testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];