summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2018-09-26 11:45:56 -0400
committerGitHub <noreply@github.com>2018-09-26 11:45:56 -0400
commit18b255407d6d38e3c04a2d62ccecf62841802ea8 (patch)
tree4973697f32e6bf526e8a5496a14e2dcef07adecc
parent3aebf547fbe8ff12d9a28cdf8ad4fb20541c3e7f (diff)
parent0bb6170e002fa13ac3c3bc39e8baa1e6ba7ac2b4 (diff)
downloadsdl_ios-18b255407d6d38e3c04a2d62ccecf62841802ea8.tar.gz
Merge pull request #947 from XevoInc/feature/multiple_transports
Multiple simultaneous transports
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj54
-rw-r--r--SmartDeviceLink/SDLControlFramePayloadConstants.h6
-rw-r--r--SmartDeviceLink/SDLControlFramePayloadConstants.m6
-rw-r--r--SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.h11
-rw-r--r--SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.m88
-rw-r--r--SmartDeviceLink/SDLControlFramePayloadRegisterSecondaryTransportNak.h24
-rw-r--r--SmartDeviceLink/SDLControlFramePayloadRegisterSecondaryTransportNak.m83
-rw-r--r--SmartDeviceLink/SDLControlFramePayloadTransportEventUpdate.h27
-rw-r--r--SmartDeviceLink/SDLControlFramePayloadTransportEventUpdate.m100
-rw-r--r--SmartDeviceLink/SDLGlobals.m2
-rw-r--r--SmartDeviceLink/SDLLifecycleManager.m57
-rw-r--r--SmartDeviceLink/SDLLogFileModuleMap.m2
-rw-r--r--SmartDeviceLink/SDLProtocol.h18
-rw-r--r--SmartDeviceLink/SDLProtocol.m127
-rw-r--r--SmartDeviceLink/SDLProtocolConstants.h8
-rw-r--r--SmartDeviceLink/SDLProtocolListener.h3
-rw-r--r--SmartDeviceLink/SDLProtocolReceivedMessageRouter.m15
-rw-r--r--SmartDeviceLink/SDLProxy.h28
-rw-r--r--SmartDeviceLink/SDLProxy.m16
-rw-r--r--SmartDeviceLink/SDLSecondaryTransportManager.h80
-rw-r--r--SmartDeviceLink/SDLSecondaryTransportManager.m710
-rw-r--r--SmartDeviceLink/SDLSecondaryTransportPrimaryProtocolHandler.h46
-rw-r--r--SmartDeviceLink/SDLSecondaryTransportPrimaryProtocolHandler.m76
-rw-r--r--SmartDeviceLink/SDLStreamingAudioLifecycleManager.m12
-rw-r--r--SmartDeviceLink/SDLStreamingMediaManager.h20
-rw-r--r--SmartDeviceLink/SDLStreamingMediaManager.m32
-rw-r--r--SmartDeviceLink/SDLStreamingProtocolDelegate.h41
-rw-r--r--SmartDeviceLink/SDLStreamingVideoLifecycleManager.m12
-rw-r--r--SmartDeviceLink/SDLV2ProtocolHeader.m2
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m26
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/ControlFramePayloadSpecs/SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m77
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/ControlFramePayloadSpecs/SDLControlFramePayloadTransportEventUpdateSpec.m85
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m121
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/SDLControlFramePayloadRPCStartServiceAckSpec.m76
-rw-r--r--SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m1052
-rw-r--r--SmartDeviceLinkTests/SDLStreamingAudioLifecycleManagerSpec.m420
36 files changed, 3494 insertions, 69 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index cbcc7cfb4..e9323dc81 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -1360,6 +1360,17 @@
E9C32B9D1AB20C5900F283AF /* EAAccessory+SDLProtocols.m in Sources */ = {isa = PBXBuildFile; fileRef = E9C32B991AB20C5900F283AF /* EAAccessory+SDLProtocols.m */; };
E9C32B9E1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = E9C32B9A1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.h */; };
E9C32B9F1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.m in Sources */ = {isa = PBXBuildFile; fileRef = E9C32B9B1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.m */; };
+ EE28F9E3209802A500B1B61D /* SDLStreamingProtocolDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = EE6CBF872064CAEE00EEE0CA /* SDLStreamingProtocolDelegate.h */; };
+ EE38C0C3211C440400E170AD /* SDLSecondaryTransportPrimaryProtocolHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = EE38C0C2211C440400E170AD /* SDLSecondaryTransportPrimaryProtocolHandler.m */; };
+ EE460E082066B5F20006EDD3 /* SDLControlFramePayloadTransportEventUpdateSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EE460E072066B5F20006EDD3 /* SDLControlFramePayloadTransportEventUpdateSpec.m */; };
+ EE460E0A2066B6E40006EDD3 /* SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EE460E092066B6E40006EDD3 /* SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m */; };
+ EE798CA420561210008EDE8E /* SDLSecondaryTransportManager.h in Headers */ = {isa = PBXBuildFile; fileRef = EE798CA32056120F008EDE8E /* SDLSecondaryTransportManager.h */; };
+ EE798CA620561218008EDE8E /* SDLSecondaryTransportManager.m in Sources */ = {isa = PBXBuildFile; fileRef = EE798CA520561217008EDE8E /* SDLSecondaryTransportManager.m */; };
+ EE7B6123205BF6C800E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.m in Sources */ = {isa = PBXBuildFile; fileRef = EE7B6122205BF6C800E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.m */; };
+ EE7B6124205BF97B00E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.h in Headers */ = {isa = PBXBuildFile; fileRef = EE7B6121205BF69C00E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.h */; };
+ EEB1932E205028B700A8940C /* SDLControlFramePayloadTransportEventUpdate.h in Headers */ = {isa = PBXBuildFile; fileRef = EEB1932D205028B700A8940C /* SDLControlFramePayloadTransportEventUpdate.h */; };
+ EEB19330205028BE00A8940C /* SDLControlFramePayloadTransportEventUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = EEB1932F205028BE00A8940C /* SDLControlFramePayloadTransportEventUpdate.m */; };
+ EEB2537E2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EEB2537D2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m */; };
EE5D1B33208EBCA900D17216 /* SDLTCPTransportSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EE5D1B32208EBCA900D17216 /* SDLTCPTransportSpec.m */; };
EEA41D45210BA8CF0006CB6E /* TestTCPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = EEA41D44210BA8CF0006CB6E /* TestTCPServer.m */; };
EED5C9FE1F4D18D100F04000 /* SDLH264Packetizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EED5C9FD1F4D18D100F04000 /* SDLH264Packetizer.h */; };
@@ -2854,6 +2865,18 @@
E9C32B991AB20C5900F283AF /* EAAccessory+SDLProtocols.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "EAAccessory+SDLProtocols.m"; sourceTree = "<group>"; };
E9C32B9A1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "EAAccessoryManager+SDLProtocols.h"; sourceTree = "<group>"; };
E9C32B9B1AB20C5900F283AF /* EAAccessoryManager+SDLProtocols.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "EAAccessoryManager+SDLProtocols.m"; sourceTree = "<group>"; };
+ EE38C0C1211C43E100E170AD /* SDLSecondaryTransportPrimaryProtocolHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLSecondaryTransportPrimaryProtocolHandler.h; sourceTree = "<group>"; };
+ EE38C0C2211C440400E170AD /* SDLSecondaryTransportPrimaryProtocolHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSecondaryTransportPrimaryProtocolHandler.m; sourceTree = "<group>"; };
+ EE460E072066B5F20006EDD3 /* SDLControlFramePayloadTransportEventUpdateSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLControlFramePayloadTransportEventUpdateSpec.m; path = ControlFramePayloadSpecs/SDLControlFramePayloadTransportEventUpdateSpec.m; sourceTree = "<group>"; };
+ EE460E092066B6E40006EDD3 /* SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m; path = ControlFramePayloadSpecs/SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m; sourceTree = "<group>"; };
+ EE6CBF872064CAEE00EEE0CA /* SDLStreamingProtocolDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLStreamingProtocolDelegate.h; sourceTree = "<group>"; };
+ EE798CA32056120F008EDE8E /* SDLSecondaryTransportManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLSecondaryTransportManager.h; sourceTree = "<group>"; };
+ EE798CA520561217008EDE8E /* SDLSecondaryTransportManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLSecondaryTransportManager.m; sourceTree = "<group>"; };
+ EE7B6121205BF69C00E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLControlFramePayloadRegisterSecondaryTransportNak.h; sourceTree = "<group>"; };
+ EE7B6122205BF6C800E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLControlFramePayloadRegisterSecondaryTransportNak.m; sourceTree = "<group>"; };
+ EEB1932D205028B700A8940C /* SDLControlFramePayloadTransportEventUpdate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLControlFramePayloadTransportEventUpdate.h; sourceTree = "<group>"; };
+ EEB1932F205028BE00A8940C /* SDLControlFramePayloadTransportEventUpdate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLControlFramePayloadTransportEventUpdate.m; sourceTree = "<group>"; };
+ EEB2537D2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLSecondaryTransportManagerSpec.m; path = ProxySpecs/SDLSecondaryTransportManagerSpec.m; sourceTree = "<group>"; };
EE5D1B32208EBCA900D17216 /* SDLTCPTransportSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLTCPTransportSpec.m; sourceTree = "<group>"; };
EEA41D43210BA89B0006CB6E /* TestTCPServer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TestTCPServer.h; path = TestUtilities/TestTCPServer.h; sourceTree = "<group>"; };
EEA41D44210BA8CF0006CB6E /* TestTCPServer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = TestTCPServer.m; path = TestUtilities/TestTCPServer.m; sourceTree = "<group>"; };
@@ -3645,6 +3668,10 @@
5D4631131F2136B60092EFDC /* SDLControlFramePayloadNak.m */,
5DB9964C1F26886C002D8795 /* SDLControlFramePayloadEndService.h */,
5DB9964D1F26886C002D8795 /* SDLControlFramePayloadEndService.m */,
+ EE7B6121205BF69C00E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.h */,
+ EE7B6122205BF6C800E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.m */,
+ EEB1932D205028B700A8940C /* SDLControlFramePayloadTransportEventUpdate.h */,
+ EEB1932F205028BE00A8940C /* SDLControlFramePayloadTransportEventUpdate.m */,
);
name = General;
sourceTree = "<group>";
@@ -3707,6 +3734,7 @@
5D5934ED1A85160400687FB9 /* Proxy */ = {
isa = PBXGroup;
children = (
+ EE798CA2205611DC008EDE8E /* Secondary Transport */,
5D6CC8ED1C610E490027F60A /* Security */,
5D5934FE1A851B2500687FB9 /* @protocols */,
5D61FB031A84238A00846EE7 /* SDLLockScreenStatusManager.h */,
@@ -4532,6 +4560,7 @@
1FF7DABF1F75CF6C00B46C30 /* SDLHapticManagerSpec.m */,
5D59DD461B14FDEE00BE744D /* SDLLockScreenStatusManagerSpec.m */,
DA661E2B1E553E7E001C1345 /* SDLStreamingMediaManagerSpec.m */,
+ EEB2537D2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m */,
);
name = ProxySpecs;
sourceTree = "<group>";
@@ -4870,6 +4899,7 @@
isa = PBXGroup;
children = (
5DFFB9141BD7C89700DB3F04 /* SDLConnectionManagerType.h */,
+ EE6CBF872064CAEE00EEE0CA /* SDLStreamingProtocolDelegate.h */,
5D616B501D59042B00553F6B /* Errors */,
5DA3F35C1BC4484B0026F2D0 /* Notifications */,
5DA3F3571BC448160026F2D0 /* Categories */,
@@ -5231,6 +5261,8 @@
children = (
5DC09ED91F2F7FEC00F4AB1D /* SDLControlFramePayloadNakSpec.m */,
5DA23FEF1F2FA0FF009C0313 /* SDLControlFramePayloadEndServiceSpec.m */,
+ EE460E072066B5F20006EDD3 /* SDLControlFramePayloadTransportEventUpdateSpec.m */,
+ EE460E092066B6E40006EDD3 /* SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m */,
);
name = General;
sourceTree = "<group>";
@@ -5584,6 +5616,17 @@
name = "@categories";
sourceTree = "<group>";
};
+ EE798CA2205611DC008EDE8E /* Secondary Transport */ = {
+ isa = PBXGroup;
+ children = (
+ EE798CA32056120F008EDE8E /* SDLSecondaryTransportManager.h */,
+ EE798CA520561217008EDE8E /* SDLSecondaryTransportManager.m */,
+ EE38C0C1211C43E100E170AD /* SDLSecondaryTransportPrimaryProtocolHandler.h */,
+ EE38C0C2211C440400E170AD /* SDLSecondaryTransportPrimaryProtocolHandler.m */,
+ );
+ name = "Secondary Transport";
+ sourceTree = "<group>";
+ };
EE5D1B31208EBC7100D17216 /* TransportSpecs */ = {
isa = PBXGroup;
children = (
@@ -5649,6 +5692,8 @@
5D61FD291A84238C00846EE7 /* SDLPerformInteraction.h in Headers */,
DAC572571D1067270004288B /* SDLTouchManager.h in Headers */,
5D61FE0D1A84238C00846EE7 /* SDLVrCapabilities.h in Headers */,
+ EEB1932E205028B700A8940C /* SDLControlFramePayloadTransportEventUpdate.h in Headers */,
+ EE798CA420561210008EDE8E /* SDLSecondaryTransportManager.h in Headers */,
5DBF06271E64A91D00A5CF03 /* SDLLogFileModule.h in Headers */,
5D61FC531A84238C00846EE7 /* SDLButtonEventMode.h in Headers */,
1FF7DAB61F75B27300B46C30 /* SDLFocusableItemLocatorType.h in Headers */,
@@ -5736,6 +5781,7 @@
5D61FDA91A84238C00846EE7 /* SDLSpeechCapabilities.h in Headers */,
5D92935320B2F76500FCC775 /* SDLTemplateColorScheme.h in Headers */,
5DA23FFD1F312DBA009C0313 /* SDLVideoEncoderDelegate.h in Headers */,
+ EE7B6124205BF97B00E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.h in Headers */,
5D61FCE01A84238C00846EE7 /* SDLKeyboardEvent.h in Headers */,
5D3E48751D6F3B330000BFEF /* SDLAsynchronousOperation.h in Headers */,
DA9F7E631DCBFAC800ACAE48 /* SDLDateTime.h in Headers */,
@@ -6001,6 +6047,7 @@
5D61FCC11A84238C00846EE7 /* SDLHMILevel.h in Headers */,
8B9376D71F3349FC009605C4 /* SDLMetadataTags.h in Headers */,
5D61FD471A84238C00846EE7 /* SDLProtocolListener.h in Headers */,
+ EE28F9E3209802A500B1B61D /* SDLStreamingProtocolDelegate.h in Headers */,
5D61FCA01A84238C00846EE7 /* SDLEncodedSyncPDataResponse.h in Headers */,
5D61FD271A84238C00846EE7 /* SDLPerformAudioPassThruResponse.h in Headers */,
5D61FD251A84238C00846EE7 /* SDLPerformAudioPassThru.h in Headers */,
@@ -6330,6 +6377,7 @@
332A914F1CED9CC60043824C /* SDLAppInfo.m in Sources */,
1E5AD0491F1F773E0029B8AF /* SDLModuleType.m in Sources */,
DA9F7E841DCC047200ACAE48 /* SDLWayPointType.m in Sources */,
+ EE7B6123205BF6C800E0655B /* SDLControlFramePayloadRegisterSecondaryTransportNak.m in Sources */,
5D61FC561A84238C00846EE7 /* SDLButtonName.m in Sources */,
5D61FCC21A84238C00846EE7 /* SDLHMILevel.m in Sources */,
5D9FC2A71FD8815800ACA5C2 /* SDLPCMAudioConverter.m in Sources */,
@@ -6551,6 +6599,7 @@
5D61FC771A84238C00846EE7 /* SDLDeleteFile.m in Sources */,
5D61FC811A84238C00846EE7 /* SDLDeleteSubMenuResponse.m in Sources */,
DA9F7E681DCBFAD400ACAE48 /* SDLOasisAddress.m in Sources */,
+ EEB19330205028BE00A8940C /* SDLControlFramePayloadTransportEventUpdate.m in Sources */,
5D61FC7B1A84238C00846EE7 /* SDLDeleteInteractionChoiceSet.m in Sources */,
5D61FDC01A84238C00846EE7 /* SDLSystemRequest.m in Sources */,
5D92938120B70CD600FCC775 /* SDLCheckChoiceVROptionalOperation.m in Sources */,
@@ -6562,6 +6611,7 @@
5D92935F20B33FF700FCC775 /* SDLChoiceSet.m in Sources */,
5D61FC321A84238C00846EE7 /* SDLAddSubMenu.m in Sources */,
5D61FDF61A84238C00846EE7 /* SDLV1ProtocolHeader.m in Sources */,
+ EE798CA620561218008EDE8E /* SDLSecondaryTransportManager.m in Sources */,
5D61FDAA1A84238C00846EE7 /* SDLSpeechCapabilities.m in Sources */,
1E5AD0951F20BEAD0029B8AF /* SDLSetInteriorVehicleDataResponse.m in Sources */,
5D61FDB41A84238C00846EE7 /* SDLSubscribeVehicleDataResponse.m in Sources */,
@@ -6619,6 +6669,7 @@
1FF7DABC1F75B2BF00B46C30 /* SDLFocusableItemLocator.m in Sources */,
EED5CA021F4D18EC00F04000 /* SDLRAWH264Packetizer.m in Sources */,
5D61FC851A84238C00846EE7 /* SDLDeviceLevelStatus.m in Sources */,
+ EE38C0C3211C440400E170AD /* SDLSecondaryTransportPrimaryProtocolHandler.m in Sources */,
5D9FDA981F2A7D3F00A495C8 /* emhashmap.c in Sources */,
5D61FD1E1A84238C00846EE7 /* SDLOnTBTClientState.m in Sources */,
5D0C29FD20D93D8C008B56CD /* SDLVideoStreamingState.m in Sources */,
@@ -6864,7 +6915,9 @@
5DA23FF01F2FA0FF009C0313 /* SDLControlFramePayloadEndServiceSpec.m in Sources */,
162E83591A9BDE8B00906325 /* SDLListFilesResponseSpec.m in Sources */,
162E832A1A9BDE8B00906325 /* SDLDeleteInteractionChoiceSetSpec.m in Sources */,
+ EE460E0A2066B6E40006EDD3 /* SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m in Sources */,
162E839D1A9BDE8B00906325 /* SDLRPCResponseSpec.m in Sources */,
+ EE460E082066B5F20006EDD3 /* SDLControlFramePayloadTransportEventUpdateSpec.m in Sources */,
1EE8C4521F38657D00FDC2CF /* SDLTemperatureSpec.m in Sources */,
5DB1BCDF1D243DD3002FFC37 /* SDLLockScreenConfigurationSpec.m in Sources */,
162E82E51A9BDE8B00906325 /* SDLImageTypeSpec.m in Sources */,
@@ -7018,6 +7071,7 @@
5DBEFA541F434B9E009EE295 /* SDLStreamingMediaConfigurationSpec.m in Sources */,
1EB59CD2202DCA9B00343A61 /* SDLMassageModeSpec.m in Sources */,
162E82CB1A9BDE8A00906325 /* SDLAppHMITypeSpec.m in Sources */,
+ EEB2537E2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m in Sources */,
162E83031A9BDE8B00906325 /* SDLTriggerSource.m in Sources */,
162E82D61A9BDE8A00906325 /* SDLComponentVolumeStatusSpec.m in Sources */,
162E835C1A9BDE8B00906325 /* SDLPutFileResponseSpec.m in Sources */,
diff --git a/SmartDeviceLink/SDLControlFramePayloadConstants.h b/SmartDeviceLink/SDLControlFramePayloadConstants.h
index f95b90ef8..37eb51dfc 100644
--- a/SmartDeviceLink/SDLControlFramePayloadConstants.h
+++ b/SmartDeviceLink/SDLControlFramePayloadConstants.h
@@ -14,8 +14,14 @@ extern int64_t const SDLControlFrameInt64NotFound;
extern char *const SDLControlFrameProtocolVersionKey;
extern char *const SDLControlFrameHashIdKey;
extern char *const SDLControlFrameMTUKey;
+extern char *const SDLControlFrameReasonKey;
extern char *const SDLControlFrameRejectedParams;
extern char *const SDLControlFrameVideoProtocolKey;
extern char *const SDLControlFrameVideoCodecKey;
extern char *const SDLControlFrameHeightKey;
extern char *const SDLControlFrameWidthKey;
+extern char *const SDLControlFrameSecondaryTransportsKey;
+extern char *const SDLControlFrameAudioServiceTransportsKey;
+extern char *const SDLControlFrameVideoServiceTransportsKey;
+extern char *const SDLControlFrameTCPIPAddressKey;
+extern char *const SDLControlFrameTCPPortKey;
diff --git a/SmartDeviceLink/SDLControlFramePayloadConstants.m b/SmartDeviceLink/SDLControlFramePayloadConstants.m
index 8d9965f37..41048765d 100644
--- a/SmartDeviceLink/SDLControlFramePayloadConstants.m
+++ b/SmartDeviceLink/SDLControlFramePayloadConstants.m
@@ -14,8 +14,14 @@ int64_t const SDLControlFrameInt64NotFound = -1;
char *const SDLControlFrameProtocolVersionKey = "protocolVersion";
char *const SDLControlFrameHashIdKey = "hashId";
char *const SDLControlFrameMTUKey = "mtu";
+char *const SDLControlFrameReasonKey = "reason";
char *const SDLControlFrameRejectedParams = "rejectedParams";
char *const SDLControlFrameVideoProtocolKey = "videoProtocol";
char *const SDLControlFrameVideoCodecKey = "videoCodec";
char *const SDLControlFrameHeightKey = "height";
char *const SDLControlFrameWidthKey = "width";
+char *const SDLControlFrameSecondaryTransportsKey = "secondaryTransports";
+char *const SDLControlFrameAudioServiceTransportsKey = "audioServiceTransports";
+char *const SDLControlFrameVideoServiceTransportsKey = "videoServiceTransports";
+char *const SDLControlFrameTCPIPAddressKey = "tcpIpAddress";
+char *const SDLControlFrameTCPPortKey = "tcpPort";
diff --git a/SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.h b/SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.h
index 078405e1e..ba339bdc2 100644
--- a/SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.h
+++ b/SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.h
@@ -23,7 +23,16 @@ NS_ASSUME_NONNULL_BEGIN
/// The negotiated version of the protocol. Must be in the format "Major.Minor.Patch"
@property (copy, nonatomic, readonly, nullable) NSString *protocolVersion;
-- (instancetype)initWithHashId:(int32_t)hashId mtu:(int64_t)mtu protocolVersion:(nullable NSString *)protocolVersion;
+/** The transport types for Secondary Transport */
+@property (copy, nonatomic, readonly, nullable) NSArray<NSString *> *secondaryTransports;
+
+/** List of transports that are allowed to carry audio service. The values can be either 1 (primary transport) or 2 (secondary transport) and are listed in preferred order. */
+@property (copy, nonatomic, readonly, nullable) NSArray<NSNumber *> *audioServiceTransports;
+
+/** List of transports that are allowed to carry video service. The values can be either 1 (primary transport) or 2 (secondary transport) and are listed in preferred order. */
+@property (copy, nonatomic, readonly, nullable) NSArray<NSNumber *> *videoServiceTransports;
+
+- (instancetype)initWithHashId:(int32_t)hashId mtu:(int64_t)mtu protocolVersion:(nullable NSString *)protocolVersion secondaryTransports:(nullable NSArray<NSString *> *)secondaryTransports audioServiceTransports:(nullable NSArray<NSNumber *> *)audioServiceTransports videoServiceTransports:(nullable NSArray<NSNumber *> *)videoServiceTransports;
@end
diff --git a/SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.m b/SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.m
index cc2c2c3a5..536ecfc86 100644
--- a/SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.m
+++ b/SmartDeviceLink/SDLControlFramePayloadRPCStartServiceAck.m
@@ -19,19 +19,30 @@ NS_ASSUME_NONNULL_BEGIN
@property (assign, nonatomic, readwrite) int32_t hashId;
@property (assign, nonatomic, readwrite) int64_t mtu;
@property (copy, nonatomic, readwrite, nullable) NSString *protocolVersion;
+@property (copy, nonatomic, readwrite, nullable) NSArray<NSString *> *secondaryTransports;
+@property (copy, nonatomic, readwrite, nullable) NSArray<NSNumber *> *audioServiceTransports;
+@property (copy, nonatomic, readwrite, nullable) NSArray<NSNumber *> *videoServiceTransports;
@end
@implementation SDLControlFramePayloadRPCStartServiceAck
-- (instancetype)initWithHashId:(int32_t)hashId mtu:(int64_t)mtu protocolVersion:(nullable NSString *)protocolVersion {
+- (instancetype)initWithHashId:(int32_t)hashId
+ mtu:(int64_t)mtu
+ protocolVersion:(nullable NSString *)protocolVersion
+ secondaryTransports:(nullable NSArray<NSString *> *)secondaryTransports
+ audioServiceTransports:(nullable NSArray<NSNumber *> *)audioServiceTransports
+ videoServiceTransports:(nullable NSArray<NSNumber *> *)videoServiceTransports {
self = [super init];
if (!self) return nil;
_hashId = hashId;
_mtu = mtu;
_protocolVersion = protocolVersion;
+ _secondaryTransports = secondaryTransports;
+ _audioServiceTransports = audioServiceTransports;
+ _videoServiceTransports = videoServiceTransports;
return self;
}
@@ -72,6 +83,22 @@ NS_ASSUME_NONNULL_BEGIN
bson_object_put_string(&payloadObject, SDLControlFrameProtocolVersionKey, (char *)self.protocolVersion.UTF8String);
}
+ if (self.secondaryTransports != nil) {
+ BsonArray arrayObject;
+ // Currently there are 8 transport types defined. So initial value of 8 should be sufficient.
+ bson_array_initialize(&arrayObject, 8);
+
+ for (NSString *transport in self.secondaryTransports) {
+ bson_array_add_string(&arrayObject, (char *)transport.UTF8String);
+ }
+
+ bson_object_put_array(&payloadObject, SDLControlFrameSecondaryTransportsKey, &arrayObject);
+ }
+
+ [self sdl_addServiceTransports:&payloadObject fromArray:self.audioServiceTransports forKey:SDLControlFrameAudioServiceTransportsKey];
+
+ [self sdl_addServiceTransports:&payloadObject fromArray:self.videoServiceTransports forKey:SDLControlFrameVideoServiceTransportsKey];
+
BytePtr bsonData = bson_object_to_bytes(&payloadObject);
NSUInteger length = bson_object_size(&payloadObject);
@@ -91,11 +118,68 @@ NS_ASSUME_NONNULL_BEGIN
self.protocolVersion = [NSString stringWithUTF8String:utf8String];
}
+ BsonArray *arrayObject = bson_object_get_array(&payloadObject, SDLControlFrameSecondaryTransportsKey);
+ if (arrayObject != NULL) {
+ NSMutableArray<NSString *> *secondaryTransports = [NSMutableArray array];
+ size_t index = 0;
+
+ while ((utf8String = bson_array_get_string(arrayObject, index)) != NULL) {
+ [secondaryTransports addObject:[NSString stringWithUTF8String:utf8String]];
+ index++;
+ }
+ self.secondaryTransports = [secondaryTransports copy];
+ }
+
+ self.audioServiceTransports = [self sdl_getServiceTransports:&payloadObject forKey:SDLControlFrameAudioServiceTransportsKey];
+
+ self.videoServiceTransports = [self sdl_getServiceTransports:&payloadObject forKey:SDLControlFrameVideoServiceTransportsKey];
+
bson_object_deinitialize(&payloadObject);
}
+- (nullable NSArray<NSNumber *> *)sdl_getServiceTransports:(BsonObject *)payloadObject forKey:(const char * const)key {
+ if (payloadObject == NULL || key == NULL) {
+ return nil;
+ }
+
+ BsonArray *arrayObject = bson_object_get_array(payloadObject, key);
+ if (arrayObject == NULL) {
+ return nil;
+ }
+
+ NSMutableArray<NSNumber *> *transports = [NSMutableArray array];
+ int32_t num;
+ size_t index = 0;
+
+ while ((num = bson_array_get_int32(arrayObject, index)) != -1) {
+ [transports addObject:@(num)];
+ index++;
+ }
+
+ return [transports copy];
+}
+
+- (void)sdl_addServiceTransports:(BsonObject *)payloadObject fromArray:(NSArray<NSNumber *> *)array forKey:(const char * const)key {
+ if (payloadObject == NULL || array == nil || key == NULL) {
+ return;
+ }
+
+ BsonArray arrayObject;
+ // currently there are 2 transports defined (primary and secondary)
+ bson_array_initialize(&arrayObject, 2);
+
+ for (NSNumber *num in array) {
+ int32_t transport = [num intValue];
+ if (transport != -1) {
+ bson_array_add_int32(&arrayObject, transport);
+ }
+ }
+
+ bson_object_put_array(payloadObject, key, &arrayObject);
+}
+
- (NSString *)description {
- return [NSString stringWithFormat:@"<%@>: Protocol Version: %@, hash id: %d, MTU: %lld", NSStringFromClass(self.class), self.protocolVersion, self.hashId, self.mtu];
+ return [NSString stringWithFormat:@"<%@>: Protocol Version: %@, hash id: %d, MTU: %lld, secondary transports: %@, transports for audio service: %@, transports for video service: %@", NSStringFromClass(self.class), self.protocolVersion, self.hashId, self.mtu, self.secondaryTransports, self.audioServiceTransports, self.videoServiceTransports];
}
@end
diff --git a/SmartDeviceLink/SDLControlFramePayloadRegisterSecondaryTransportNak.h b/SmartDeviceLink/SDLControlFramePayloadRegisterSecondaryTransportNak.h
new file mode 100644
index 000000000..1435ba42e
--- /dev/null
+++ b/SmartDeviceLink/SDLControlFramePayloadRegisterSecondaryTransportNak.h
@@ -0,0 +1,24 @@
+//
+// SDLControlFramePayloadRegisterSecondaryTransportNak.h
+// SmartDeviceLink-iOS
+//
+// Created by Sho Amano on 2018/03/16.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLControlFramePayloadType.h"
+
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLControlFramePayloadRegisterSecondaryTransportNak : NSObject <SDLControlFramePayloadType>
+
+@property (copy, nonatomic, readonly, nullable) NSString *reason;
+
+- (instancetype)initWithReason:(nullable NSString *)reason;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLControlFramePayloadRegisterSecondaryTransportNak.m b/SmartDeviceLink/SDLControlFramePayloadRegisterSecondaryTransportNak.m
new file mode 100644
index 000000000..570d41fa5
--- /dev/null
+++ b/SmartDeviceLink/SDLControlFramePayloadRegisterSecondaryTransportNak.m
@@ -0,0 +1,83 @@
+//
+// SDLControlFramePayloadRegisterSecondaryTransportNak.m
+// SmartDeviceLink
+//
+// Created by Sho Amano on 2018/03/16.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import "SDLControlFramePayloadRegisterSecondaryTransportNak.h"
+
+#import "bson_object.h"
+#import "SDLControlFramePayloadConstants.h"
+
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLControlFramePayloadRegisterSecondaryTransportNak ()
+
+@property (copy, nonatomic, readwrite, nullable) NSString *reason;
+
+@end
+
+@implementation SDLControlFramePayloadRegisterSecondaryTransportNak
+
+- (instancetype)initWithReason:(nullable NSString *)reason {
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+
+ _reason = reason;
+
+ return self;
+}
+
+- (instancetype)initWithData:(nullable NSData *)data {
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+
+ if (data.length > 0) {
+ [self sdl_parse:data];
+ }
+
+ return self;
+}
+
+- (nullable NSData *)data {
+ if (self.reason == nil) {
+ return nil;
+ }
+
+ BsonObject payloadObject;
+ bson_object_initialize_default(&payloadObject);
+ bson_object_put_string(&payloadObject, SDLControlFrameReasonKey, (char *)self.reason.UTF8String);
+
+ BytePtr bsonData = bson_object_to_bytes(&payloadObject);
+ NSUInteger length = bson_object_size(&payloadObject);
+
+ bson_object_deinitialize(&payloadObject);
+
+ return [[NSData alloc] initWithBytes:bsonData length:length];
+}
+
+- (void)sdl_parse:(NSData *)data {
+ BsonObject payloadObject = bson_object_from_bytes((BytePtr)data.bytes);
+
+ char *reasonString = bson_object_get_string(&payloadObject, SDLControlFrameReasonKey);
+ if (reasonString != NULL) {
+ self.reason = [NSString stringWithUTF8String:reasonString];
+ }
+
+ bson_object_deinitialize(&payloadObject);
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<%@>: reason: %@", NSStringFromClass(self.class), self.reason];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLControlFramePayloadTransportEventUpdate.h b/SmartDeviceLink/SDLControlFramePayloadTransportEventUpdate.h
new file mode 100644
index 000000000..747934790
--- /dev/null
+++ b/SmartDeviceLink/SDLControlFramePayloadTransportEventUpdate.h
@@ -0,0 +1,27 @@
+//
+// SDLControlFramePayloadTransportEventUpdate.h
+// SmartDeviceLink-iOS
+//
+// Created by Sho Amano on 2018/03/05.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLControlFramePayloadType.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLControlFramePayloadTransportEventUpdate : NSObject <SDLControlFramePayloadType>
+
+/** A string representing IP address of Core's TCP transport. It can be either IPv4 or IPv6 address. */
+@property (copy, nonatomic, readonly, nullable) NSString *tcpIpAddress;
+/** TCP port number that Core is listening on */
+@property (assign, nonatomic, readonly) int32_t tcpPort;
+
+
+- (instancetype)initWithTcpIpAddress:(nullable NSString *)tcpIpAddress tcpPort:(int32_t)tcpPort;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLControlFramePayloadTransportEventUpdate.m b/SmartDeviceLink/SDLControlFramePayloadTransportEventUpdate.m
new file mode 100644
index 000000000..daaf3f13c
--- /dev/null
+++ b/SmartDeviceLink/SDLControlFramePayloadTransportEventUpdate.m
@@ -0,0 +1,100 @@
+//
+// SDLControlFramePayloadTransportEventUpdate.m
+// SmartDeviceLink-iOS
+//
+// Created by Sho Amano on 2018/03/05.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import "SDLControlFramePayloadTransportEventUpdate.h"
+
+#import "bson_object.h"
+#import "SDLControlFramePayloadConstants.h"
+
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLControlFramePayloadTransportEventUpdate ()
+
+// A string representing IP address of Core's TCP transport. It can be either IPv4 or IPv6 address.
+@property (copy, nonatomic, readwrite, nullable) NSString *tcpIpAddress;
+// TCP port number that Core is listening on
+@property (assign, nonatomic, readwrite) int32_t tcpPort;
+
+@end
+
+@implementation SDLControlFramePayloadTransportEventUpdate
+
+- (instancetype)initWithTcpIpAddress:(nullable NSString *)tcpIpAddress tcpPort:(int32_t)tcpPort {
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+
+ _tcpIpAddress = tcpIpAddress;
+ if (tcpPort >= 0 && tcpPort <= 65535) {
+ _tcpPort = tcpPort;
+ } else {
+ _tcpPort = SDLControlFrameInt32NotFound;
+ }
+
+ return self;
+}
+
+- (instancetype)initWithData:(nullable NSData *)data {
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+
+ _tcpPort = SDLControlFrameInt32NotFound;
+
+ if (data.length > 0) {
+ [self sdl_parse:data];
+ }
+
+ return self;
+}
+
+- (nullable NSData *)data {
+ if (self.tcpIpAddress == nil && self.tcpPort == SDLControlFrameInt32NotFound) {
+ return nil;
+ }
+
+ BsonObject payloadObject;
+ bson_object_initialize_default(&payloadObject);
+
+ if (self.tcpIpAddress != nil) {
+ bson_object_put_string(&payloadObject, SDLControlFrameTCPIPAddressKey, (char *)self.tcpIpAddress.UTF8String);
+ }
+ if (self.tcpPort != SDLControlFrameInt32NotFound) {
+ bson_object_put_int32(&payloadObject, SDLControlFrameTCPPortKey, self.tcpPort);
+ }
+
+ BytePtr bsonData = bson_object_to_bytes(&payloadObject);
+ NSUInteger length = bson_object_size(&payloadObject);
+
+ bson_object_deinitialize(&payloadObject);
+
+ return [[NSData alloc] initWithBytes:bsonData length:length];
+}
+
+- (void)sdl_parse:(NSData *)data {
+ BsonObject payloadObject = bson_object_from_bytes((BytePtr)data.bytes);
+
+ char *utf8String = bson_object_get_string(&payloadObject, SDLControlFrameTCPIPAddressKey);
+ if (utf8String != NULL) {
+ self.tcpIpAddress = [NSString stringWithUTF8String:utf8String];
+ }
+ self.tcpPort = bson_object_get_int32(&payloadObject, SDLControlFrameTCPPortKey);
+
+ bson_object_deinitialize(&payloadObject);
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<%@>: TCP IP address: %@, TCP port: %d", NSStringFromClass(self.class), self.tcpIpAddress, self.tcpPort];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLGlobals.m b/SmartDeviceLink/SDLGlobals.m
index 4e26b5d61..d5364a119 100644
--- a/SmartDeviceLink/SDLGlobals.m
+++ b/SmartDeviceLink/SDLGlobals.m
@@ -13,7 +13,7 @@
NS_ASSUME_NONNULL_BEGIN
// VERSION DEPENDENT CODE
-NSString *const SDLMaxProxyProtocolVersion = @"5.0.0";
+NSString *const SDLMaxProxyProtocolVersion = @"5.1.0";
NSString *const SDLMaxProxyRPCVersion = @"5.0.0";
NSUInteger const SDLDefaultMTUSize = UINT32_MAX;
diff --git a/SmartDeviceLink/SDLLifecycleManager.m b/SmartDeviceLink/SDLLifecycleManager.m
index ed5891466..0f6a7d4a6 100644
--- a/SmartDeviceLink/SDLLifecycleManager.m
+++ b/SmartDeviceLink/SDLLifecycleManager.m
@@ -45,11 +45,13 @@
#import "SDLResponseDispatcher.h"
#import "SDLResult.h"
#import "SDLScreenManager.h"
+#import "SDLSecondaryTransportManager.h"
#import "SDLSequentialRPCRequestOperation.h"
#import "SDLSetAppIcon.h"
#import "SDLStateMachine.h"
#import "SDLStreamingMediaConfiguration.h"
#import "SDLStreamingMediaManager.h"
+#import "SDLStreamingProtocolDelegate.h"
#import "SDLSystemCapabilityManager.h"
#import "SDLUnregisterAppInterface.h"
@@ -70,7 +72,7 @@ SDLLifecycleState *const SDLLifecycleStateReady = @"Ready";
#pragma mark - SDLManager Private Interface
-@interface SDLLifecycleManager () <SDLConnectionManagerType>
+@interface SDLLifecycleManager () <SDLConnectionManagerType, SDLStreamingProtocolDelegate>
// Readonly public properties
@property (copy, nonatomic, readwrite) SDLConfiguration *configuration;
@@ -79,6 +81,7 @@ SDLLifecycleState *const SDLLifecycleStateReady = @"Ready";
@property (strong, nonatomic, readwrite) SDLStateMachine *lifecycleStateMachine;
// Private properties
+@property (strong, nonatomic, nullable) SDLSecondaryTransportManager *secondaryTransportManager;
@property (copy, nonatomic) SDLManagerReadyBlock readyHandler;
@property (copy, nonatomic) dispatch_queue_t lifecycleQueue;
@@ -205,11 +208,18 @@ SDLLifecycleState *const SDLLifecycleStateReady = @"Ready";
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (self.configuration.lifecycleConfig.tcpDebugMode) {
+ // secondary transport manager is not used
+ self.secondaryTransportManager = nil;
self.proxy = [SDLProxy tcpProxyWithListener:self.notificationDispatcher
tcpIPAddress:self.configuration.lifecycleConfig.tcpDebugIPAddress
- tcpPort:@(self.configuration.lifecycleConfig.tcpDebugPort).stringValue];
+ tcpPort:@(self.configuration.lifecycleConfig.tcpDebugPort).stringValue
+ secondaryTransportManager:self.secondaryTransportManager];
} else {
- self.proxy = [SDLProxy iapProxyWithListener:self.notificationDispatcher];
+ // 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];
}
#pragma clang diagnostic pop
}
@@ -229,7 +239,12 @@ SDLLifecycleState *const SDLLifecycleStateReady = @"Ready";
[self.permissionManager stop];
[self.lockScreenManager stop];
[self.screenManager stop];
- [self.streamManager stop];
+ if (self.secondaryTransportManager != nil) {
+ [self.secondaryTransportManager stop];
+ } else {
+ [self audioServiceProtocolDidUpdateFromOldProtocol:self.proxy.protocol toNewProtocol:nil];
+ [self videoServiceProtocolDidUpdateFromOldProtocol:self.proxy.protocol toNewProtocol:nil];
+ }
[self.systemCapabilityManager stop];
[self.responseDispatcher clear];
@@ -361,8 +376,10 @@ SDLLifecycleState *const SDLLifecycleStateReady = @"Ready";
dispatch_group_leave(managerGroup);
}];
- if (self.streamManager != nil) {
- [self.streamManager startWithProtocol:self.proxy.protocol];
+ // if secondary transport manager is used, streaming media manager will be started through onAudioServiceProtocolUpdated and onVideoServiceProtocolUpdated
+ if (self.secondaryTransportManager == nil && self.streamManager != nil) {
+ [self audioServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:self.proxy.protocol];
+ [self videoServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:self.proxy.protocol];
}
dispatch_group_enter(managerGroup);
@@ -695,6 +712,34 @@ SDLLifecycleState *const SDLLifecycleStateReady = @"Ready";
}
}
+#pragma mark Streaming protocol listener
+
+- (void)audioServiceProtocolDidUpdateFromOldProtocol:(nullable SDLProtocol *)oldProtocol toNewProtocol:(nullable SDLProtocol *)newProtocol {
+ if ((oldProtocol == nil && newProtocol == nil) || (oldProtocol == newProtocol)) {
+ return;
+ }
+
+ if (oldProtocol != nil) {
+ [self.streamManager stopAudio];
+ }
+ if (newProtocol != nil) {
+ [self.streamManager startAudioWithProtocol:newProtocol];
+ }
+}
+
+- (void)videoServiceProtocolDidUpdateFromOldProtocol:(nullable SDLProtocol *)oldProtocol toNewProtocol:(nullable SDLProtocol *)newProtocol {
+ if ((oldProtocol == nil && newProtocol == nil) || (oldProtocol == newProtocol)) {
+ return;
+ }
+
+ if (oldProtocol != nil) {
+ [self.streamManager stopVideo];
+ }
+ if (newProtocol != nil) {
+ [self.streamManager startVideoWithProtocol:newProtocol];
+ }
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLLogFileModuleMap.m b/SmartDeviceLink/SDLLogFileModuleMap.m
index c9aa4150b..c777672ce 100644
--- a/SmartDeviceLink/SDLLogFileModuleMap.m
+++ b/SmartDeviceLink/SDLLogFileModuleMap.m
@@ -30,7 +30,7 @@
}
+ (SDLLogFileModule *)sdl_transportModule {
- return [SDLLogFileModule moduleWithName:@"Transport" files:[NSSet setWithArray:@[@"SDLIAPSession", @"SDLIAPTransport", @"SDLStreamDelegate", @"SDLTCPTransport"]]];
+ return [SDLLogFileModule moduleWithName:@"Transport" files:[NSSet setWithArray:@[@"SDLIAPSession", @"SDLIAPTransport", @"SDLSecondaryTransportManager", @"SDLSecondaryTransportPrimaryProtocolHandler", @"SDLStreamDelegate", @"SDLTCPTransport"]]];
}
+ (SDLLogFileModule *)sdl_proxyModule {
diff --git a/SmartDeviceLink/SDLProtocol.h b/SmartDeviceLink/SDLProtocol.h
index 35055fbf6..e4d0e44fc 100644
--- a/SmartDeviceLink/SDLProtocol.h
+++ b/SmartDeviceLink/SDLProtocol.h
@@ -41,6 +41,8 @@ extern NSString *const SDLProtocolSecurityErrorDomain;
/**
* A table for tracking all subscribers
+ *
+ * If you update protocolDelegateTable while the protocol is running, please make sure to guard with @synchronized.
*/
@property (nullable, strong, nonatomic) NSHashTable<id<SDLProtocolListener>> *protocolDelegateTable;
@@ -57,6 +59,17 @@ extern NSString *const SDLProtocolSecurityErrorDomain;
#pragma mark - Sending
/**
+ * Pre-configure protocol header for specified service type
+ *
+ * This is used to initialize Session ID before starting a protocol.
+ *
+ * @param header The header which is applied to the service type
+ * @param serviceType A SDLServiceType object
+ * @return YES if the header is successfully set, NO otherwise
+ */
+- (BOOL)storeHeader:(SDLProtocolHeader *)header forServiceType:(SDLServiceType)serviceType;
+
+/**
* Sends a start service message to Core
*
* @param serviceType A SDLServiceType object
@@ -81,6 +94,11 @@ extern NSString *const SDLProtocolSecurityErrorDomain;
- (void)endServiceWithType:(SDLServiceType)serviceType;
/**
+ * Sends a Register Secondary Transport control frame to Core
+ */
+- (void)registerSecondaryTransport;
+
+/**
* Sends an unencrypted RPC to Core
*
* @param message A SDLRPCMessage message
diff --git a/SmartDeviceLink/SDLProtocol.m b/SmartDeviceLink/SDLProtocol.m
index 405fdd3bc..ab6eb647e 100644
--- a/SmartDeviceLink/SDLProtocol.m
+++ b/SmartDeviceLink/SDLProtocol.m
@@ -8,6 +8,7 @@
#import "SDLControlFramePayloadConstants.h"
#import "SDLControlFramePayloadEndService.h"
#import "SDLControlFramePayloadNak.h"
+#import "SDLControlFramePayloadRegisterSecondaryTransportNak.h"
#import "SDLControlFramePayloadRPCStartService.h"
#import "SDLControlFramePayloadRPCStartServiceAck.h"
#import "SDLLogMacros.h"
@@ -74,6 +75,15 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Service metadata
+- (BOOL)storeHeader:(SDLProtocolHeader *)header forServiceType:(SDLServiceType)serviceType {
+ if (header == nil) {
+ return NO;
+ }
+
+ SDLLogD(@"Storing SessionID %i of serviceType %i", header.sessionID, serviceType);
+ self.serviceHeaders[@(serviceType)] = [header copy];
+ return YES;
+}
- (UInt8)sdl_retrieveSessionIDforServiceType:(SDLServiceType)serviceType {
SDLProtocolHeader *header = self.serviceHeaders[@(serviceType)];
@@ -87,7 +97,11 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - SDLTransportDelegate
- (void)onTransportConnected {
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners;
+ @synchronized(self.protocolDelegateTable) {
+ listeners = self.protocolDelegateTable.allObjects;
+ }
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(onProtocolOpened)]) {
[listener onProtocolOpened];
}
@@ -95,7 +109,11 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)onTransportDisconnected {
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners;
+ @synchronized(self.protocolDelegateTable) {
+ listeners = self.protocolDelegateTable.allObjects;
+ }
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(onProtocolClosed)]) {
[listener onProtocolClosed];
}
@@ -219,6 +237,23 @@ NS_ASSUME_NONNULL_BEGIN
}
+#pragma mark - Register Secondary Transport
+
+- (void)registerSecondaryTransport {
+ SDLProtocolHeader *header = [SDLProtocolHeader headerForVersion:(UInt8)[SDLGlobals sharedGlobals].majorProtocolVersion];
+ header.frameType = SDLFrameTypeControl;
+ header.serviceType = SDLServiceTypeControl;
+ header.frameData = SDLFrameInfoRegisterSecondaryTransport;
+ header.sessionID = [self sdl_retrieveSessionIDforServiceType:SDLServiceTypeControl];
+ if ([SDLGlobals sharedGlobals].majorProtocolVersion >= 2) {
+ [((SDLV2ProtocolHeader *)header) setMessageID:++_messageID];
+ }
+
+ SDLProtocolMessage *message = [SDLProtocolMessage messageWithHeader:header andPayload:nil];
+ [self sdl_sendDataToTransport:message.data onService:SDLServiceTypeControl];
+}
+
+
#pragma mark - Send Data
- (void)sendRPC:(SDLRPCMessage *)message {
@@ -467,7 +502,8 @@ NS_ASSUME_NONNULL_BEGIN
self.serviceHeaders[@(startServiceACK.header.serviceType)] = [startServiceACK.header copy];
// Pass along to all the listeners
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(handleProtocolStartServiceACKMessage:)]) {
[listener handleProtocolStartServiceACKMessage:startServiceACK];
}
@@ -477,7 +513,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)handleProtocolStartServiceNAKMessage:(SDLProtocolMessage *)startServiceNAK {
[self sdl_logControlNAKPayload:startServiceNAK];
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(handleProtocolStartServiceNAKMessage:)]) {
[listener handleProtocolStartServiceNAKMessage:startServiceNAK];
}
@@ -488,7 +525,8 @@ NS_ASSUME_NONNULL_BEGIN
// Remove the session id
[self.serviceHeaders removeObjectForKey:@(endServiceACK.header.serviceType)];
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(handleProtocolEndServiceACKMessage:)]) {
[listener handleProtocolEndServiceACKMessage:endServiceACK];
}
@@ -498,13 +536,34 @@ NS_ASSUME_NONNULL_BEGIN
- (void)handleProtocolEndServiceNAKMessage:(SDLProtocolMessage *)endServiceNAK {
[self sdl_logControlNAKPayload:endServiceNAK];
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(handleProtocolEndServiceNAKMessage:)]) {
[listener handleProtocolEndServiceNAKMessage:endServiceNAK];
}
}
}
+- (void)handleProtocolRegisterSecondaryTransportACKMessage:(SDLProtocolMessage *)registerSecondaryTransportACK {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
+ if ([listener respondsToSelector:@selector(handleProtocolRegisterSecondaryTransportACKMessage:)]) {
+ [listener handleProtocolRegisterSecondaryTransportACKMessage:registerSecondaryTransportACK];
+ }
+ }
+}
+
+- (void)handleProtocolRegisterSecondaryTransportNAKMessage:(SDLProtocolMessage *)registerSecondaryTransportNAK {
+ [self sdl_logControlNAKPayload:registerSecondaryTransportNAK];
+
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
+ if ([listener respondsToSelector:@selector(handleProtocolRegisterSecondaryTransportNAKMessage:)]) {
+ [listener handleProtocolRegisterSecondaryTransportNAKMessage:registerSecondaryTransportNAK];
+ }
+ }
+}
+
- (void)handleHeartbeatForSession:(Byte)session {
// Respond with a heartbeat ACK
SDLProtocolHeader *header = [SDLProtocolHeader headerForVersion:(UInt8)[SDLGlobals sharedGlobals].majorProtocolVersion];
@@ -515,7 +574,8 @@ NS_ASSUME_NONNULL_BEGIN
SDLProtocolMessage *message = [SDLProtocolMessage messageWithHeader:header andPayload:nil];
[self sdl_sendDataToTransport:message.data onService:header.serviceType];
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(handleHeartbeatForSession:)]) {
[listener handleHeartbeatForSession:session];
}
@@ -523,13 +583,23 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)handleHeartbeatACK {
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(handleHeartbeatACK)]) {
[listener handleHeartbeatACK];
}
}
}
+- (void)handleTransportEventUpdateMessage:(SDLProtocolMessage *)transportEventUpdate {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
+ if ([listener respondsToSelector:@selector(handleTransportEventUpdateMessage:)]) {
+ [listener handleTransportEventUpdateMessage:transportEventUpdate];
+ }
+ }
+}
+
- (void)onProtocolMessageReceived:(SDLProtocolMessage *)msg {
// Control service (but not control frame type) messages are TLS handshake messages
if (msg.header.serviceType == SDLServiceTypeControl) {
@@ -537,7 +607,8 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(onProtocolMessageReceived:)]) {
[listener onProtocolMessageReceived:msg];
}
@@ -545,7 +616,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)onProtocolOpened {
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(onProtocolOpened)]) {
[listener onProtocolOpened];
}
@@ -553,7 +625,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)onProtocolClosed {
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(onProtocolClosed)]) {
[listener onProtocolClosed];
}
@@ -561,7 +634,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)onError:(NSString *)info exception:(NSException *)e {
- for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ NSArray<id<SDLProtocolListener>> *listeners = [self sdl_getProtocolListeners];
+ for (id<SDLProtocolListener> listener in listeners) {
if ([listener respondsToSelector:@selector(onError:exception:)]) {
[listener onError:info exception:e];
}
@@ -569,12 +643,29 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)sdl_logControlNAKPayload:(SDLProtocolMessage *)nakMessage {
- if (nakMessage.header.version >= 5) {
- SDLControlFramePayloadNak *endServiceNakPayload = [[SDLControlFramePayloadNak alloc] initWithData:nakMessage.payload];
- NSArray<NSString *> *rejectedParams = endServiceNakPayload.rejectedParams;
- if (rejectedParams.count > 0) {
- SDLLogE(@"Start Service NAK'd, service type: %@, rejectedParams: %@", @(nakMessage.header.serviceType), rejectedParams);
- }
+ switch (nakMessage.header.frameData) {
+ case SDLFrameInfoStartServiceNACK: // fallthrough
+ case SDLFrameInfoEndServiceNACK: {
+ if (nakMessage.header.version >= 5) {
+ SDLControlFramePayloadNak *endServiceNakPayload = [[SDLControlFramePayloadNak alloc] initWithData:nakMessage.payload];
+ NSArray<NSString *> *rejectedParams = endServiceNakPayload.rejectedParams;
+ if (rejectedParams.count > 0) {
+ SDLLogE(@"Start Service NAK'd, service type: %@, rejectedParams: %@", @(nakMessage.header.serviceType), rejectedParams);
+ }
+ }
+ } break;
+ case SDLFrameInfoRegisterSecondaryTransportNACK: {
+ SDLControlFramePayloadRegisterSecondaryTransportNak *payload = [[SDLControlFramePayloadRegisterSecondaryTransportNak alloc] initWithData:nakMessage.payload];
+ SDLLogE(@"Register Secondary Transport NAK'd, reason: %@", payload.reason);
+ } break;
+ default:
+ break;
+ }
+}
+
+- (NSArray<id<SDLProtocolListener>> *)sdl_getProtocolListeners {
+ @synchronized(self.protocolDelegateTable) {
+ return self.protocolDelegateTable.allObjects;
}
}
diff --git a/SmartDeviceLink/SDLProtocolConstants.h b/SmartDeviceLink/SDLProtocolConstants.h
index 8ce267964..592605b24 100644
--- a/SmartDeviceLink/SDLProtocolConstants.h
+++ b/SmartDeviceLink/SDLProtocolConstants.h
@@ -49,6 +49,10 @@ typedef NS_ENUM(UInt8, SDLServiceType) {
- SDLFrameInfoEndService: Requests that a specific type of service is ended.
- SDLFrameInfoEndServiceACK: Acknowledges that the specific service has been ended successfully.
- SDLFrameInfoEndServiceNACK: Negatively acknowledges that the specific service was not ended or has not yet been started.
+ - SDLFrameInfoRegisterSecondaryTransport: Notifies that a Secondary Transport has been established.
+ - SDLFrameInfoRegisterSecondaryTransportACK: Acknowledges that the Secondary Transport has been recognized.
+ - SDLFrameInfoRegisterSecondaryTransportNACK: Negatively acknowledges that the Secondary Transport has not been recognized.
+ - SDLFrameInfoTransportEventUpdate: Indicates the status or configuration of transport(s) is/are updated.
- SDLFrameInfoServiceDataAck: Deprecated.
- SDLFrameInfoHeartbeatACK: Acknowledges that a Heartbeat control packet has been received.
- SDLFrameInfoSingleFrame: Payload contains a single packet.
@@ -63,6 +67,10 @@ typedef NS_ENUM(UInt8, SDLFrameInfo) {
SDLFrameInfoEndService = 0x04,
SDLFrameInfoEndServiceACK = 0x05,
SDLFrameInfoEndServiceNACK = 0x06,
+ SDLFrameInfoRegisterSecondaryTransport = 0x07,
+ SDLFrameInfoRegisterSecondaryTransportACK = 0x08,
+ SDLFrameInfoRegisterSecondaryTransportNACK = 0x09,
+ SDLFrameInfoTransportEventUpdate = 0xFD,
SDLFrameInfoServiceDataAck = 0xFE,
SDLFrameInfoHeartbeatACK = 0xFF,
// If frameType == Single (0x01)
diff --git a/SmartDeviceLink/SDLProtocolListener.h b/SmartDeviceLink/SDLProtocolListener.h
index 90685a46c..e944f25f6 100644
--- a/SmartDeviceLink/SDLProtocolListener.h
+++ b/SmartDeviceLink/SDLProtocolListener.h
@@ -40,6 +40,9 @@ NS_ASSUME_NONNULL_BEGIN
* @param endServiceNAK A SDLProtocolMessage object
*/
- (void)handleProtocolEndServiceNAKMessage:(SDLProtocolMessage *)endServiceNAK;
+- (void)handleProtocolRegisterSecondaryTransportACKMessage:(SDLProtocolMessage *)registerSecondaryTransportACK;
+- (void)handleProtocolRegisterSecondaryTransportNAKMessage:(SDLProtocolMessage *)registerSecondaryTransportNAK;
+- (void)handleTransportEventUpdateMessage:(SDLProtocolMessage *)transportEventUpdate;
#pragma mark - Older protocol handlers
diff --git a/SmartDeviceLink/SDLProtocolReceivedMessageRouter.m b/SmartDeviceLink/SDLProtocolReceivedMessageRouter.m
index cb12229cf..a4bbf71e2 100644
--- a/SmartDeviceLink/SDLProtocolReceivedMessageRouter.m
+++ b/SmartDeviceLink/SDLProtocolReceivedMessageRouter.m
@@ -72,6 +72,16 @@ NS_ASSUME_NONNULL_BEGIN
[self.delegate handleProtocolEndServiceNAKMessage:message];
}
} break;
+ case SDLFrameInfoRegisterSecondaryTransportACK: {
+ if ([self.delegate respondsToSelector:@selector(handleProtocolRegisterSecondaryTransportACKMessage:)]) {
+ [self.delegate handleProtocolRegisterSecondaryTransportACKMessage:message];
+ }
+ } break;
+ case SDLFrameInfoRegisterSecondaryTransportNACK: {
+ if ([self.delegate respondsToSelector:@selector(handleProtocolRegisterSecondaryTransportNAKMessage:)]) {
+ [self.delegate handleProtocolRegisterSecondaryTransportNAKMessage:message];
+ }
+ } break;
case SDLFrameInfoHeartbeat: {
if ([self.delegate respondsToSelector:@selector(handleHeartbeatForSession:)]) {
[self.delegate handleHeartbeatForSession:message.header.sessionID];
@@ -82,6 +92,11 @@ NS_ASSUME_NONNULL_BEGIN
[self.delegate handleHeartbeatACK];
}
} break;
+ case SDLFrameInfoTransportEventUpdate: {
+ if ([self.delegate respondsToSelector:@selector(handleTransportEventUpdateMessage:)]) {
+ [self.delegate handleTransportEventUpdateMessage:message];
+ }
+ } break;
default: break; // Other frame data is possible, but we don't care about them
}
}
diff --git a/SmartDeviceLink/SDLProxy.h b/SmartDeviceLink/SDLProxy.h
index 2863eef23..9049cf93f 100644
--- a/SmartDeviceLink/SDLProxy.h
+++ b/SmartDeviceLink/SDLProxy.h
@@ -4,6 +4,7 @@
@class SDLProtocol;
@class SDLPutFile;
@class SDLRPCMessage;
+@class SDLSecondaryTransportManager;
@class SDLStreamingMediaManager;
@class SDLTimer;
@@ -48,29 +49,32 @@ NS_ASSUME_NONNULL_BEGIN
/**
* Convenience init.
*
- * @param transport The type of network connection
- * @param delegate The subscriber
- * @return A SDLProxy object
+ * @param transport The type of network connection
+ * @param delegate The subscriber
+ * @param secondaryTransportManager The secondary transport manager
+ * @return A SDLProxy object
*/
-- (id)initWithTransport:(id<SDLTransportType>)transport delegate:(id<SDLProxyListener>)delegate;
+- (id)initWithTransport:(id<SDLTransportType>)transport delegate:(id<SDLProxyListener>)delegate secondaryTransportManager:(nullable SDLSecondaryTransportManager *)secondaryTransportManager;
/**
* Creates a SDLProxy object with an iap (USB / Bluetooth) transport network connection.
*
- * @param delegate The subscriber
- * @return A SDLProxy object
+ * @param delegate The subscriber
+ * @param secondaryTransportManager The secondary transport manager
+ * @return A SDLProxy object
*/
-+ (SDLProxy *)iapProxyWithListener:(id<SDLProxyListener>)delegate;
++ (SDLProxy *)iapProxyWithListener:(id<SDLProxyListener>)delegate secondaryTransportManager:(nullable SDLSecondaryTransportManager *)secondaryTransportManager;
/**
* Creates a SDLProxy object with a TCP (WiFi) transport network connection.
*
- * @param delegate The subscriber
- * @param ipaddress The IP address of Core
- * @param port The port address of Core
- * @return A SDLProxy object
+ * @param delegate The subscriber
+ * @param ipaddress The IP address of Core
+ * @param port The port address of Core
+ * @param secondaryTransportManager The secondary transport manager
+ * @return A SDLProxy object
*/
-+ (SDLProxy *)tcpProxyWithListener:(id<SDLProxyListener>)delegate tcpIPAddress:(NSString *)ipaddress tcpPort:(NSString *)port;
++ (SDLProxy *)tcpProxyWithListener:(id<SDLProxyListener>)delegate tcpIPAddress:(NSString *)ipaddress tcpPort:(NSString *)port secondaryTransportManager:(nullable SDLSecondaryTransportManager *)secondaryTransportManager;
/**
* Adds a delegate.
diff --git a/SmartDeviceLink/SDLProxy.m b/SmartDeviceLink/SDLProxy.m
index da9e891bc..5a837b298 100644
--- a/SmartDeviceLink/SDLProxy.m
+++ b/SmartDeviceLink/SDLProxy.m
@@ -29,6 +29,7 @@
#import "SDLRPCResponse.h"
#import "SDLRegisterAppInterfaceResponse.h"
#import "SDLRequestType.h"
+#import "SDLSecondaryTransportManager.h"
#import "SDLStreamingMediaManager.h"
#import "SDLSubscribeButton.h"
#import "SDLSystemContext.h"
@@ -69,7 +70,7 @@ static float DefaultConnectionTimeout = 45.0;
@implementation SDLProxy
#pragma mark - Object lifecycle
-- (instancetype)initWithTransport:(id<SDLTransportType>)transport delegate:(id<SDLProxyListener>)delegate {
+- (instancetype)initWithTransport:(id<SDLTransportType>)transport delegate:(id<SDLProxyListener>)delegate secondaryTransportManager:(nullable SDLSecondaryTransportManager *)secondaryTransportManager {
if (self = [super init]) {
SDLLogD(@"Framework Version: %@", self.proxyVersion);
_lsm = [[SDLLockScreenStatusManager alloc] init];
@@ -84,6 +85,11 @@ static float DefaultConnectionTimeout = 45.0;
[_protocol.protocolDelegateTable addObject:self];
_protocol.transport = transport;
+ // make sure that secondary transport manager is started prior to starting protocol
+ if (secondaryTransportManager != nil) {
+ [secondaryTransportManager startWithPrimaryProtocol:_protocol];
+ }
+
[self.transport connect];
SDLLogV(@"Proxy transport initialization");
@@ -101,17 +107,17 @@ static float DefaultConnectionTimeout = 45.0;
return self;
}
-+ (SDLProxy *)iapProxyWithListener:(id<SDLProxyListener>)delegate {
++ (SDLProxy *)iapProxyWithListener:(id<SDLProxyListener>)delegate secondaryTransportManager:(nullable SDLSecondaryTransportManager *)secondaryTransportManager {
SDLIAPTransport *transport = [[SDLIAPTransport alloc] init];
- SDLProxy *ret = [[SDLProxy alloc] initWithTransport:transport delegate:delegate];
+ SDLProxy *ret = [[SDLProxy alloc] initWithTransport:transport delegate:delegate secondaryTransportManager:secondaryTransportManager];
return ret;
}
-+ (SDLProxy *)tcpProxyWithListener:(id<SDLProxyListener>)delegate tcpIPAddress:(NSString *)ipaddress tcpPort:(NSString *)port {
++ (SDLProxy *)tcpProxyWithListener:(id<SDLProxyListener>)delegate tcpIPAddress:(NSString *)ipaddress tcpPort:(NSString *)port secondaryTransportManager:(nullable SDLSecondaryTransportManager *)secondaryTransportManager {
SDLTCPTransport *transport = [[SDLTCPTransport alloc] initWithHostName:ipaddress portNumber:port];
- SDLProxy *ret = [[SDLProxy alloc] initWithTransport:transport delegate:delegate];
+ SDLProxy *ret = [[SDLProxy alloc] initWithTransport:transport delegate:delegate secondaryTransportManager:secondaryTransportManager];
return ret;
}
diff --git a/SmartDeviceLink/SDLSecondaryTransportManager.h b/SmartDeviceLink/SDLSecondaryTransportManager.h
new file mode 100644
index 000000000..0af612d7e
--- /dev/null
+++ b/SmartDeviceLink/SDLSecondaryTransportManager.h
@@ -0,0 +1,80 @@
+//
+// SDLSecondaryTransportManager.h
+// SmartDeviceLink-iOS
+//
+// Created by Sho Amano on 2018/02/28.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import "SDLProtocolListener.h"
+#import "SDLStreamingProtocolDelegate.h"
+
+@class SDLControlFramePayloadRPCStartServiceAck;
+@class SDLControlFramePayloadTransportEventUpdate;
+@class SDLProtocol;
+@class SDLStateMachine;
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef NSString SDLSecondaryTransportState;
+extern SDLSecondaryTransportState *const SDLSecondaryTransportStateStopped;
+extern SDLSecondaryTransportState *const SDLSecondaryTransportStateStarted;
+extern SDLSecondaryTransportState *const SDLSecondaryTransportStateConfigured;
+extern SDLSecondaryTransportState *const SDLSecondaryTransportStateConnecting;
+extern SDLSecondaryTransportState *const SDLSecondaryTransportStateRegistered;
+extern SDLSecondaryTransportState *const SDLSecondaryTransportStateReconnecting;
+
+/**
+ A class to control and manage secondary transport feature.
+
+ Secondary transport manager does most of the things required for the feature. It listens
+ on the primary transport and receives necessary information through Version Negotiation
+ and TransportEventUpdate control frame. It initiates secondary transport's connection
+ and sets up SDLProtocol that runs on the transport. Then it starts streaming media
+ manager with appropriate SDLProtocol instance. When the secondary transport is
+ disconnected, this manager retries connection with a regular interval.
+ */
+@interface SDLSecondaryTransportManager : NSObject <SDLProtocolListener>
+
+/** state of this manager */
+@property (strong, nonatomic, readonly) SDLStateMachine *stateMachine;
+
+/**
+ Create a new secondary transport manager.
+
+ @param streamingProtocolDelegate a delegate to handle updates on protocol instances
+ @param queue a serial dispatch queue that the internal state machine runs on
+ @return A new secondary transport manager
+ */
+- (instancetype)initWithStreamingProtocolDelegate:(id<SDLStreamingProtocolDelegate>)streamingProtocolDelegate
+ serialQueue:(dispatch_queue_t)queue;
+
+/**
+ * Start the manager.
+
+ @param primaryProtocol protocol that runs on the main (primary) transport
+ */
+- (void)startWithPrimaryProtocol:(SDLProtocol *)primaryProtocol;
+
+/**
+ * Stop the manager.
+ */
+- (void)stop;
+
+/**
+ * Call this method when Start Service ACK control frame is received on primary transport.
+
+ @param payload payload of Start Service ACK frame received on the primary transport
+ */
+- (void)onStartServiceAckReceived:(SDLControlFramePayloadRPCStartServiceAck *)payload;
+
+/**
+ * Call this method when Transport Event Update control frame is received on primary transport.
+
+ @param payload payload of Transport Event Update frame received on the primary transport
+ */
+- (void)onTransportEventUpdateReceived:(SDLControlFramePayloadTransportEventUpdate *)payload;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLSecondaryTransportManager.m b/SmartDeviceLink/SDLSecondaryTransportManager.m
new file mode 100644
index 000000000..6f3b95273
--- /dev/null
+++ b/SmartDeviceLink/SDLSecondaryTransportManager.m
@@ -0,0 +1,710 @@
+//
+// SDLSecondaryTransportManager.m
+// SmartDeviceLink
+//
+// Created by Sho Amano on 2018/02/28.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+#import "SDLSecondaryTransportManager.h"
+
+#import "SDLControlFramePayloadConstants.h"
+#import "SDLControlFramePayloadRPCStartServiceAck.h"
+#import "SDLControlFramePayloadTransportEventUpdate.h"
+#import "SDLIAPTransport.h"
+#import "SDLLogMacros.h"
+#import "SDLProtocol.h"
+#import "SDLProtocolHeader.h"
+#import "SDLSecondaryTransportPrimaryProtocolHandler.h"
+#import "SDLStateMachine.h"
+#import "SDLTCPTransport.h"
+#import "SDLTimer.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+// same as SDLProtocol.m
+typedef NSNumber SDLServiceTypeBox;
+
+typedef NS_ENUM(NSInteger, SDLTransportClass) {
+ SDLTransportClassInvalid = 0,
+ SDLTransportClassPrimary = 1,
+ SDLTransportClassSecondary = 2,
+};
+typedef NSNumber SDLTransportClassBox;
+
+typedef NS_ENUM(NSInteger, SDLSecondaryTransportType) {
+ SDLSecondaryTransportTypeDisabled, // only for Secondary Transport
+ SDLSecondaryTransportTypeIAP,
+ SDLSecondaryTransportTypeTCP
+};
+typedef NSNumber SDLSecondaryTransportTypeBox;
+
+SDLSecondaryTransportState *const SDLSecondaryTransportStateStopped = @"Stopped";
+SDLSecondaryTransportState *const SDLSecondaryTransportStateStarted = @"Started";
+SDLSecondaryTransportState *const SDLSecondaryTransportStateConfigured = @"Configured";
+SDLSecondaryTransportState *const SDLSecondaryTransportStateConnecting = @"Connecting";
+SDLSecondaryTransportState *const SDLSecondaryTransportStateRegistered = @"Registered";
+SDLSecondaryTransportState *const SDLSecondaryTransportStateReconnecting = @"Reconnecting";
+
+// Timeout for receiving Register Secondary Transport ACK frame
+static const float RegisterTransportTime = 10.0;
+
+// Delay to retry connection after secondary transport is disconnected or registration is failed
+static const float RetryConnectionDelay = 5.0;
+
+// Indicates that a TCP port is not specified (unconfigured).
+static const int TCPPortUnspecified = -1;
+
+
+@interface SDLSecondaryTransportManager ()
+
+// State of this manager.
+@property (strong, nonatomic, readwrite) SDLStateMachine *stateMachine;
+// Dedicated queue that the state machine will run on.
+@property (copy, nonatomic) dispatch_queue_t stateMachineQueue;
+
+// Instance of the protocol that runs on primary transport.
+@property (weak, nonatomic) SDLProtocol *primaryProtocol;
+// A class to catch Start Service ACK and Transport Config Update frames.
+@property (strong, nonatomic) SDLSecondaryTransportPrimaryProtocolHandler *primaryProtocolHandler;
+
+// 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;
+// Instance of the protocol that runs on secondary transport.
+@property (nullable, strong, nonatomic) SDLProtocol *secondaryProtocol;
+// Timer to check Register Secondary Transport ACK response on secondary transport.
+@property (strong, nonatomic, nullable) SDLTimer *registerTransportTimer;
+
+// A delegate to notify protocol change.
+@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;
+// Configuration sent by system; list of transports that are allowed to carry video service
+@property (strong, nonatomic, nonnull) NSArray<SDLTransportClassBox *> *transportsForVideoService;
+// A map to remember which service is currently running on which transport
+@property (strong, nonatomic) NSMutableDictionary<SDLServiceTypeBox *, SDLTransportClassBox *> *streamingServiceTransportMap;
+
+// IP address of SDL Core (either IPv4 or IPv6 address)
+@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;
+
+@end
+
+@implementation SDLSecondaryTransportManager
+
+#pragma mark - Public
+
+- (instancetype)initWithStreamingProtocolDelegate:(id<SDLStreamingProtocolDelegate>)streamingProtocolDelegate
+ serialQueue:(dispatch_queue_t)queue {
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+
+ _stateMachine = [[SDLStateMachine alloc] initWithTarget:self initialState:SDLSecondaryTransportStateStopped states:[self.class sdl_stateTransitionDictionary]];
+ _stateMachineQueue = queue;
+ _streamingProtocolDelegate = streamingProtocolDelegate;
+
+ _secondaryTransportType = SDLSecondaryTransportTypeDisabled;
+ _transportsForAudioService = @[];
+ _transportsForVideoService = @[];
+ _streamingServiceTransportMap = [@{@(SDLServiceTypeAudio):@(SDLTransportClassInvalid),
+ @(SDLServiceTypeVideo):@(SDLTransportClassInvalid)} mutableCopy];
+ _tcpPort = TCPPortUnspecified;
+
+ return self;
+}
+
+- (void)startWithPrimaryProtocol:(SDLProtocol *)primaryProtocol {
+ SDLLogD(@"SDLSecondaryTransportManager start");
+
+ // this method must be called in SDLLifecycleManager's state machine queue
+ if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(self.stateMachineQueue)) != 0) {
+ SDLLogE(@"startWithPrimaryProtocol: must be called in SDLLifecycleManager's state machine queue!");
+ }
+
+ if (![self.stateMachine isCurrentState:SDLSecondaryTransportStateStopped]) {
+ SDLLogW(@"Secondary transport manager is already started!");
+ return;
+ }
+
+ self.primaryProtocol = primaryProtocol;
+ self.primaryProtocolHandler = [[SDLSecondaryTransportPrimaryProtocolHandler alloc] initWithSecondaryTransportManager:self primaryProtocol:primaryProtocol];
+
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateStarted];
+}
+
+- (void)stop {
+ SDLLogD(@"SDLSecondaryTransportManager stop");
+
+ // this method must be called in SDLLifecycleManager's state machine queue
+ if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(self.stateMachineQueue)) != 0) {
+ SDLLogE(@"stop must be called in SDLLifecycleManager's state machine queue!");
+ }
+
+ // stop all services, including those running on primary transport
+ SDLLogD(@"Stopping audio / video services on both transports");
+ [self sdl_handleTransportUpdateWithPrimaryAvailable:NO secondaryAvailable:NO];
+
+ [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];
+
+ 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];
+ }
+ });
+}
+
+#pragma mark - State machine
+
++ (NSDictionary<SDLState *, SDLAllowableStateTransitions *> *)sdl_stateTransitionDictionary {
+ return @{
+ SDLSecondaryTransportStateStopped: @[SDLSecondaryTransportStateStarted],
+ SDLSecondaryTransportStateStarted: @[SDLSecondaryTransportStateConfigured, SDLSecondaryTransportStateStopped],
+ SDLSecondaryTransportStateConfigured: @[SDLSecondaryTransportStateConnecting, SDLSecondaryTransportStateStopped],
+ SDLSecondaryTransportStateConnecting: @[SDLSecondaryTransportStateRegistered, SDLSecondaryTransportStateConfigured, SDLSecondaryTransportStateReconnecting, SDLSecondaryTransportStateStopped],
+ SDLSecondaryTransportStateRegistered: @[SDLSecondaryTransportStateReconnecting, SDLSecondaryTransportStateConfigured, SDLSecondaryTransportStateStopped],
+ SDLSecondaryTransportStateReconnecting: @[SDLSecondaryTransportStateConfigured, SDLSecondaryTransportStateStopped]
+ };
+}
+
+- (void)didEnterStateStopped {
+ [self sdl_stopManager];
+}
+
+- (void)didEnterStateStarted {
+ [self sdl_startManager];
+}
+
+- (void)didEnterStateConfigured {
+ if ((self.secondaryTransportType == SDLSecondaryTransportTypeTCP && [self sdl_isTCPReady])
+ || self.secondaryTransportType == SDLSecondaryTransportTypeIAP) {
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
+ }
+}
+
+- (void)didEnterStateConnecting {
+ [self sdl_connectSecondaryTransport];
+}
+
+- (void)willLeaveStateConnecting {
+ // make sure to terminate the timer, which is only used in Connecting state
+ [self.registerTransportTimer cancel];
+ self.registerTransportTimer = nil;
+}
+
+- (void)willTransitionFromStateConnectingToStateConfigured {
+ [self sdl_disconnectSecondaryTransport];
+}
+
+- (void)willTransitionFromStateConnectingToStateReconnecting {
+ [self sdl_disconnectSecondaryTransport];
+}
+
+- (void)willTransitionFromStateConnectingToStateStopped {
+ [self sdl_disconnectSecondaryTransport];
+}
+
+- (void)didEnterStateRegistered {
+ [self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:YES];
+}
+
+- (void)willTransitionFromStateRegisteredToStateConfigured {
+ // before disconnecting Secondary Transport, stop running services
+ SDLLogD(@"Stopping services on secondary transport");
+ [self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
+
+ [self sdl_disconnectSecondaryTransport];
+}
+
+- (void)willTransitionFromStateRegisteredToStateReconnecting {
+ SDLLogD(@"Stopping services on secondary transport");
+ [self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
+
+ [self sdl_disconnectSecondaryTransport];
+}
+
+- (void)willTransitionFromStateRegisteredToStateStopped {
+ // sdl_handleTransportUpdateWithPrimaryAvailable is called in stop method
+ [self sdl_disconnectSecondaryTransport];
+}
+
+- (void)didEnterStateReconnecting {
+ __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");
+ [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.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 {
+ // update audio service
+ [self sdl_updateService:SDLServiceTypeAudio
+ allowedTransports:self.transportsForAudioService
+ primaryAvailable:primaryAvailable
+ secondaryAvailable:secondaryAvailable
+ transportUpdatedBlock:^(SDLProtocol * _Nullable oldProtocol, SDLProtocol * _Nullable newProtocol) {
+ [self.streamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:oldProtocol toNewProtocol:newProtocol];
+ }];
+
+ // update video service
+ [self sdl_updateService:SDLServiceTypeVideo
+ allowedTransports:self.transportsForVideoService
+ primaryAvailable:primaryAvailable
+ secondaryAvailable:secondaryAvailable
+ transportUpdatedBlock:^(SDLProtocol * _Nullable oldProtocol, SDLProtocol * _Nullable newProtocol) {
+ [self.streamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:oldProtocol toNewProtocol:newProtocol];
+ }];
+}
+
+- (void)sdl_updateService:(UInt8)service
+ allowedTransports:(nonnull NSArray<SDLTransportClassBox *> *)transportList
+ primaryAvailable:(BOOL)primaryTransportAvailable
+ secondaryAvailable:(BOOL)secondaryTransportAvailable
+ transportUpdatedBlock:(void (^)(SDLProtocol * _Nullable oldProtocol, SDLProtocol * _Nullable newProtocol))transportUpdatedBlock {
+ SDLTransportClass newTransport = SDLTransportClassInvalid;
+ // the list is in preferred order, so take a look from the beginning
+ for (SDLTransportClassBox *transport in transportList) {
+ if ([transport intValue] == SDLTransportClassSecondary && secondaryTransportAvailable) {
+ newTransport = SDLTransportClassSecondary;
+ break;
+ } else if ([transport intValue] == SDLTransportClassPrimary && primaryTransportAvailable) {
+ newTransport = SDLTransportClassPrimary;
+ break;
+ }
+ }
+
+ SDLTransportClass oldTransport = [self.streamingServiceTransportMap[@(service)] intValue];
+
+ // update transport, and notify the change to start/stop/restart service
+ self.streamingServiceTransportMap[@(service)] = @(newTransport);
+
+ if (oldTransport != newTransport) {
+ if (oldTransport != SDLTransportClassInvalid && newTransport != SDLTransportClassInvalid) {
+ SDLLogD(@"Restarting service 0x%X from %@ to %@ transport", service, [self sdl_getTransportClassName:oldTransport], [self sdl_getTransportClassName:newTransport]);
+ } else if (oldTransport != SDLTransportClassInvalid) {
+ SDLLogD(@"Stopping service 0x%X on %@ transport", service, [self sdl_getTransportClassName:oldTransport]);
+ } else if (newTransport != SDLTransportClassInvalid) {
+ SDLLogD(@"Starting service 0x%X on %@ transport", service, [self sdl_getTransportClassName:newTransport]);
+ }
+
+ transportUpdatedBlock([self sdl_getProtocolFromTransportClass:oldTransport],
+ [self sdl_getProtocolFromTransportClass:newTransport]);
+ }
+}
+
+- (nullable SDLProtocol *)sdl_getProtocolFromTransportClass:(SDLTransportClass)transportClass {
+ switch (transportClass) {
+ case SDLTransportClassPrimary: return self.primaryProtocol;
+ case SDLTransportClassSecondary: return self.secondaryProtocol;
+ default: return nil;
+ }
+}
+
+- (nullable NSString *)sdl_getTransportClassName:(SDLTransportClass)transportClass {
+ switch (transportClass) {
+ case SDLTransportClassPrimary: return @"primary";
+ case SDLTransportClassSecondary: return @"secondary";
+ default: return nil;
+ }
+}
+
+
+#pragma mark - Transport management
+
+// try establishing secondary transport. Returns NO if failed
+- (BOOL)sdl_connectSecondaryTransport {
+ if (self.secondaryTransport != nil) {
+ SDLLogW(@"Attempting to connect secondary transport, but it's already started.");
+ return NO;
+ }
+
+ switch (self.secondaryTransportType) {
+ case SDLSecondaryTransportTypeTCP:
+ return [self sdl_startTCPSecondaryTransport];
+ case SDLSecondaryTransportTypeIAP:
+ return [self sdl_startIAPSecondaryTransport];
+ default:
+ SDLLogW(@"Unknown transport type for secondary transport: %ld", (long)self.secondaryTransportType);
+ return NO;
+ }
+}
+
+- (BOOL)sdl_disconnectSecondaryTransport {
+ if (self.secondaryTransport == nil) {
+ SDLLogW(@"Attempted to disconnect secondary transport, but it's already stopped.");
+ return NO;
+ }
+
+ SDLLogD(@"Disconnect secondary transport");
+ [self.secondaryTransport disconnect];
+ self.secondaryTransport = nil;
+
+ return YES;
+}
+
+- (BOOL)sdl_startTCPSecondaryTransport {
+ if (![self sdl_isTCPReady]) {
+ SDLLogD(@"TCP secondary transport is not ready.");
+ return NO;
+ }
+
+ SDLLogD(@"Starting TCP Secondary Transport (IP address = \"%@\", port = \"%d\")", self.ipAddress, self.tcpPort);
+
+ SDLTCPTransport *transport = [[SDLTCPTransport alloc] init];
+ transport.hostName = self.ipAddress;
+ transport.portNumber = [NSString stringWithFormat:@"%d", self.tcpPort];
+ SDLProtocol *protocol = [[SDLProtocol alloc] init];
+ transport.delegate = protocol;
+ protocol.transport = transport;
+ [protocol.protocolDelegateTable addObject:self];
+
+ self.secondaryProtocol = protocol;
+ self.secondaryTransport = transport;
+
+ // we reuse Session ID acquired from primary transport's protocol
+ // this is for Register Secondary Transport frame
+ [self.secondaryProtocol storeHeader:self.primaryProtocolHandler.primaryRPCHeader forServiceType:SDLServiceTypeControl];
+ // this is for video and audio services
+ [self.secondaryProtocol storeHeader:self.primaryProtocolHandler.primaryRPCHeader forServiceType:SDLServiceTypeRPC];
+
+ [self.secondaryTransport connect];
+ return YES;
+}
+
+- (BOOL)sdl_startIAPSecondaryTransport {
+ SDLLogD(@"Starting Secondary Transport: iAP");
+
+ SDLIAPTransport *transport = [[SDLIAPTransport alloc] init];
+ SDLProtocol *protocol = [[SDLProtocol alloc] init];
+ transport.delegate = protocol;
+ protocol.transport = transport;
+ [protocol.protocolDelegateTable addObject:self];
+
+ self.secondaryProtocol = protocol;
+ self.secondaryTransport = transport;
+
+ // we reuse Session ID acquired from primary transport's protocol
+ // this is for Register Secondary Transport frame
+ [self.secondaryProtocol storeHeader:self.primaryProtocolHandler.primaryRPCHeader forServiceType:SDLServiceTypeControl];
+ // this is for video and audio services
+ [self.secondaryProtocol storeHeader:self.primaryProtocolHandler.primaryRPCHeader forServiceType:SDLServiceTypeRPC];
+
+ [self.secondaryTransport connect];
+ return YES;
+}
+
+- (BOOL)sdl_isTCPReady {
+ if ([self.ipAddress length] == 0) {
+ SDLLogD(@"IP address is empty");
+ return NO;
+ }
+ if (self.tcpPort == TCPPortUnspecified) {
+ SDLLogD(@"TCP port number is not available");
+ return NO;
+ }
+ if (!(self.tcpPort > 0 && self.tcpPort <= 65535)) {
+ SDLLogW(@"Invalid TCP port number for secondary transport: %d", self.tcpPort);
+ return NO;
+ }
+
+ if ([self sdl_getAppState] != UIApplicationStateActive) {
+ SDLLogD(@"App state is not Active, abort starting TCP transport");
+ return NO;
+ }
+
+ return YES;
+}
+
+- (UIApplicationState)sdl_getAppState {
+ if ([NSThread isMainThread]) {
+ return [UIApplication sharedApplication].applicationState;
+ } else {
+ __block UIApplicationState appState;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ appState = [UIApplication sharedApplication].applicationState;
+ });
+ return appState;
+ }
+}
+
+
+#pragma mark - SDLProtocolListener Implementation
+
+// called on transport's thread, notifying that the transport is established
+- (void)onProtocolOpened {
+ SDLLogD(@"Secondary transport connected");
+
+ self.registerTransportTimer = [[SDLTimer alloc] initWithDuration:RegisterTransportTime repeat:NO];
+ __weak typeof(self) weakSelf = self;
+ self.registerTransportTimer.elapsedBlock = ^{
+ SDLLogW(@"Timeout for registration of secondary transport");
+ dispatch_async(weakSelf.stateMachineQueue, ^{
+ __strong typeof(self) strongSelf = weakSelf;
+ if (!strongSelf) {
+ return;
+ }
+
+ // if the state is still Connecting, go back to Configured state and retry immediately
+ if ([strongSelf.stateMachine isCurrentState:SDLSecondaryTransportStateConnecting]) {
+ SDLLogD(@"Retry secondary transport connection after registration timeout");
+ [strongSelf.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
+ }
+ });
+ };
+ [self.registerTransportTimer start];
+
+ [self.secondaryProtocol registerSecondaryTransport];
+}
+
+// called on transport's thread, notifying that the transport is disconnected
+// (Note: when transport's disconnect method is called, this method will not be called)
+- (void)onProtocolClosed {
+ SDLLogD(@"secondary transport disconnected");
+
+ dispatch_async(self.stateMachineQueue, ^{
+ if ([self sdl_isTransportOpened]) {
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateReconnecting];
+ }
+ });
+}
+
+// called from SDLProtocol's _receiveQueue of secondary transport
+- (void)handleProtocolRegisterSecondaryTransportACKMessage:(SDLProtocolMessage *)registerSecondaryTransportACK {
+ SDLLogD(@"Received Register Secondary Transport ACK frame");
+
+ dispatch_async(self.stateMachineQueue, ^{
+ // secondary transport is now ready
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateRegistered];
+ });
+}
+
+// called from SDLProtocol's _receiveQueue of secondary transport
+- (void)handleProtocolRegisterSecondaryTransportNAKMessage:(SDLProtocolMessage *)registerSecondaryTransportNAK {
+ SDLLogW(@"Received Register Secondary Transport NAK frame");
+
+ dispatch_async(self.stateMachineQueue, ^{
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateReconnecting];
+ });
+}
+
+#pragma mark - App state handling
+
+- (void)sdl_onAppStateUpdated:(NSNotification *)notification {
+ dispatch_async(_stateMachineQueue, ^{
+ if (notification.name == UIApplicationWillResignActiveNotification) {
+ if ([self sdl_isTransportOpened] && self.secondaryTransportType == SDLSecondaryTransportTypeTCP) {
+ SDLLogD(@"Disconnecting TCP transport since the app will go to background");
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
+ }
+ } else if (notification.name == UIApplicationDidBecomeActiveNotification) {
+ if (([self.stateMachine isCurrentState:SDLSecondaryTransportStateConfigured])
+ && self.secondaryTransportType == SDLSecondaryTransportTypeTCP && [self sdl_isTCPReady]) {
+ SDLLogD(@"Resuming TCP transport since the app becomes foreground");
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
+ }
+ }
+ });
+}
+
+#pragma mark - Utility methods
+
+- (SDLSecondaryTransportType)sdl_convertTransportType:(NSString *)transportString {
+ if ([transportString isEqualToString:@"TCP_WIFI"]) {
+ return SDLSecondaryTransportTypeTCP;
+ } else if ([transportString isEqualToString:@"IAP_BLUETOOTH"] ||
+ [transportString isEqualToString:@"IAP_USB"] ||
+ [transportString isEqualToString:@"IAP_USB_HOST_MODE"] ||
+ [transportString isEqualToString:@"IAP_USB_DEVICE_MODE"] ||
+ [transportString isEqualToString:@"IAP_CARPLAY"]) {
+ return SDLSecondaryTransportTypeIAP;
+ } else {
+ // "SPP_BLUETOOTH" and "AOA_USB" are not used
+ return SDLSecondaryTransportTypeDisabled;
+ }
+}
+
+- (nullable NSArray<SDLTransportClassBox *> *)sdl_convertServiceTransports:(nullable NSArray<NSNumber *> *)transports {
+ if (transports == nil) {
+ return nil;
+ }
+
+ // Actually there is nothing to "convert" here. We just check the range and recreate an array.
+ NSMutableArray<SDLTransportClassBox *> *array = [[NSMutableArray alloc] init];
+ for (NSNumber *num in transports) {
+ int transport = [num intValue];
+ if (transport == 1) {
+ [array addObject:@(SDLTransportClassPrimary)];
+ } else if (transport == 2) {
+ [array addObject:@(SDLTransportClassSecondary)];
+ } else {
+ SDLLogE(@"Unknown transport class received: %d", transport);
+ }
+ }
+
+ return [array copy];
+}
+
+- (SDLSecondaryTransportType)sdl_getTransportTypeFromProtocol:(SDLProtocol *)protocol {
+ if ([protocol.transport isMemberOfClass:[SDLIAPTransport class]]) {
+ return SDLSecondaryTransportTypeIAP;
+ } else if ([protocol.transport isMemberOfClass:[SDLTCPTransport class]]) {
+ return SDLSecondaryTransportTypeTCP;
+ } else {
+ SDLLogE(@"Unknown type of primary transport");
+ return SDLSecondaryTransportTypeDisabled;
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLSecondaryTransportPrimaryProtocolHandler.h b/SmartDeviceLink/SDLSecondaryTransportPrimaryProtocolHandler.h
new file mode 100644
index 000000000..d3e8510ee
--- /dev/null
+++ b/SmartDeviceLink/SDLSecondaryTransportPrimaryProtocolHandler.h
@@ -0,0 +1,46 @@
+//
+// SDLSecondaryTransportPrimaryProtocolHandler.h
+// SmartDeviceLink-iOS
+//
+// Created by Sho Amano on 2018/08/09.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import "SDLProtocolListener.h"
+
+@class SDLProtocol;
+@class SDLSecondaryTransportManager;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ A class to receive event from primary transport.
+ */
+@interface SDLSecondaryTransportPrimaryProtocolHandler : NSObject <SDLProtocolListener>
+
+/** The header of Start Service ACK frame received on primary transport. */
+@property (copy, nonatomic) SDLProtocolHeader *primaryRPCHeader;
+
+/**
+ Create a new primary protocol handler with given SDLSecondaryTransportManager and SDLProtocol instances.
+
+ @param manager instance of SDLSecondaryTransportManager
+ @param primaryProtocol instance of SDLProtocol for the primary transport
+ @return A new primary protocol handler
+ */
+- (instancetype)initWithSecondaryTransportManager:(SDLSecondaryTransportManager *)manager
+ primaryProtocol:(SDLProtocol *)primaryProtocol;
+
+/**
+ * Start the handler.
+ */
+- (void)start;
+
+/**
+ * Stop the handler.
+ */
+- (void)stop;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLSecondaryTransportPrimaryProtocolHandler.m b/SmartDeviceLink/SDLSecondaryTransportPrimaryProtocolHandler.m
new file mode 100644
index 000000000..066a68999
--- /dev/null
+++ b/SmartDeviceLink/SDLSecondaryTransportPrimaryProtocolHandler.m
@@ -0,0 +1,76 @@
+//
+// SDLSecondaryTransportPrimaryProtocolHandler.m
+// SmartDeviceLink
+//
+// Created by Sho Amano on 2018/08/09.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLSecondaryTransportPrimaryProtocolHandler.h"
+
+#import "SDLControlFramePayloadRPCStartServiceAck.h"
+#import "SDLControlFramePayloadTransportEventUpdate.h"
+#import "SDLLogMacros.h"
+#import "SDLProtocol.h"
+#import "SDLProtocolMessage.h"
+#import "SDLSecondaryTransportManager.h"
+
+@interface SDLSecondaryTransportPrimaryProtocolHandler ()
+@property (weak, nonatomic) SDLSecondaryTransportManager *secondaryTransportManager;
+@property (weak, nonatomic) SDLProtocol *primaryProtocol;
+@end
+
+@implementation SDLSecondaryTransportPrimaryProtocolHandler
+
+- (instancetype)initWithSecondaryTransportManager:(SDLSecondaryTransportManager *)manager
+ primaryProtocol:(SDLProtocol *)primaryProtocol {
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+
+ _secondaryTransportManager = manager;
+ _primaryProtocol = primaryProtocol;
+
+ return self;
+}
+
+- (void)start {
+ @synchronized(self.primaryProtocol.protocolDelegateTable) {
+ [self.primaryProtocol.protocolDelegateTable addObject:self];
+ }
+}
+
+- (void)stop {
+ @synchronized(self.primaryProtocol.protocolDelegateTable) {
+ [self.primaryProtocol.protocolDelegateTable removeObject:self];
+ }
+}
+
+// called from protocol's _reeiveQueue of Primary Transport
+- (void)handleProtocolStartServiceACKMessage:(SDLProtocolMessage *)startServiceACK {
+ if (startServiceACK.header.serviceType != SDLServiceTypeRPC) {
+ return;
+ }
+
+ SDLLogV(@"Received Start Service ACK header of RPC service on primary transport");
+
+ // keep header to acquire Session ID
+ self.primaryRPCHeader = startServiceACK.header;
+
+ SDLControlFramePayloadRPCStartServiceAck *payload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithData:startServiceACK.payload];
+
+ [self.secondaryTransportManager onStartServiceAckReceived:payload];
+}
+
+// called from protocol's _reeiveQueue of Primary Transport
+- (void)handleTransportEventUpdateMessage:(SDLProtocolMessage *)transportEventUpdate {
+ SDLControlFramePayloadTransportEventUpdate *payload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithData:transportEventUpdate.payload];
+ SDLLogV(@"Transport Config Update: %@", payload);
+
+ [self.secondaryTransportManager onTransportEventUpdateReceived:payload];
+}
+
+@end
diff --git a/SmartDeviceLink/SDLStreamingAudioLifecycleManager.m b/SmartDeviceLink/SDLStreamingAudioLifecycleManager.m
index a6bc98be7..215962283 100644
--- a/SmartDeviceLink/SDLStreamingAudioLifecycleManager.m
+++ b/SmartDeviceLink/SDLStreamingAudioLifecycleManager.m
@@ -93,9 +93,14 @@ NS_ASSUME_NONNULL_BEGIN
- (void)startWithProtocol:(SDLProtocol *)protocol {
_protocol = protocol;
- if (![self.protocol.protocolDelegateTable containsObject:self]) {
- [self.protocol.protocolDelegateTable addObject:self];
+ @synchronized(self.protocol.protocolDelegateTable) {
+ if (![self.protocol.protocolDelegateTable containsObject:self]) {
+ [self.protocol.protocolDelegateTable addObject:self];
+ }
}
+
+ // attempt to start streaming since we may already have necessary conditions met
+ [self sdl_startAudioSession];
}
- (void)stop {
@@ -293,6 +298,9 @@ NS_ASSUME_NONNULL_BEGIN
SDLLogD(@"HMI level changed from level %@ to level %@", self.hmiLevel, hmiStatus.hmiLevel);
self.hmiLevel = hmiStatus.hmiLevel;
+ // if startWithProtocol has not been called yet, abort here
+ if (!self.protocol) { return; }
+
if (self.isHmiStateAudioStreamCapable) {
[self sdl_startAudioSession];
} else {
diff --git a/SmartDeviceLink/SDLStreamingMediaManager.h b/SmartDeviceLink/SDLStreamingMediaManager.h
index 9d714ea7e..f9df79067 100644
--- a/SmartDeviceLink/SDLStreamingMediaManager.h
+++ b/SmartDeviceLink/SDLStreamingMediaManager.h
@@ -126,11 +126,31 @@ NS_ASSUME_NONNULL_BEGIN
- (void)startWithProtocol:(SDLProtocol *)protocol;
/**
+ * Start the audio feature of the manager. This is used internally. To use an SDLStreamingMediaManager, you should use the manager found on `SDLManager`.
+ */
+- (void)startAudioWithProtocol:(SDLProtocol *)protocol;
+
+/**
+ * Start the video feature of the manager. This is used internally. To use an SDLStreamingMediaManager, you should use the manager found on `SDLManager`.
+ */
+- (void)startVideoWithProtocol:(SDLProtocol *)protocol;
+
+/**
* Stop the manager. This method is used internally.
*/
- (void)stop;
/**
+ * Stop the audio feature of the manager. This method is used internally.
+ */
+- (void)stopAudio;
+
+/**
+ * Stop the video feature of the manager. This method is used internally.
+ */
+- (void)stopVideo;
+
+/**
* This method receives raw image data and will run iOS8+'s hardware video encoder to turn the data into a video stream, which will then be passed to the connected head unit.
*
* @param imageBuffer A CVImageBufferRef to be encoded by Video Toolbox
diff --git a/SmartDeviceLink/SDLStreamingMediaManager.m b/SmartDeviceLink/SDLStreamingMediaManager.m
index 0d57cd6bd..95819666a 100644
--- a/SmartDeviceLink/SDLStreamingMediaManager.m
+++ b/SmartDeviceLink/SDLStreamingMediaManager.m
@@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (strong, nonatomic) SDLStreamingAudioLifecycleManager *audioLifecycleManager;
@property (strong, nonatomic) SDLStreamingVideoLifecycleManager *videoLifecycleManager;
+@property (assign, nonatomic) BOOL audioStarted;
+@property (assign, nonatomic) BOOL videoStarted;
@end
@@ -45,13 +47,33 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)startWithProtocol:(SDLProtocol *)protocol {
+ [self startAudioWithProtocol:protocol];
+ [self startVideoWithProtocol:protocol];
+}
+
+- (void)startAudioWithProtocol:(SDLProtocol *)protocol {
[self.audioLifecycleManager startWithProtocol:protocol];
+ self.audioStarted = YES;
+}
+
+- (void)startVideoWithProtocol:(SDLProtocol *)protocol {
[self.videoLifecycleManager startWithProtocol:protocol];
+ self.videoStarted = YES;
}
- (void)stop {
+ [self stopAudio];
+ [self stopVideo];
+}
+
+- (void)stopAudio {
[self.audioLifecycleManager stop];
+ self.audioStarted = NO;
+}
+
+- (void)stopVideo {
[self.videoLifecycleManager stop];
+ self.videoStarted = NO;
}
- (BOOL)sendVideoData:(CVImageBufferRef)imageBuffer {
@@ -86,7 +108,14 @@ NS_ASSUME_NONNULL_BEGIN
}
- (BOOL)isStreamingSupported {
- return self.videoLifecycleManager.isStreamingSupported;
+ // both audio and video lifecycle managers checks the param in Register App Interface response,
+ // hence the flag should be same between two managers if they are started
+ if (self.videoStarted) {
+ return self.videoLifecycleManager.isStreamingSupported;
+ } else if (self.audioStarted) {
+ return self.audioLifecycleManager.isStreamingSupported;
+ }
+ return NO;
}
- (BOOL)isAudioConnected {
@@ -126,6 +155,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (SDLStreamingEncryptionFlag)requestedEncryptionType {
+ // both audio and video managers should have same type
return self.videoLifecycleManager.requestedEncryptionType;
}
diff --git a/SmartDeviceLink/SDLStreamingProtocolDelegate.h b/SmartDeviceLink/SDLStreamingProtocolDelegate.h
new file mode 100644
index 000000000..b3ec8f78a
--- /dev/null
+++ b/SmartDeviceLink/SDLStreamingProtocolDelegate.h
@@ -0,0 +1,41 @@
+//
+// SDLStreamingProtocolDelegate.h
+// SmartDeviceLink-iOS
+//
+// Created by Sho Amano on 2018/03/23.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SDLProtocol;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol SDLStreamingProtocolDelegate <NSObject>
+
+/**
+ * Called when protocol instance for audio service has been updated.
+ *
+ * If `newProtocol` is nil, it indicates that underlying transport
+ * becomes unavailable.
+ *
+ * @param oldProtocol protocol instance that has been used for audio streaming.
+ * @param newProtocol protocol instance that will be used for audio streaming.
+ */
+- (void)audioServiceProtocolDidUpdateFromOldProtocol:(nullable SDLProtocol *)oldProtocol toNewProtocol:(nullable SDLProtocol *)newProtocol;
+
+/**
+ * Called when protocol instance for video service has been updated.
+ *
+ * If `newProtocol` is nil, it indicates that underlying transport
+ * becomes unavailable.
+ *
+ * @param oldProtocol protocol instance that has been used for video streaming.
+ * @param newProtocol protocol instance that will be used for video streaming.
+ */
+- (void)videoServiceProtocolDidUpdateFromOldProtocol:(nullable SDLProtocol *)oldProtocol toNewProtocol:(nullable SDLProtocol *)newProtocol;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m b/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m
index b95d3fc9d..4f8e47e77 100644
--- a/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m
+++ b/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m
@@ -148,9 +148,14 @@ typedef void(^SDLVideoCapabilityResponseHandler)(SDLVideoStreamingCapability *_N
- (void)startWithProtocol:(SDLProtocol *)protocol {
_protocol = protocol;
- if (![self.protocol.protocolDelegateTable containsObject:self]) {
- [self.protocol.protocolDelegateTable addObject:self];
+ @synchronized(self.protocol.protocolDelegateTable) {
+ if (![self.protocol.protocolDelegateTable containsObject:self]) {
+ [self.protocol.protocolDelegateTable addObject:self];
+ }
}
+
+ // attempt to start streaming since we may already have necessary conditions met
+ [self sdl_startVideoSession];
}
- (void)stop {
@@ -556,6 +561,9 @@ typedef void(^SDLVideoCapabilityResponseHandler)(SDLVideoStreamingCapability *_N
self.videoStreamingState = newState;
}
+ // if startWithProtocol has not been called yet, abort here
+ if (!self.protocol) { return; }
+
if (self.isHmiStateVideoStreamCapable) {
[self sdl_startVideoSession];
} else {
diff --git a/SmartDeviceLink/SDLV2ProtocolHeader.m b/SmartDeviceLink/SDLV2ProtocolHeader.m
index ce426fd4d..d89fdeb1f 100644
--- a/SmartDeviceLink/SDLV2ProtocolHeader.m
+++ b/SmartDeviceLink/SDLV2ProtocolHeader.m
@@ -97,6 +97,8 @@ const int ProtocolV2HeaderByteSize = 12;
if (self.frameData >= 0 && self.frameData <= 5) {
NSArray *controlFrameDataNames = @[@"Heartbeat", @"StartSession", @"StartSessionACK", @"StartSessionNACK", @"EndSession", @"EndSessionACK", @"EndSessionNACK"];
frameDataString = controlFrameDataNames[self.frameData];
+ } else if (self.frameData == SDLFrameInfoTransportEventUpdate) {
+ frameDataString = @"TransportEventUpdate";
} else {
frameDataString = @"Reserved";
}
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
index 5343c7c2c..e03c821ac 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
@@ -57,6 +57,12 @@ QuickConfigurationBegin(SendingRPCsConfiguration)
QuickConfigurationEnd
+@interface SDLLifecycleManager ()
+// reach the private property for testing
+@property (strong, nonatomic, nullable) SDLSecondaryTransportManager *secondaryTransportManager;
+@end
+
+
QuickSpecBegin(SDLLifecycleManagerSpec)
describe(@"a lifecycle manager", ^{
@@ -72,7 +78,7 @@ describe(@"a lifecycle manager", ^{
__block id systemCapabilityMock = OCMClassMock([SDLSystemCapabilityManager class]);
beforeEach(^{
- OCMStub([proxyMock iapProxyWithListener:[OCMArg any]]).andReturn(proxyMock);
+ OCMStub([proxyMock iapProxyWithListener:[OCMArg any] secondaryTransportManager:[OCMArg any]]).andReturn(proxyMock);
OCMStub([(SDLProxy*)proxyMock protocol]).andReturn(protocolMock);
SDLLifecycleConfiguration *testLifecycleConfig = [SDLLifecycleConfiguration defaultConfigurationWithAppName:@"Test App" appId:@"Test Id"];
@@ -187,7 +193,13 @@ describe(@"a lifecycle manager", ^{
expect(testManager.proxy).toNot(beNil());
expect(testManager.lifecycleState).to(match(SDLLifecycleStateStarted));
});
-
+
+ it(@"should initialize secondary transport manager if not in tcpDebugMode", ^{
+ if (!testManager.configuration.lifecycleConfig.tcpDebugMode) {
+ expect(testManager.secondaryTransportManager).toNot(beNil());
+ }
+ });
+
describe(@"after receiving a connect notification", ^{
beforeEach(^{
// When we connect, we should be creating an sending an RAI
@@ -239,8 +251,10 @@ describe(@"a lifecycle manager", ^{
OCMStub([(SDLLockScreenManager *)lockScreenManagerMock start]);
OCMStub([fileManagerMock startWithCompletionHandler:([OCMArg invokeBlockWithArgs:@(YES), fileManagerStartError, nil])]);
OCMStub([permissionManagerMock startWithCompletionHandler:([OCMArg invokeBlockWithArgs:@(YES), permissionManagerStartError, nil])]);
- OCMStub([streamingManagerMock startWithProtocol:protocolMock]);
-
+ if (testConfig.lifecycleConfig.tcpDebugMode) {
+ OCMStub([streamingManagerMock startWithProtocol:protocolMock]);
+ }
+
// Send an RAI response & make sure we have an HMI status to move the lifecycle forward
testManager.hmiLevel = SDLHMILevelFull;
[testManager.lifecycleStateMachine transitionToState:SDLLifecycleStateRegistered];
@@ -252,7 +266,9 @@ describe(@"a lifecycle manager", ^{
OCMVerify([(SDLLockScreenManager *)lockScreenManagerMock start]);
OCMVerify([fileManagerMock startWithCompletionHandler:[OCMArg any]]);
OCMVerify([permissionManagerMock startWithCompletionHandler:[OCMArg any]]);
- OCMVerify([streamingManagerMock startWithProtocol:[OCMArg any]]);
+ if (testManager.configuration.lifecycleConfig.tcpDebugMode) {
+ OCMVerify([streamingManagerMock startWithProtocol:[OCMArg any]]);
+ }
});
itBehavesLike(@"unable to send an RPC", ^{ return @{ @"manager": testManager }; });
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/ControlFramePayloadSpecs/SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/ControlFramePayloadSpecs/SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m
new file mode 100644
index 000000000..e01d268f0
--- /dev/null
+++ b/SmartDeviceLinkTests/ProtocolSpecs/ControlFramePayloadSpecs/SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m
@@ -0,0 +1,77 @@
+//
+// SDLControlFramePayloadRegisterSecondaryTransportNakSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Sho Amano on 2018/03/25.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLControlFramePayloadRegisterSecondaryTransportNak.h"
+
+QuickSpecBegin(SDLControlFramePayloadRegisterSecondaryTransportNakSpec)
+
+describe(@"Test encoding data", ^{
+ __block SDLControlFramePayloadRegisterSecondaryTransportNak *testPayload = nil;
+ __block NSString *testReason = nil;
+
+ context(@"with paramaters", ^{
+ beforeEach(^{
+ testReason = @"a sample reason of NAK";
+ testPayload = [[SDLControlFramePayloadRegisterSecondaryTransportNak alloc] initWithReason:testReason];
+ });
+
+ it(@"should create the correct data", ^{
+ expect(testPayload.data.description).to(equal(@"<28000000 02726561 736f6e00 17000000 61207361 6d706c65 20726561 736f6e20 6f66204e 414b0000>"));
+ });
+ });
+
+ context(@"without parameters", ^{
+ beforeEach(^{
+ testReason = nil;
+ testPayload = [[SDLControlFramePayloadRegisterSecondaryTransportNak alloc] initWithReason:testReason];
+ });
+
+ it(@"should create no data", ^{
+ expect(testPayload.data.length).to(equal(0));
+ });
+ });
+});
+
+describe(@"Test decoding data", ^{
+ __block SDLControlFramePayloadRegisterSecondaryTransportNak *testPayload = nil;
+ __block NSData *testData = nil;
+ __block NSString *testReason = nil;
+
+ beforeEach(^{
+ testReason = @"Here is another reason";
+
+ SDLControlFramePayloadRegisterSecondaryTransportNak *payload = [[SDLControlFramePayloadRegisterSecondaryTransportNak alloc] initWithReason:testReason];
+ testData = payload.data;
+
+ testPayload = [[SDLControlFramePayloadRegisterSecondaryTransportNak alloc] initWithData:testData];
+ });
+
+ it(@"should output the correct params", ^{
+ expect(testPayload.reason).to(equal(testReason));
+ });
+});
+
+describe(@"Test nil data", ^{
+ __block SDLControlFramePayloadRegisterSecondaryTransportNak *testPayload = nil;
+ __block NSData *testData = nil;
+
+ beforeEach(^{
+ testPayload = [[SDLControlFramePayloadRegisterSecondaryTransportNak alloc] initWithData:testData];
+ });
+
+ it(@"should output the correct params", ^{
+ expect(testPayload.reason).to(beNil());
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/ControlFramePayloadSpecs/SDLControlFramePayloadTransportEventUpdateSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/ControlFramePayloadSpecs/SDLControlFramePayloadTransportEventUpdateSpec.m
new file mode 100644
index 000000000..e6a79fc08
--- /dev/null
+++ b/SmartDeviceLinkTests/ProtocolSpecs/ControlFramePayloadSpecs/SDLControlFramePayloadTransportEventUpdateSpec.m
@@ -0,0 +1,85 @@
+//
+// SDLControlFramePayloadTransportEventUpdateSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Sho Amano on 2018/03/25.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLControlFramePayloadTransportEventUpdate.h"
+#import "SDLControlFramePayloadConstants.h"
+
+QuickSpecBegin(SDLControlFramePayloadTransportEventUpdateSpec)
+
+describe(@"Test encoding data", ^{
+ __block SDLControlFramePayloadTransportEventUpdate *testPayload = nil;
+ __block NSString *testTcpIpAddress = nil;
+ __block int32_t testTcpPort = SDLControlFrameInt32NotFound;
+
+ context(@"with paramaters", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"10.20.30.254";
+ testTcpPort = 12345;
+ testPayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ });
+
+ it(@"should create the correct data", ^{
+ expect(testPayload.data.description).to(equal(@"<31000000 10746370 506f7274 00393000 00027463 70497041 64647265 7373000d 00000031 302e3230 2e33302e 32353400 00>"));
+ });
+ });
+
+ context(@"without parameters", ^{
+ beforeEach(^{
+ testTcpIpAddress = nil;
+ testTcpPort = SDLControlFrameInt32NotFound;
+ testPayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ });
+
+ it(@"should create no data", ^{
+ expect(testPayload.data.length).to(equal(0));
+ });
+ });
+});
+
+describe(@"Test decoding data", ^{
+ __block SDLControlFramePayloadTransportEventUpdate *testPayload = nil;
+ __block NSData *testData = nil;
+ __block NSString *testTcpIpAddress = nil;
+ __block int32_t testTcpPort = SDLControlFrameInt32NotFound;
+
+ beforeEach(^{
+ testTcpIpAddress = @"192.168.132.24";
+ testTcpPort = 40902;
+
+ SDLControlFramePayloadTransportEventUpdate *payload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testData = payload.data;
+
+ testPayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithData:testData];
+ });
+
+ it(@"should output the correct params", ^{
+ expect(testPayload.tcpIpAddress).to(equal(testTcpIpAddress));
+ expect(testPayload.tcpPort).to(equal(testTcpPort));
+ });
+});
+
+describe(@"Test nil data", ^{
+ __block SDLControlFramePayloadTransportEventUpdate *testPayload = nil;
+ __block NSData *testData = nil;
+
+ beforeEach(^{
+ testPayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithData:testData];
+ });
+
+ it(@"should output the correct params", ^{
+ expect(testPayload.tcpIpAddress).to(beNil());
+ expect(testPayload.tcpPort).to(equal(SDLControlFrameInt32NotFound));
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m
index 4131fed51..6ff840374 100644
--- a/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m
+++ b/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m
@@ -10,6 +10,8 @@
#import <OCMock/OCMock.h>
#import "SDLTransportType.h"
+#import "SDLControlFramePayloadRegisterSecondaryTransportNak.h"
+#import "SDLGlobals.h"
#import "SDLProtocolHeader.h"
#import "SDLProtocol.h"
#import "SDLProtocolMessage.h"
@@ -35,6 +37,10 @@ NSDictionary* dictionaryV2 = @{SDLNameCommandId:@55};
describe(@"Send StartService Tests", ^ {
context(@"Unsecure", ^{
it(@"Should send the correct data", ^ {
+ // Reset max protocol version before test. (This test case expects V1 header. If other test ran
+ // prior to this one, SDLGlobals would keep the max protocol version and this test case would fail.)
+ [[SDLGlobals sharedGlobals] setMaxHeadUnitVersion:@"1.0.0"];
+
SDLProtocol* testProtocol = [[SDLProtocol alloc] init];
__block BOOL verified = NO;
@@ -56,6 +62,44 @@ describe(@"Send StartService Tests", ^ {
expect(@(verified)).toEventually(beTruthy());
});
+
+ it(@"Should reuse stored header of RPC service when starting other service", ^{
+ // reset max protocol version before test
+ [[SDLGlobals sharedGlobals] setMaxHeadUnitVersion:@"2.0.0"];
+
+ SDLServiceType serviceTypeToStart = SDLServiceTypeVideo;
+
+ // reference header (which is taken from Start Service ACK of Version Negotiation)
+ SDLV2ProtocolHeader *refHeader = [[SDLV2ProtocolHeader alloc] init];
+ refHeader.frameType = SDLFrameTypeControl;
+ refHeader.serviceType = SDLServiceTypeRPC;
+ refHeader.frameData = SDLFrameInfoStartServiceACK;
+ refHeader.sessionID = 100;
+
+ SDLV2ProtocolHeader *header = [refHeader copy];
+ header.serviceType = serviceTypeToStart;
+ header.frameData = SDLFrameInfoStartService;
+ NSData *headerData = [header data];
+
+ SDLProtocol* testProtocol = [[SDLProtocol alloc] init];
+
+ __block BOOL verified = NO;
+ id transportMock = OCMProtocolMock(@protocol(SDLTransportType));
+ [[[transportMock stub] andDo:^(NSInvocation* invocation) {
+ verified = YES;
+
+ __unsafe_unretained NSData* data;
+ [invocation getArgument:&data atIndex:2];
+ NSData* dataSent = [data copy];
+ expect(dataSent).to(equal(headerData));
+ }] sendData:[OCMArg any]];
+ testProtocol.transport = transportMock;
+
+ [testProtocol storeHeader:header forServiceType:SDLServiceTypeRPC];
+ [testProtocol startServiceWithType:serviceTypeToStart payload:nil];
+
+ expect(@(verified)).toEventually(beTruthy());
+ });
});
context(@"Secure", ^{
@@ -127,6 +171,41 @@ describe(@"Send EndSession Tests", ^ {
});
});
+describe(@"Send Register Secondary Transport Tests", ^ {
+ it(@"Should send the correct data", ^ {
+ SDLProtocol* testProtocol = [[SDLProtocol alloc] init];
+
+ // receive a Start Service ACK frame of RPC to configure protocol version
+ SDLV2ProtocolHeader *refHeader = [[SDLV2ProtocolHeader alloc] initWithVersion:5];
+ refHeader.frameType = SDLFrameTypeControl;
+ refHeader.serviceType = SDLServiceTypeRPC;
+ refHeader.frameData = SDLFrameInfoStartServiceACK;
+ refHeader.sessionID = 0x11;
+ [testProtocol handleProtocolStartServiceACKMessage:[SDLProtocolMessage messageWithHeader:refHeader andPayload:nil]];
+
+ // store the header to apply Session ID value to Register Secondary Transport frame
+ [testProtocol storeHeader:refHeader forServiceType:SDLServiceTypeControl];
+
+ __block BOOL verified = NO;
+ id transportMock = OCMProtocolMock(@protocol(SDLTransportType));
+ [[[transportMock stub] andDo:^(NSInvocation* invocation) {
+ verified = YES;
+
+ __unsafe_unretained NSData* data;
+ [invocation getArgument:&data atIndex:2];
+ NSData* dataSent = [data copy];
+
+ const char testHeader[12] = {0x50 | SDLFrameTypeControl, SDLServiceTypeControl, SDLFrameInfoRegisterSecondaryTransport, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+ expect(dataSent).to(equal([NSData dataWithBytes:testHeader length:12]));
+ }] sendData:[OCMArg any]];
+ testProtocol.transport = transportMock;
+
+ [testProtocol registerSecondaryTransport];
+
+ expect(@(verified)).toEventually(beTruthy());
+ });
+});
+
describe(@"SendRPCRequest Tests", ^ {
__block id mockRequest;
beforeEach(^ {
@@ -345,6 +424,48 @@ xdescribe(@"HandleProtocolSessionStarted Tests", ^ {
});
});
+xdescribe(@"HandleProtocolRegisterSecondaryTransport Tests", ^ {
+ it(@"Should pass information along to delegate when ACKed", ^ {
+ SDLProtocol* testProtocol = [[SDLProtocol alloc] init];
+
+ id delegateMock = OCMProtocolMock(@protocol(SDLProtocolListener));
+
+ SDLV2ProtocolHeader* testHeader = [[SDLV2ProtocolHeader alloc] init];
+ testHeader.frameType = SDLFrameTypeControl;
+ testHeader.serviceType = SDLServiceTypeControl;
+ testHeader.frameData = SDLFrameInfoRegisterSecondaryTransportACK;
+ testHeader.sessionID = 0x32;
+ testHeader.messageID = 2;
+ testHeader.bytesInPayload = 0;
+
+ [testProtocol.protocolDelegateTable addObject:delegateMock];
+ [testProtocol handleProtocolRegisterSecondaryTransportACKMessage:[SDLProtocolMessage messageWithHeader:testHeader andPayload:nil]];
+
+ OCMExpect([delegateMock handleProtocolRegisterSecondaryTransportACKMessage:[SDLProtocolMessage messageWithHeader:testHeader andPayload:nil]]);
+ });
+
+ it(@"Should pass information along to delegate when NAKed", ^ {
+ SDLProtocol* testProtocol = [[SDLProtocol alloc] init];
+
+ id delegateMock = OCMProtocolMock(@protocol(SDLProtocolListener));
+
+ SDLV2ProtocolHeader* testHeader = [[SDLV2ProtocolHeader alloc] init];
+ testHeader.frameType = SDLFrameTypeControl;
+ testHeader.serviceType = SDLServiceTypeControl;
+ testHeader.frameData = SDLFrameInfoRegisterSecondaryTransportNACK;
+ testHeader.sessionID = 0x56;
+ testHeader.messageID = 2;
+
+ SDLControlFramePayloadRegisterSecondaryTransportNak *payload = [[SDLControlFramePayloadRegisterSecondaryTransportNak alloc] initWithReason:@"Sample reason"];
+ NSData *payloadData = payload.data;
+
+ [testProtocol.protocolDelegateTable addObject:delegateMock];
+ [testProtocol handleProtocolRegisterSecondaryTransportACKMessage:[SDLProtocolMessage messageWithHeader:testHeader andPayload:payloadData]];
+
+ OCMExpect([delegateMock handleProtocolRegisterSecondaryTransportNAKMessage:[SDLProtocolMessage messageWithHeader:testHeader andPayload:payloadData]]);
+ });
+});
+
xdescribe(@"HandleHeartbeatForSession Tests", ^{
// TODO: Test automatically sending data to head unit (dependency injection?)
it(@"Should pass information along to delegate", ^ {
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/SDLControlFramePayloadRPCStartServiceAckSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/SDLControlFramePayloadRPCStartServiceAckSpec.m
index 7397f5713..8a198c63e 100644
--- a/SmartDeviceLinkTests/ProtocolSpecs/SDLControlFramePayloadRPCStartServiceAckSpec.m
+++ b/SmartDeviceLinkTests/ProtocolSpecs/SDLControlFramePayloadRPCStartServiceAckSpec.m
@@ -14,6 +14,9 @@ describe(@"Test encoding data", ^{
__block int32_t testHashId = 0;
__block int64_t testMTU = 0;
__block NSString *testProtocolVersion = nil;
+ __block NSArray<NSString *> *testSecondaryTransports = nil;
+ __block NSArray<NSNumber *> *testAudioServiceTransports = nil;
+ __block NSArray<NSNumber *> *testVideoServiceTransports = nil;
context(@"with paramaters", ^{
beforeEach(^{
@@ -21,7 +24,7 @@ describe(@"Test encoding data", ^{
testMTU = 5984649;
testProtocolVersion = @"1.32.32";
- testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMTU protocolVersion:testProtocolVersion];
+ testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMTU protocolVersion:testProtocolVersion secondaryTransports:nil audioServiceTransports:nil videoServiceTransports:nil];
});
it(@"should create the correct data", ^{
@@ -29,12 +32,29 @@ describe(@"Test encoding data", ^{
});
});
+ context(@"with secondary transport paramaters", ^{
+ beforeEach(^{
+ testHashId = 987654;
+ testMTU = 4096;
+ testProtocolVersion = @"5.10.01";
+ testSecondaryTransports = @[@"TCP_WIFI", @"IAP_USB"];
+ testAudioServiceTransports = @[@(2)];
+ testVideoServiceTransports = @[(@2), @(1)];
+
+ testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMTU protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
+ });
+
+ it(@"should create the correct data", ^{
+ expect(testPayload.data.description).to(equal(@"<c3000000 04766964 656f5365 72766963 65547261 6e73706f 72747300 13000000 10300002 00000010 31000100 00000010 68617368 49640006 120f0012 6d747500 00100000 00000000 04736563 6f6e6461 72795472 616e7370 6f727473 00240000 00023000 09000000 5443505f 57494649 00023100 08000000 4941505f 55534200 00046175 64696f53 65727669 63655472 616e7370 6f727473 000c0000 00103000 02000000 00027072 6f746f63 6f6c5665 7273696f 6e000800 0000352e 31302e30 310000>"));
+ });
+ });
+
context(@"without parameters", ^{
beforeEach(^{
testHashId = SDLControlFrameInt32NotFound;
testMTU = SDLControlFrameInt64NotFound;
- testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMTU protocolVersion:nil];
+ testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMTU protocolVersion:nil secondaryTransports:nil audioServiceTransports:nil videoServiceTransports:nil];
});
it(@"should create no data", ^{
@@ -49,22 +69,52 @@ describe(@"Test decoding data", ^{
__block int32_t testHashId = 0;
__block int64_t testMTU = 0;
__block NSString *testProtocolVersion = nil;
+ __block NSArray<NSString *> *testSecondaryTransports = nil;
+ __block NSArray<NSNumber *> *testAudioServiceTransports = nil;
+ __block NSArray<NSNumber *> *testVideoServiceTransports = nil;
- beforeEach(^{
- testHashId = 1545784;
- testMTU = 989786483;
- testProtocolVersion = @"3.89.5";
+ context(@"with paramaters", ^{
+ beforeEach(^{
+ testHashId = 1545784;
+ testMTU = 989786483;
+ testProtocolVersion = @"3.89.5";
- SDLControlFramePayloadRPCStartServiceAck *firstPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMTU protocolVersion:testProtocolVersion];
- testData = firstPayload.data;
+ SDLControlFramePayloadRPCStartServiceAck *firstPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMTU protocolVersion:testProtocolVersion secondaryTransports:nil audioServiceTransports:nil videoServiceTransports:nil];
+ testData = firstPayload.data;
- testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithData:testData];
+ testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithData:testData];
+ });
+
+ it(@"should output the correct params", ^{
+ expect(testPayload.hashId).to(equal(testHashId));
+ expect(testPayload.mtu).to(equal(testMTU));
+ expect(testPayload.protocolVersion).to(equal(testProtocolVersion));
+ });
});
- it(@"should output the correct params", ^{
- expect(testPayload.hashId).to(equal(testHashId));
- expect(testPayload.mtu).to(equal(testMTU));
- expect(testPayload.protocolVersion).to(equal(testProtocolVersion));
+ context(@"with secondary transportparamaters", ^{
+ beforeEach(^{
+ testHashId = 17999024;
+ testMTU = 1798250;
+ testProtocolVersion = @"6.01.00";
+ testSecondaryTransports = @[@"TCP_WIFI"];
+ testAudioServiceTransports = @[@(2), @(1)];
+ testVideoServiceTransports = @[@(1)];
+
+ SDLControlFramePayloadRPCStartServiceAck *firstPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMTU protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
+ testData = firstPayload.data;
+
+ testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithData:testData];
+ });
+
+ it(@"should output the correct params", ^{
+ expect(testPayload.hashId).to(equal(testHashId));
+ expect(testPayload.mtu).to(equal(testMTU));
+ expect(testPayload.protocolVersion).to(equal(testProtocolVersion));
+ expect(testPayload.secondaryTransports).to(equal(testSecondaryTransports));
+ expect(testPayload.audioServiceTransports).to(equal(testAudioServiceTransports));
+ expect(testPayload.videoServiceTransports).to(equal(testVideoServiceTransports));
+ });
});
});
diff --git a/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m b/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m
new file mode 100644
index 000000000..81b4196c7
--- /dev/null
+++ b/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m
@@ -0,0 +1,1052 @@
+//
+// SDLSecondaryTransportManagerSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Sho Amano on 2018/03/25.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLControlFramePayloadRegisterSecondaryTransportNak.h"
+#import "SDLControlFramePayloadRPCStartServiceAck.h"
+#import "SDLControlFramePayloadTransportEventUpdate.h"
+#import "SDLIAPTransport.h"
+#import "SDLProtocol.h"
+#import "SDLSecondaryTransportManager.h"
+#import "SDLStateMachine.h"
+#import "SDLTCPTransport.h"
+#import "SDLV2ProtocolMessage.h"
+
+/* copied from SDLSecondaryTransportManager.m */
+typedef NSNumber SDLServiceTypeBox;
+
+typedef NS_ENUM(NSInteger, SDLTransportClass) {
+ SDLTransportClassInvalid = 0,
+ SDLTransportClassPrimary = 1,
+ SDLTransportClassSecondary = 2,
+};
+typedef NSNumber SDLTransportClassBox;
+
+typedef NS_ENUM(NSInteger, SDLSecondaryTransportType) {
+ SDLTransportSelectionDisabled, // only for Secondary Transport
+ SDLTransportSelectionIAP,
+ SDLTransportSelectionTCP
+};
+
+// should be in sync with SDLSecondaryTransportManager.m
+static const float RetryConnectionDelay = 5.0;
+static const int TCPPortUnspecified = -1;
+
+
+@interface SDLSecondaryTransportManager ()
+
+// we need to reach to private properties for the tests
+@property (assign, nonatomic) SDLSecondaryTransportType secondaryTransportType;
+@property (nullable, strong, nonatomic) id<SDLTransportType> secondaryTransport;
+@property (nullable, strong, nonatomic) SDLProtocol *secondaryProtocol;
+@property (strong, nonatomic, nonnull) NSArray<SDLTransportClassBox *> *transportsForAudioService;
+@property (strong, nonatomic, nonnull) NSArray<SDLTransportClassBox *> *transportsForVideoService;
+@property (strong, nonatomic) NSMutableDictionary<SDLServiceTypeBox *, SDLTransportClassBox *> *streamingServiceTransportMap;
+@property (strong, nonatomic, nullable) NSString *ipAddress;
+@property (assign, nonatomic) int tcpPort;
+
+@end
+
+@interface SDLSecondaryTransportManager (ForTest)
+// Swap sdl_getAppState method to dummy implementation.
+// Since the test runs on the main thread, dispatch_sync()-ing to the main thread doesn't work.
++ (void)swapGetAppStateMethod;
+@end
+
+@implementation SDLSecondaryTransportManager (ForTest)
+- (UIApplicationState)dummyGetAppState {
+ NSLog(@"Testing: app state for secondary transport manager is always ACTIVE");
+ return UIApplicationStateActive;
+}
+
++ (void)swapGetAppStateMethod {
+ SEL selector = NSSelectorFromString(@"sdl_getAppState");
+ Method from = class_getInstanceMethod(self, selector);
+ Method to = class_getInstanceMethod(self, @selector(dummyGetAppState));
+ method_exchangeImplementations(from, to);
+}
+@end
+
+@interface SDLTCPTransport (ConnectionDisabled)
+// Disable connect and disconnect methods
++ (void)swapConnectionMethods;
+@end
+
+@implementation SDLTCPTransport (ConnectionDisabled)
+- (void)dummyConnect {
+ NSLog(@"SDLTCPTransport connect doing nothing");
+}
+- (void)dummyDisconnect {
+ NSLog(@"SDLTCPTransport disconnect doing nothing");
+}
+
++ (void)swapConnectionMethods {
+ Method from = class_getInstanceMethod(self, @selector(connect));
+ Method to = class_getInstanceMethod(self, @selector(dummyConnect));
+ method_exchangeImplementations(from, to);
+
+ from = class_getInstanceMethod(self, @selector(disconnect));
+ to = class_getInstanceMethod(self, @selector(dummyDisconnect));
+ method_exchangeImplementations(from, to);
+}
+@end
+
+@interface SDLIAPTransport (ConnectionDisabled)
+// Disable connect and disconnect methods
++ (void)swapConnectionMethods;
+@end
+
+@implementation SDLIAPTransport (ConnectionDisabled)
+- (void)dummyConnect {
+ NSLog(@"SDLIAPTransport connect doing nothing");
+}
+- (void)dummyDisconnect {
+ NSLog(@"SDLIAPTransport disconnect doing nothing");
+}
+
++ (void)swapConnectionMethods {
+ Method from = class_getInstanceMethod(self, @selector(connect));
+ Method to = class_getInstanceMethod(self, @selector(dummyConnect));
+ method_exchangeImplementations(from, to);
+
+ from = class_getInstanceMethod(self, @selector(disconnect));
+ to = class_getInstanceMethod(self, @selector(dummyDisconnect));
+ method_exchangeImplementations(from, to);
+}
+@end
+
+
+QuickSpecBegin(SDLSecondaryTransportManagerSpec)
+
+describe(@"the secondary transport manager ", ^{
+ __block SDLSecondaryTransportManager *manager = nil;
+ __block dispatch_queue_t testStateMachineQueue;
+ __block SDLProtocol *testPrimaryProtocol = nil;
+ __block id<SDLTransportType> testPrimaryTransport = nil;
+ __block id testStreamingProtocolDelegate = nil;
+
+ beforeEach(^{
+ [SDLSecondaryTransportManager swapGetAppStateMethod];
+ [SDLTCPTransport swapConnectionMethods];
+ [SDLIAPTransport swapConnectionMethods];
+
+ // "strict" mock. If one of the delegate methods is called without prior expectation, it will throw an exception
+ testStreamingProtocolDelegate = OCMStrictProtocolMock(@protocol(SDLStreamingProtocolDelegate));
+ testStateMachineQueue = dispatch_queue_create("com.sdl.testsecondarytransportmanager", DISPATCH_QUEUE_SERIAL);
+ manager = [[SDLSecondaryTransportManager alloc] initWithStreamingProtocolDelegate:testStreamingProtocolDelegate serialQueue:testStateMachineQueue];
+ });
+
+ afterEach(^{
+ // it is possible that manager calls methods of SDLStreamingProtocolDelegate while stopping, so accept them
+ // (Don't put OCMVerifyAll() after calling stop.)
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:OCMOCK_ANY toNewProtocol:nil]);
+ OCMExpect([testStreamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:OCMOCK_ANY toNewProtocol:nil]);
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stop];
+ });
+ manager = nil;
+
+ [SDLIAPTransport swapConnectionMethods];
+ [SDLTCPTransport swapConnectionMethods];
+ [SDLSecondaryTransportManager swapGetAppStateMethod];
+ });
+
+
+ it(@"should initialize its property", ^{
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionDisabled));
+ expect(manager.transportsForAudioService).to(equal(@[]));
+ expect(manager.transportsForVideoService).to(equal(@[]));
+ NSMutableDictionary<SDLServiceTypeBox *, SDLTransportClassBox *> *expectedAssignedTransport = [@{@(SDLServiceTypeAudio):@(SDLTransportClassInvalid),
+ @(SDLServiceTypeVideo):@(SDLTransportClassInvalid)} mutableCopy];
+ expect(manager.streamingServiceTransportMap).to(equal(expectedAssignedTransport));
+ expect(manager.ipAddress).to(beNil());
+ expect(manager.tcpPort).to(equal(TCPPortUnspecified));
+ });
+
+
+ describe(@"when started", ^{
+ beforeEach(^{
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ });
+
+ it(@"should transition to Started state", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStarted));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+
+ describe(@"In Started state", ^{
+ beforeEach(^{
+ // In the tests, we assume primary transport is iAP
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ testPrimaryTransport = [[SDLIAPTransport alloc] init];
+ testPrimaryProtocol.transport = testPrimaryTransport;
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+ });
+
+ describe(@"when received Start Service ACK on primary transport", ^{
+ __block SDLProtocolHeader *testStartServiceACKHeader = nil;
+ __block SDLProtocolMessage *testStartServiceACKMessage = nil;
+ __block SDLControlFramePayloadRPCStartServiceAck *testStartServiceACKPayload = nil;
+ __block int32_t testHashId = 12345;
+ __block int64_t testMtu = 12345678;
+ __block NSString *testProtocolVersion = @"5.1.0";
+ __block NSArray<NSString *> *testSecondaryTransports = nil;
+ __block NSArray<NSNumber *> *testAudioServiceTransports = nil;
+ __block NSArray<NSNumber *> *testVideoServiceTransports = nil;
+
+ beforeEach(^{
+ testStartServiceACKHeader = [SDLProtocolHeader headerForVersion:5];
+ testStartServiceACKHeader.frameType = SDLFrameTypeControl;
+ testStartServiceACKHeader.serviceType = SDLServiceTypeRPC;
+ testStartServiceACKHeader.frameData = SDLFrameInfoStartServiceACK;
+
+ testSecondaryTransports = nil;
+ testAudioServiceTransports = nil;
+ testVideoServiceTransports = nil;
+ });
+
+ context(@"with parameters for TCP secondary transport", ^{
+ beforeEach(^{
+ testSecondaryTransports = @[@"TCP_WIFI"];
+ testAudioServiceTransports = @[@(2), @(1)];
+ testVideoServiceTransports = @[@(2)];
+
+ testStartServiceACKPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMtu protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
+ testStartServiceACKMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testStartServiceACKHeader andPayload:testStartServiceACKPayload.data];
+
+ });
+
+ it(@"should configure its properties and transition to Configured state", ^{
+ // in this configuration, only audio service is allowed on primary transport
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:testPrimaryProtocol]);
+
+ [testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+
+ expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionTCP));
+ NSArray<SDLTransportClassBox *> *expectedTransportsForAudioService = @[@(SDLTransportClassSecondary), @(SDLTransportClassPrimary)];
+ expect(manager.transportsForAudioService).to(equal(expectedTransportsForAudioService));
+ NSArray<SDLTransportClassBox *> *expectedTransportsForVideoService = @[@(SDLTransportClassSecondary)];
+ expect(manager.transportsForVideoService).to(equal(expectedTransportsForVideoService));
+
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"with only secondary transports parameter for TCP", ^{
+ beforeEach(^{
+ // Note: this is not allowed for now. It should contain only one element.
+ testSecondaryTransports = @[@"TCP_WIFI", @"IAP_USB_HOST_MODE"];
+
+ testStartServiceACKPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMtu protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
+ testStartServiceACKMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testStartServiceACKHeader andPayload:testStartServiceACKPayload.data];
+ });
+
+ it(@"should configure its properties and transition to Configured state", ^{
+ // in this case, audio and video services start on primary transport (for compatibility)
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:testPrimaryProtocol]);
+ OCMExpect([testStreamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:testPrimaryProtocol]);
+
+ [testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+
+ expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionTCP));
+ expect(manager.transportsForAudioService).to(equal(@[@(SDLTransportClassPrimary)]));
+ expect(manager.transportsForVideoService).to(equal(@[@(SDLTransportClassPrimary)]));
+
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"with parameters for iAP secondary transport", ^{
+ beforeEach(^{
+ testSecondaryTransports = @[@"IAP_USB_HOST_MODE"];
+ testAudioServiceTransports = @[@(2)];
+ testVideoServiceTransports = @[@(2)];
+
+ testStartServiceACKPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMtu protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
+ testStartServiceACKMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testStartServiceACKHeader andPayload:testStartServiceACKPayload.data];
+ });
+
+ it(@"should transition to Configured state with transport type disabled", ^{
+ // Since primary transport is iAP, we cannot use iAP for secondary transport.
+ // So both services run on primary transport.
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:testPrimaryProtocol]);
+ OCMExpect([testStreamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:testPrimaryProtocol]);
+
+ [testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+
+ // see the comment above
+ expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionDisabled));
+ expect(manager.transportsForAudioService).to(equal(@[@(SDLTransportClassPrimary)]));
+ expect(manager.transportsForVideoService).to(equal(@[@(SDLTransportClassPrimary)]));
+
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"without secondary transport related parameter", ^{
+ beforeEach(^{
+ testStartServiceACKPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMtu protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
+ testStartServiceACKMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testStartServiceACKHeader andPayload:testStartServiceACKPayload.data];
+ });
+
+ it(@"should transition to Configured state with transport type disabled", ^{
+ // both services run on primary transport
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:testPrimaryProtocol]);
+ OCMExpect([testStreamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:testPrimaryProtocol]);
+
+ [testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+
+ expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionDisabled));
+ expect(manager.transportsForAudioService).to(equal(@[@(SDLTransportClassPrimary)]));
+ expect(manager.transportsForVideoService).to(equal(@[@(SDLTransportClassPrimary)]));
+
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+ describe(@"when received Transport Event Update frame on primary transport prior to Start Service ACK", ^{
+ __block SDLProtocolHeader *testTransportEventUpdateHeader = nil;
+ __block SDLProtocolMessage *testTransportEventUpdateMessage = nil;
+ __block SDLControlFramePayloadTransportEventUpdate *testTransportEventUpdatePayload = nil;
+ __block NSString *testTcpIpAddress = nil;
+ __block int32_t testTcpPort = TCPPortUnspecified;
+
+ beforeEach(^{
+ testTransportEventUpdateHeader = [SDLProtocolHeader headerForVersion:5];
+ testTransportEventUpdateHeader.frameType = SDLFrameTypeControl;
+ testTransportEventUpdateHeader.serviceType = SDLServiceTypeControl;
+ testTransportEventUpdateHeader.frameData = SDLFrameInfoTransportEventUpdate;
+ testTcpIpAddress = @"192.168.1.1";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should configure its properties but stay in Started state", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStarted));
+
+ expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionDisabled));
+ expect(manager.transportsForAudioService).to(equal(@[]));
+ expect(manager.transportsForVideoService).to(equal(@[]));
+ expect(manager.ipAddress).to(equal(testTcpIpAddress));
+ expect(manager.tcpPort).to(equal(testTcpPort));
+
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ describe(@"when received Transport Event Update frame and then Start Service ACK on primary transport", ^{
+ __block SDLProtocolHeader *testTransportEventUpdateHeader = nil;
+ __block SDLProtocolMessage *testTransportEventUpdateMessage = nil;
+ __block SDLControlFramePayloadTransportEventUpdate *testTransportEventUpdatePayload = nil;
+ __block NSString *testTcpIpAddress = nil;
+ __block int32_t testTcpPort = TCPPortUnspecified;
+
+ __block SDLProtocolHeader *testStartServiceACKHeader = nil;
+ __block SDLProtocolMessage *testStartServiceACKMessage = nil;
+ __block SDLControlFramePayloadRPCStartServiceAck *testStartServiceACKPayload = nil;
+ __block int32_t testHashId = 12345;
+ __block int64_t testMtu = 1234567;
+ __block NSString *testProtocolVersion = @"5.1.0";
+ __block NSArray<NSString *> *testSecondaryTransports = @[@"TCP_WIFI"];
+ __block NSArray<NSNumber *> *testAudioServiceTransports = @[@(2)];
+ __block NSArray<NSNumber *> *testVideoServiceTransports = @[@(2)];
+
+ beforeEach(^{
+ testTransportEventUpdateHeader = [SDLProtocolHeader headerForVersion:5];
+ testTransportEventUpdateHeader.frameType = SDLFrameTypeControl;
+ testTransportEventUpdateHeader.serviceType = SDLServiceTypeControl;
+ testTransportEventUpdateHeader.frameData = SDLFrameInfoTransportEventUpdate;
+
+ testStartServiceACKHeader = [SDLProtocolHeader headerForVersion:5];
+ testStartServiceACKHeader.frameType = SDLFrameTypeControl;
+ testStartServiceACKHeader.serviceType = SDLServiceTypeRPC;
+ testStartServiceACKHeader.frameData = SDLFrameInfoStartServiceACK;
+ });
+
+ context(@"and if TCP configuration is valid", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"fd12:3456:789a::1";
+ testTcpPort = 5678;
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+
+ testStartServiceACKPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMtu protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
+ testStartServiceACKMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testStartServiceACKHeader andPayload:testStartServiceACKPayload.data];
+ });
+
+ it(@"should configure its properties and immediately transition to Connecting state", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
+
+ expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionTCP));
+ NSArray<SDLTransportClassBox *> *expectedTransportsForAudioService = @[@(SDLTransportClassSecondary)];
+ expect(manager.transportsForAudioService).to(equal(expectedTransportsForAudioService));
+ NSArray<SDLTransportClassBox *> *expectedTransportsForVideoService = @[@(SDLTransportClassSecondary)];
+ expect(manager.transportsForVideoService).to(equal(expectedTransportsForVideoService));
+ expect(manager.ipAddress).to(equal(testTcpIpAddress));
+ expect(manager.tcpPort).to(equal(testTcpPort));
+
+ SDLTCPTransport *secondaryTransport = (SDLTCPTransport *)manager.secondaryTransport;
+ expect(secondaryTransport.hostName).to(equal(testTcpIpAddress));
+ NSString *portNumberString = [NSString stringWithFormat:@"%d", testTcpPort];
+ expect(secondaryTransport.portNumber).to(equal(portNumberString));
+
+ // audio and video services will not start until secondary transport is established
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"and if TCP configuration is invalid", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"";
+ testTcpPort = 5678;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+
+ testStartServiceACKPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMtu protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
+ testStartServiceACKMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testStartServiceACKHeader andPayload:testStartServiceACKPayload.data];
+ });
+
+ it(@"should configure its properties and transition to Configured state", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+
+ expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionTCP));
+ NSArray<SDLTransportClassBox *> *expectedTransportsForAudioService = @[@(SDLTransportClassSecondary)];
+ expect(manager.transportsForAudioService).to(equal(expectedTransportsForAudioService));
+ NSArray<SDLTransportClassBox *> *expectedTransportsForVideoService = @[@(SDLTransportClassSecondary)];
+ expect(manager.transportsForVideoService).to(equal(expectedTransportsForVideoService));
+ expect(manager.ipAddress).to(equal(testTcpIpAddress));
+ expect(manager.tcpPort).to(equal(testTcpPort));
+
+ expect(manager.secondaryTransport).to(beNil());
+
+ // audio and video services will not start until secondary transport is established
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+ describe(@"when stopped", ^{
+ it(@"should transition to Stopped state", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stop];
+ });
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+
+ describe(@"In Configured state", ^{
+ describe(@"if secondary transport is iAP", ^{
+ beforeEach(^{
+ // in this case we assume the primary transport is TCP
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ testPrimaryTransport = [[SDLTCPTransport alloc] init];
+ testPrimaryProtocol.transport = testPrimaryTransport;
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+
+ manager.secondaryTransportType = SDLTransportSelectionIAP;
+ });
+
+ it(@"should transition to Connecting state", ^{
+ // setToState cannot be used here, as the method will set the state after calling didEnterStateConfigured
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
+ });
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ describe(@"if secondary transport is TCP", ^{
+ beforeEach(^{
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ testPrimaryTransport = [[SDLIAPTransport alloc] init];
+ testPrimaryProtocol.transport = testPrimaryTransport;
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+
+ manager.secondaryTransportType = SDLTransportSelectionTCP;
+ manager.ipAddress = nil;
+ manager.tcpPort = TCPPortUnspecified;
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
+ });
+ });
+
+ describe(@"and Transport Event Update is not received", ^{
+ it(@"should stay in Configured state", ^{
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ describe(@"and Transport Event Update is received", ^{
+ __block SDLProtocolHeader *testTransportEventUpdateHeader = nil;
+ __block SDLProtocolMessage *testTransportEventUpdateMessage = nil;
+ __block SDLControlFramePayloadTransportEventUpdate *testTransportEventUpdatePayload = nil;
+ __block NSString *testTcpIpAddress = nil;
+ __block int32_t testTcpPort = TCPPortUnspecified;
+
+ beforeEach(^{
+ testTransportEventUpdateHeader = [SDLProtocolHeader headerForVersion:5];
+ testTransportEventUpdateHeader.frameType = SDLFrameTypeControl;
+ testTransportEventUpdateHeader.serviceType = SDLServiceTypeControl;
+ testTransportEventUpdateHeader.frameData = SDLFrameInfoTransportEventUpdate;
+ testTcpIpAddress = @"10.20.30.40";
+ testTcpPort = 22222;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should transition to Connecting state", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+ describe(@"when stopped", ^{
+ beforeEach(^{
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ testPrimaryTransport = [[SDLIAPTransport alloc] init];
+ testPrimaryProtocol.transport = testPrimaryTransport;
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ [manager.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
+ });
+ });
+
+ it(@"should transition to Stopped state", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stop];
+ });
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+
+ describe(@"In Connecting state", ^{
+ __block SDLProtocol *secondaryProtocol = nil;
+ __block id testSecondaryProtocolMock = nil;
+
+ beforeEach(^{
+ secondaryProtocol = [[SDLProtocol alloc] init];
+ testSecondaryProtocolMock = OCMPartialMock(secondaryProtocol);
+
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ testPrimaryTransport = [[SDLIAPTransport alloc] init];
+ testPrimaryProtocol.transport = testPrimaryTransport;
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+
+ [secondaryProtocol.protocolDelegateTable addObject:manager];
+ manager.secondaryProtocol = secondaryProtocol;
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateConnecting fromOldState:nil callEnterTransition:NO];
+ });
+ });
+
+ describe(@"when transport is opened", ^{
+ it(@"should send out Register Secondary Transport frame", ^{
+ OCMExpect([testSecondaryProtocolMock registerSecondaryTransport]);
+
+ [testSecondaryProtocolMock onProtocolOpened];
+ [NSThread sleepForTimeInterval:0.1];
+
+ OCMVerifyAll(testSecondaryProtocolMock);
+ OCMVerifyAll(testStreamingProtocolDelegate);
+
+ // Note: cannot test the timeout scenario since the timer fires on main thread
+ });
+
+ describe(@"and Register Secondary Transport ACK is received", ^{
+ __block SDLProtocolHeader *testRegisterSecondaryTransportAckHeader = nil;
+ __block SDLProtocolMessage *testRegisterSecondaryTransportAckMessage = nil;
+
+ beforeEach(^{
+ // assume audio and video services are allowed only on secondary transport
+ manager.transportsForAudioService = @[@(SDLTransportClassSecondary)];
+ manager.transportsForVideoService = @[@(SDLTransportClassSecondary)];
+ manager.streamingServiceTransportMap[@(SDLServiceTypeAudio)] = @(SDLTransportClassInvalid);
+ manager.streamingServiceTransportMap[@(SDLServiceTypeVideo)] = @(SDLTransportClassInvalid);
+
+ testRegisterSecondaryTransportAckHeader = [SDLProtocolHeader headerForVersion:5];
+ testRegisterSecondaryTransportAckHeader.frameType = SDLFrameTypeControl;
+ testRegisterSecondaryTransportAckHeader.serviceType = SDLServiceTypeControl;
+ testRegisterSecondaryTransportAckHeader.frameData = SDLFrameInfoRegisterSecondaryTransportACK;
+
+ testRegisterSecondaryTransportAckMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testRegisterSecondaryTransportAckHeader andPayload:nil];
+ });
+
+ it(@"should transition to Registered state", ^{
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:secondaryProtocol]);
+ OCMExpect([testStreamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:nil toNewProtocol:secondaryProtocol]);
+
+ [testSecondaryProtocolMock handleBytesFromTransport:testRegisterSecondaryTransportAckMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateRegistered));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ describe(@"and Register Secondary Transport NACK is received", ^{
+ __block SDLProtocolHeader *testRegisterSecondaryTransportNakHeader = nil;
+ __block SDLProtocolMessage *testRegisterSecondaryTransportNakMessage = nil;
+ __block SDLControlFramePayloadRegisterSecondaryTransportNak *testRegisterSecondaryTransportPayload = nil;
+ __block NSString *testRegisterFailedReason = @"Unknown";
+
+ beforeEach(^{
+ testRegisterSecondaryTransportNakHeader = [SDLProtocolHeader headerForVersion:5];
+ testRegisterSecondaryTransportNakHeader.frameType = SDLFrameTypeControl;
+ testRegisterSecondaryTransportNakHeader.serviceType = SDLServiceTypeControl;
+ testRegisterSecondaryTransportNakHeader.frameData = SDLFrameInfoRegisterSecondaryTransportNACK;
+
+ testRegisterSecondaryTransportPayload = [[SDLControlFramePayloadRegisterSecondaryTransportNak alloc] initWithReason:testRegisterFailedReason];
+ testRegisterSecondaryTransportNakMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testRegisterSecondaryTransportNakHeader andPayload:testRegisterSecondaryTransportPayload.data];
+ });
+
+ it(@"should transition to Reconnecting state", ^{
+ [testSecondaryProtocolMock handleBytesFromTransport:testRegisterSecondaryTransportNakMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateReconnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+ describe(@"when transport is closed", ^{
+ it(@"should transition to Reconnecting state", ^{
+ [testSecondaryProtocolMock onProtocolClosed];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateReconnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ describe(@"when Transport Event Update is received", ^{
+ __block SDLProtocolHeader *testTransportEventUpdateHeader = nil;
+ __block SDLProtocolMessage *testTransportEventUpdateMessage = nil;
+ __block SDLControlFramePayloadTransportEventUpdate *testTransportEventUpdatePayload = nil;
+ __block NSString *testTcpIpAddress = nil;
+ __block int32_t testTcpPort = TCPPortUnspecified;
+
+ beforeEach(^{
+ manager.secondaryTransportType = SDLTransportSelectionTCP;
+ manager.ipAddress = @"192.168.1.1";
+ manager.tcpPort = 12345;
+
+ testTransportEventUpdateHeader = [SDLProtocolHeader headerForVersion:5];
+ testTransportEventUpdateHeader.frameType = SDLFrameTypeControl;
+ testTransportEventUpdateHeader.serviceType = SDLServiceTypeControl;
+ testTransportEventUpdateHeader.frameData = SDLFrameInfoTransportEventUpdate;
+ });
+
+ context(@"with same IP address and port number", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"192.168.1.1";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should ignore the frame and stay in Connecting state", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"with different IP address", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"172.16.12.34";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should transition to Configured state, then transition to Connecting state again", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"with invalid IP address", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should transition to Configured state", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ });
+
+ describe(@"when stopped", ^{
+ it(@"should transition to Stopped state", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stop];
+ });
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+
+ describe(@"In Registered state", ^{
+ __block SDLProtocol *secondaryProtocol = nil;
+ __block id testSecondaryProtocolMock = nil;
+
+ beforeEach(^{
+ secondaryProtocol = [[SDLProtocol alloc] init];
+ testSecondaryProtocolMock = OCMPartialMock(secondaryProtocol);
+
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ testPrimaryTransport = [[SDLIAPTransport alloc] init];
+ testPrimaryProtocol.transport = testPrimaryTransport;
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+
+ [secondaryProtocol.protocolDelegateTable addObject:manager];
+ manager.secondaryProtocol = secondaryProtocol;
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateRegistered fromOldState:nil callEnterTransition:NO];
+ });
+ });
+
+ describe(@"when Transport Event Update is received", ^{
+ __block SDLProtocolHeader *testTransportEventUpdateHeader = nil;
+ __block SDLProtocolMessage *testTransportEventUpdateMessage = nil;
+ __block SDLControlFramePayloadTransportEventUpdate *testTransportEventUpdatePayload = nil;
+ __block NSString *testTcpIpAddress = nil;
+ __block int32_t testTcpPort = TCPPortUnspecified;
+
+ beforeEach(^{
+ // assume audio and video services are allowed only on secondary transport
+ manager.transportsForAudioService = @[@(SDLTransportClassSecondary)];
+ manager.transportsForVideoService = @[@(SDLTransportClassSecondary)];
+ manager.streamingServiceTransportMap[@(SDLServiceTypeAudio)] = @(SDLTransportClassSecondary);
+ manager.streamingServiceTransportMap[@(SDLServiceTypeVideo)] = @(SDLTransportClassSecondary);
+
+ manager.secondaryTransportType = SDLTransportSelectionTCP;
+ manager.ipAddress = @"192.168.1.1";
+ manager.tcpPort = 12345;
+
+ testTransportEventUpdateHeader = [SDLProtocolHeader headerForVersion:5];
+ testTransportEventUpdateHeader.frameType = SDLFrameTypeControl;
+ testTransportEventUpdateHeader.serviceType = SDLServiceTypeControl;
+ testTransportEventUpdateHeader.frameData = SDLFrameInfoTransportEventUpdate;
+ });
+
+ context(@"with same IP address and port number", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"192.168.1.1";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should ignore the frame and stay in Registered state", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateRegistered));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"with different IP address", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"172.16.12.34";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should transition to Configured state, then transition to Connecting state again", ^{
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:secondaryProtocol toNewProtocol:nil]);
+ OCMExpect([testStreamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:secondaryProtocol toNewProtocol:nil]);
+
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"with invalid IP address", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should transition to Configured state", ^{
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:secondaryProtocol toNewProtocol:nil]);
+ OCMExpect([testStreamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:secondaryProtocol toNewProtocol:nil]);
+
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+ describe(@"when transport is closed", ^{
+ beforeEach(^{
+ // assume audio and video services are allowed only on secondary transport
+ manager.transportsForAudioService = @[@(SDLTransportClassSecondary)];
+ manager.transportsForVideoService = @[@(SDLTransportClassSecondary)];
+ manager.streamingServiceTransportMap[@(SDLServiceTypeAudio)] = @(SDLTransportClassSecondary);
+ manager.streamingServiceTransportMap[@(SDLServiceTypeVideo)] = @(SDLTransportClassSecondary);
+ });
+
+ it(@"should transition to Reconnecting state", ^{
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:secondaryProtocol toNewProtocol:nil]);
+ OCMExpect([testStreamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:secondaryProtocol toNewProtocol:nil]);
+
+ [testSecondaryProtocolMock onProtocolClosed];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateReconnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ describe(@"when stopped", ^{
+ beforeEach(^{
+ // assume audio and video services are allowed only on secondary transport
+ manager.transportsForAudioService = @[@(SDLTransportClassSecondary)];
+ manager.transportsForVideoService = @[@(SDLTransportClassSecondary)];
+ manager.streamingServiceTransportMap[@(SDLServiceTypeAudio)] = @(SDLTransportClassSecondary);
+ manager.streamingServiceTransportMap[@(SDLServiceTypeVideo)] = @(SDLTransportClassSecondary);
+ });
+
+ it(@"should transition to Stopped state", ^{
+ OCMExpect([testStreamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:secondaryProtocol toNewProtocol:nil]);
+ OCMExpect([testStreamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:secondaryProtocol toNewProtocol:nil]);
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stop];
+ });
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+
+ describe(@"In Reconnecting state", ^{
+ beforeEach(^{
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ testPrimaryTransport = [[SDLIAPTransport alloc] init];
+ testPrimaryProtocol.transport = testPrimaryTransport;
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ [manager.stateMachine setToState:SDLSecondaryTransportStateReconnecting fromOldState:nil callEnterTransition:NO];
+ });
+ });
+
+ describe(@"when reconnecting timeout is fired", ^{
+ beforeEach(^{
+ manager.secondaryTransportType = SDLTransportSelectionTCP;
+ manager.ipAddress = @"";
+ manager.tcpPort = 12345;
+ });
+
+ it(@"should transition to Configured state", ^{
+ // call didEnterStateReconnecting
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateReconnecting fromOldState:nil callEnterTransition:YES];
+ });
+
+ // wait for the timer
+ float waitTime = RetryConnectionDelay + 5.0;
+ NSLog(@"Please wait for reconnection timeout ... (for %f seconds)", waitTime);
+ [NSThread sleepForTimeInterval:waitTime];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ describe(@"when Transport Event Update is received", ^{
+ __block SDLProtocolHeader *testTransportEventUpdateHeader = nil;
+ __block SDLProtocolMessage *testTransportEventUpdateMessage = nil;
+ __block SDLControlFramePayloadTransportEventUpdate *testTransportEventUpdatePayload = nil;
+ __block NSString *testTcpIpAddress = nil;
+ __block int32_t testTcpPort = TCPPortUnspecified;
+
+ beforeEach(^{
+ manager.secondaryTransportType = SDLTransportSelectionTCP;
+ manager.ipAddress = @"192.168.1.1";
+ manager.tcpPort = 12345;
+
+ testTransportEventUpdateHeader = [SDLProtocolHeader headerForVersion:5];
+ testTransportEventUpdateHeader.frameType = SDLFrameTypeControl;
+ testTransportEventUpdateHeader.serviceType = SDLServiceTypeControl;
+ testTransportEventUpdateHeader.frameData = SDLFrameInfoTransportEventUpdate;
+ });
+
+ context(@"with same IP address and port number", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"192.168.1.1";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should ignore the frame and stay in Reconnecting state", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateReconnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"with different IP address", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"172.16.12.34";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should transition to Configured state before timeout, then transition to Connecting state again", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+
+ context(@"with invalid IP address", ^{
+ beforeEach(^{
+ testTcpIpAddress = @"";
+ testTcpPort = 12345;
+
+ testTransportEventUpdatePayload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithTcpIpAddress:testTcpIpAddress tcpPort:testTcpPort];
+ testTransportEventUpdateMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testTransportEventUpdateHeader andPayload:testTransportEventUpdatePayload.data];
+ });
+
+ it(@"should transition to Configured state before timeout", ^{
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
+ [NSThread sleepForTimeInterval:0.1];
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+
+ describe(@"when stopped", ^{
+ it(@"should transition to Stopped state", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stop];
+ });
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLStreamingAudioLifecycleManagerSpec.m b/SmartDeviceLinkTests/SDLStreamingAudioLifecycleManagerSpec.m
new file mode 100644
index 000000000..ecd043840
--- /dev/null
+++ b/SmartDeviceLinkTests/SDLStreamingAudioLifecycleManagerSpec.m
@@ -0,0 +1,420 @@
+//
+// SDLStreamingAudioLifecycleManagerSpec.m
+// SmartDeviceLink-iOS
+//
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLConnectionManagerType.h"
+#import "SDLControlFramePayloadAudioStartServiceAck.h"
+#import "SDLControlFramePayloadConstants.h"
+#import "SDLControlFramePayloadNak.h"
+#import "SDLDisplayCapabilities.h"
+#import "SDLGenericResponse.h"
+#import "SDLGetSystemCapability.h"
+#import "SDLGetSystemCapabilityResponse.h"
+#import "SDLGlobals.h"
+#import "SDLFocusableItemLocatorType.h"
+#import "SDLFocusableItemLocator.h"
+#import "SDLHMILevel.h"
+#import "SDLImageResolution.h"
+#import "SDLNotificationConstants.h"
+#import "SDLOnHMIStatus.h"
+#import "SDLProtocol.h"
+#import "SDLRPCNotificationNotification.h"
+#import "SDLRegisterAppInterfaceResponse.h"
+#import "SDLRPCResponseNotification.h"
+#import "SDLScreenParams.h"
+#import "SDLStateMachine.h"
+#import "SDLStreamingMediaConfiguration.h"
+#import "SDLStreamingAudioLifecycleManager.h"
+#import "SDLFakeStreamingManagerDataSource.h"
+#import "SDLSystemCapability.h"
+#import "SDLV2ProtocolHeader.h"
+#import "SDLV2ProtocolMessage.h"
+#import "TestConnectionManager.h"
+
+QuickSpecBegin(SDLStreamingAudioLifecycleManagerSpec)
+
+describe(@"the audio streaming media manager", ^{
+ __block SDLStreamingAudioLifecycleManager *streamingLifecycleManager = nil;
+ __block SDLStreamingMediaConfiguration *testConfiguration = [SDLStreamingMediaConfiguration insecureConfiguration];
+ __block TestConnectionManager *testConnectionManager = 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(^{
+ testConnectionManager = [[TestConnectionManager alloc] init];
+ streamingLifecycleManager = [[SDLStreamingAudioLifecycleManager alloc] initWithConnectionManager:testConnectionManager configuration:testConfiguration];
+ });
+
+ it(@"should initialize properties", ^{
+ expect(streamingLifecycleManager.audioManager).toNot(beNil());
+ expect(@(streamingLifecycleManager.isStreamingSupported)).to(equal(@NO));
+ expect(@(streamingLifecycleManager.isAudioConnected)).to(equal(@NO));
+ expect(@(streamingLifecycleManager.isAudioEncrypted)).to(equal(@NO));
+ expect(@(streamingLifecycleManager.requestedEncryptionType)).to(equal(@(SDLStreamingEncryptionFlagNone)));
+ expect(streamingLifecycleManager.currentAppState).to(equal(SDLAppStateActive));
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateStopped));
+ });
+
+ describe(@"when started", ^{
+ __block BOOL readyHandlerSuccess = NO;
+ __block NSError *readyHandlerError = nil;
+
+ __block id protocolMock = OCMClassMock([SDLProtocol class]);
+
+ beforeEach(^{
+ readyHandlerSuccess = NO;
+ readyHandlerError = nil;
+
+ [streamingLifecycleManager startWithProtocol:protocolMock];
+ });
+
+ it(@"should be ready to stream", ^{
+ expect(@(streamingLifecycleManager.isStreamingSupported)).to(equal(@NO));
+ expect(@(streamingLifecycleManager.isAudioConnected)).to(equal(@NO));
+ expect(@(streamingLifecycleManager.isAudioEncrypted)).to(equal(@NO));
+ expect(streamingLifecycleManager.currentAppState).to(equal(SDLAppStateActive));
+ expect(streamingLifecycleManager.currentAudioStreamState).to(match(SDLAudioStreamStateStopped));
+ });
+
+ describe(@"after receiving a register app interface notification", ^{
+ __block SDLRegisterAppInterfaceResponse *someRegisterAppInterfaceResponse = nil;
+ __block SDLDisplayCapabilities *someDisplayCapabilities = nil;
+ __block SDLScreenParams *someScreenParams = nil;
+ __block SDLImageResolution *someImageResolution = nil;
+
+ beforeEach(^{
+ someImageResolution = [[SDLImageResolution alloc] init];
+ someImageResolution.resolutionWidth = @(600);
+ someImageResolution.resolutionHeight = @(100);
+
+ someScreenParams = [[SDLScreenParams alloc] init];
+ someScreenParams.resolution = someImageResolution;
+ });
+
+ context(@"that does not support graphics", ^{
+ beforeEach(^{
+ someDisplayCapabilities = [[SDLDisplayCapabilities alloc] init];
+ someDisplayCapabilities.graphicSupported = @NO;
+
+ someDisplayCapabilities.screenParams = someScreenParams;
+
+ someRegisterAppInterfaceResponse = [[SDLRegisterAppInterfaceResponse alloc] init];
+ someRegisterAppInterfaceResponse.displayCapabilities = someDisplayCapabilities;
+ SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveRegisterAppInterfaceResponse object:self rpcResponse:someRegisterAppInterfaceResponse];
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+ [NSThread sleepForTimeInterval:0.1];
+ });
+
+ it(@"should not support streaming", ^{
+ expect(@(streamingLifecycleManager.isStreamingSupported)).to(equal(@NO));
+ });
+ });
+
+ context(@"that supports graphics", ^{
+ beforeEach(^{
+ someDisplayCapabilities = [[SDLDisplayCapabilities alloc] init];
+ someDisplayCapabilities.graphicSupported = @YES;
+
+ someDisplayCapabilities.screenParams = someScreenParams;
+
+ someRegisterAppInterfaceResponse = [[SDLRegisterAppInterfaceResponse alloc] init];
+ someRegisterAppInterfaceResponse.displayCapabilities = someDisplayCapabilities;
+ SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveRegisterAppInterfaceResponse object:self rpcResponse:someRegisterAppInterfaceResponse];
+
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+ [NSThread sleepForTimeInterval:0.1];
+ });
+
+ it(@"should support streaming", ^{
+ expect(@(streamingLifecycleManager.isStreamingSupported)).to(equal(@YES));
+ });
+ });
+ });
+
+ describe(@"if the app state is active", ^{
+ __block id streamStub = nil;
+
+ beforeEach(^{
+ streamStub = OCMPartialMock(streamingLifecycleManager);
+
+ OCMStub([streamStub isStreamingSupported]).andReturn(YES);
+
+ [streamingLifecycleManager.appStateMachine setToState:SDLAppStateActive fromOldState:nil callEnterTransition:NO];
+ });
+
+ describe(@"and audio stream is open", ^{
+ beforeEach(^{
+ [streamingLifecycleManager.audioStreamStateMachine setToState:SDLAudioStreamStateReady fromOldState:nil callEnterTransition:NO];
+ });
+
+ describe(@"and the hmi state is limited", ^{
+ beforeEach(^{
+ streamingLifecycleManager.hmiLevel = SDLHMILevelLimited;
+ });
+
+ describe(@"and the hmi state changes to", ^{
+ context(@"none", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelNone);
+ });
+
+ it(@"should close audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateShuttingDown));
+ });
+ });
+
+ context(@"background", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelBackground);
+ });
+
+ it(@"should close audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateShuttingDown));
+ });
+ });
+
+ context(@"limited", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelLimited);
+ });
+
+ it(@"should not close audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateReady));
+ });
+ });
+
+ context(@"full", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelFull);
+ });
+
+ it(@"should not close audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateReady));
+ });
+ });
+ });
+
+ describe(@"and the app state changes to", ^{
+ context(@"inactive", ^{
+ beforeEach(^{
+ [streamingLifecycleManager.appStateMachine setToState:SDLAppStateInactive fromOldState:nil callEnterTransition:YES];
+ });
+
+ it(@"should shut down the video stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateShuttingDown));
+ });
+ });
+ });
+ });
+
+ describe(@"and the hmi state is full", ^{
+ beforeEach(^{
+ streamingLifecycleManager.hmiLevel = SDLHMILevelFull;
+ });
+
+ context(@"and hmi state changes to none", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelNone);
+ });
+
+ it(@"should close audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateShuttingDown));
+ });
+ });
+
+ context(@"and hmi state changes to background", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelBackground);
+ });
+
+ it(@"should close audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateShuttingDown));
+ });
+ });
+
+ context(@"and hmi state changes to limited", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelLimited);
+ });
+
+ it(@"should not close audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateReady));
+ });
+ });
+
+ context(@"and hmi state changes to full", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelFull);
+ });
+
+ it(@"should not close audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateReady));
+ });
+ });
+ });
+ });
+
+ describe(@"and audio stream is closed", ^{
+ beforeEach(^{
+ [streamingLifecycleManager.audioStreamStateMachine setToState:SDLAudioStreamStateStopped fromOldState:nil callEnterTransition:NO];
+ });
+
+ describe(@"and the hmi state is none", ^{
+ beforeEach(^{
+ streamingLifecycleManager.hmiLevel = SDLHMILevelNone;
+ });
+
+ context(@"and hmi state changes to none", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelNone);
+ });
+
+ it(@"should not start audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateStopped));
+ });
+ });
+
+ context(@"and hmi state changes to background", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelBackground);
+ });
+
+ it(@"should not start audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateStopped));
+ });
+ });
+
+ context(@"and hmi state changes to limited", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelLimited);
+ });
+
+ it(@"should start audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateStarting));
+ });
+ });
+
+ context(@"and hmi state changes to full", ^{
+ beforeEach(^{
+ sendNotificationForHMILevel(SDLHMILevelFull);
+ });
+
+ it(@"should start audio stream", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateStarting));
+ });
+ });
+ });
+ });
+ });
+
+ describe(@"sending a video capabilities request", ^{
+ describe(@"after receiving an Audio Start ACK", ^{
+ __block SDLProtocolHeader *testAudioHeader = nil;
+ __block SDLProtocolMessage *testAudioMessage = nil;
+ __block SDLControlFramePayloadAudioStartServiceAck *testAudioStartServicePayload = nil;
+ __block int64_t testMTU = 786579;
+
+ beforeEach(^{
+ [streamingLifecycleManager.audioStreamStateMachine setToState:SDLAudioStreamStateStarting fromOldState:nil callEnterTransition:NO];
+
+ testAudioHeader = [[SDLV2ProtocolHeader alloc] initWithVersion:5];
+ testAudioHeader.frameType = SDLFrameTypeSingle;
+ testAudioHeader.frameData = SDLFrameInfoStartServiceACK;
+ testAudioHeader.encrypted = YES;
+ testAudioHeader.serviceType = SDLServiceTypeAudio;
+
+ testAudioStartServicePayload = [[SDLControlFramePayloadAudioStartServiceAck alloc] initWithMTU:testMTU];
+ testAudioMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testAudioHeader andPayload:testAudioStartServicePayload.data];
+ [streamingLifecycleManager handleProtocolStartServiceACKMessage:testAudioMessage];
+ });
+
+ it(@"should have set all the right properties", ^{
+ expect([[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeAudio]).to(equal(testMTU));
+ expect(streamingLifecycleManager.audioEncrypted).to(equal(YES));
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateReady));
+ });
+ });
+
+ describe(@"after receiving an Audio Start NAK", ^{
+ __block SDLProtocolHeader *testAudioHeader = nil;
+ __block SDLProtocolMessage *testAudioMessage = nil;
+
+ beforeEach(^{
+ [streamingLifecycleManager.audioStreamStateMachine setToState:SDLAudioStreamStateStarting fromOldState:nil callEnterTransition:NO];
+
+ testAudioHeader = [[SDLV2ProtocolHeader alloc] initWithVersion:5];
+ testAudioHeader.frameType = SDLFrameTypeSingle;
+ testAudioHeader.frameData = SDLFrameInfoStartServiceNACK;
+ testAudioHeader.encrypted = NO;
+ testAudioHeader.serviceType = SDLServiceTypeAudio;
+
+ testAudioMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testAudioHeader andPayload:nil];
+ [streamingLifecycleManager handleProtocolEndServiceACKMessage:testAudioMessage];
+ });
+
+ it(@"should have set all the right properties", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateStopped));
+ });
+ });
+
+ describe(@"after receiving a audio end ACK", ^{
+ __block SDLProtocolHeader *testAudioHeader = nil;
+ __block SDLProtocolMessage *testAudioMessage = nil;
+
+ beforeEach(^{
+ [streamingLifecycleManager.audioStreamStateMachine setToState:SDLAudioStreamStateStarting fromOldState:nil callEnterTransition:NO];
+
+ testAudioHeader = [[SDLV2ProtocolHeader alloc] initWithVersion:5];
+ testAudioHeader.frameType = SDLFrameTypeSingle;
+ testAudioHeader.frameData = SDLFrameInfoEndServiceACK;
+ testAudioHeader.encrypted = NO;
+ testAudioHeader.serviceType = SDLServiceTypeAudio;
+
+ testAudioMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testAudioHeader andPayload:nil];
+ [streamingLifecycleManager handleProtocolEndServiceACKMessage:testAudioMessage];
+ });
+
+ it(@"should have set all the right properties", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateStopped));
+ });
+ });
+
+ describe(@"after receiving a audio end NAK", ^{
+ __block SDLProtocolHeader *testAudioHeader = nil;
+ __block SDLProtocolMessage *testAudioMessage = nil;
+
+ beforeEach(^{
+ [streamingLifecycleManager.audioStreamStateMachine setToState:SDLAudioStreamStateStarting fromOldState:nil callEnterTransition:NO];
+
+ testAudioHeader = [[SDLV2ProtocolHeader alloc] initWithVersion:5];
+ testAudioHeader.frameType = SDLFrameTypeSingle;
+ testAudioHeader.frameData = SDLFrameInfoEndServiceNACK;
+ testAudioHeader.encrypted = NO;
+ testAudioHeader.serviceType = SDLServiceTypeAudio;
+
+ testAudioMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testAudioHeader andPayload:nil];
+ [streamingLifecycleManager handleProtocolEndServiceNAKMessage:testAudioMessage];
+ });
+
+ it(@"should have set all the right properties", ^{
+ expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamStateStopped));
+ });
+ });
+ });
+ });
+});
+
+QuickSpecEnd