diff options
author | Joel Fischer <joeljfischer@gmail.com> | 2020-02-18 13:20:41 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-18 13:20:41 -0500 |
commit | b32a989be79254bea4e26ad3535a9553a54af700 (patch) | |
tree | 89911cd3083b15306e829f631b9638ff96890637 | |
parent | e981200dc95cf116c710e04e8b61a188db7d0006 (diff) | |
parent | baf0c06459abd22e87e98156ce614e3f33c05e04 (diff) | |
download | sdl_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.m | 10 | ||||
-rw-r--r-- | SmartDeviceLink/SDLLogFileModuleMap.m | 26 | ||||
-rw-r--r-- | SmartDeviceLink/SDLSecondaryTransportManager.m | 350 | ||||
-rw-r--r-- | SmartDeviceLink/SDLTCPTransport.m | 12 | ||||
-rw-r--r-- | SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m | 53 |
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]; |