summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicoleYarroch <nicole@livio.io>2018-08-16 09:13:48 -0400
committerNicoleYarroch <nicole@livio.io>2018-08-16 09:13:48 -0400
commita3f56fbe66cd6954040ee05806d5df0d418b742c (patch)
treefd868a5591b5bf48d57cf68cf6bf03ff9f9f29a9
parent50bb24d60702abe875979243b0a6ee4e0bfc8735 (diff)
parent43efffc3e883d1beee890b9dc70cf948f448a767 (diff)
downloadsdl_ios-feature/issue_599_expand_mobile_putfile.tar.gz
Merge branch 'develop' into feature/issue_599_expand_mobile_putfilefeature/issue_599_expand_mobile_putfile
Signed-off-by: NicoleYarroch <nicole@livio.io> # Conflicts: # SmartDeviceLink/SDLTCPTransport.m
-rw-r--r--SmartDeviceLink-iOS.podspec3
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj30
-rw-r--r--SmartDeviceLink.podspec1
-rw-r--r--SmartDeviceLink/SDLAudioStreamingIndicator.h36
-rw-r--r--SmartDeviceLink/SDLAudioStreamingIndicator.m9
-rw-r--r--SmartDeviceLink/SDLError.h8
-rw-r--r--SmartDeviceLink/SDLError.m39
-rw-r--r--SmartDeviceLink/SDLErrorConstants.h25
-rw-r--r--SmartDeviceLink/SDLIAPSession.m16
-rw-r--r--SmartDeviceLink/SDLIAPTransport.m41
-rw-r--r--SmartDeviceLink/SDLLogFileModuleMap.m2
-rw-r--r--SmartDeviceLink/SDLNames.h1
-rw-r--r--SmartDeviceLink/SDLNames.m1
-rw-r--r--SmartDeviceLink/SDLNotificationConstants.h1
-rw-r--r--SmartDeviceLink/SDLNotificationConstants.m1
-rw-r--r--SmartDeviceLink/SDLNotificationDispatcher.m4
-rw-r--r--SmartDeviceLink/SDLProtocol.m8
-rw-r--r--SmartDeviceLink/SDLProtocolListener.h9
-rw-r--r--SmartDeviceLink/SDLProxy.m4
-rw-r--r--SmartDeviceLink/SDLSetMediaClockTimer.h13
-rw-r--r--SmartDeviceLink/SDLSetMediaClockTimer.m18
-rw-r--r--SmartDeviceLink/SDLStreamingVideoLifecycleManager.m11
-rw-r--r--SmartDeviceLink/SDLTCPTransport.h4
-rw-r--r--SmartDeviceLink/SDLTCPTransport.m405
-rw-r--r--SmartDeviceLink/SDLTransportDelegate.h1
-rw-r--r--SmartDeviceLink/SmartDeviceLink.h1
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m11
-rw-r--r--SmartDeviceLinkTests/RPCSpecs/EnumSpecs/SDLAudioStreamingIndicatorSpec.m24
-rw-r--r--SmartDeviceLinkTests/RPCSpecs/RequestSpecs/SDLSetMediaClockTimerSpec.m8
-rw-r--r--SmartDeviceLinkTests/TestUtilities/TestTCPServer.h71
-rw-r--r--SmartDeviceLinkTests/TestUtilities/TestTCPServer.m370
-rw-r--r--SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m291
-rw-r--r--SmartDeviceLink_Example/MenuManager.m2
33 files changed, 1321 insertions, 148 deletions
diff --git a/SmartDeviceLink-iOS.podspec b/SmartDeviceLink-iOS.podspec
index 0663c8214..ddaa964d7 100644
--- a/SmartDeviceLink-iOS.podspec
+++ b/SmartDeviceLink-iOS.podspec
@@ -38,6 +38,7 @@ ss.public_header_files = [
'SmartDeviceLink/SDLAudioControlCapabilities.h',
'SmartDeviceLink/SDLAudioPassThruCapabilities.h',
'SmartDeviceLink/SDLAudioStreamingState.h',
+'SmartDeviceLink/SDLAudioStreamingIndicator.h',
'SmartDeviceLink/SDLAudioStreamManager.h',
'SmartDeviceLink/SDLAudioStreamManagerDelegate.h',
'SmartDeviceLink/SDLStreamingAudioManagerType.h',
@@ -101,7 +102,7 @@ ss.public_header_files = [
'SmartDeviceLink/SDLEncodedSyncPDataResponse.h',
'SmartDeviceLink/SDLEndAudioPassThru.h',
'SmartDeviceLink/SDLEndAudioPassThruResponse.h',
-'SmartDeviceLink/SDLEqualizerSettings.h'
+'SmartDeviceLink/SDLEqualizerSettings.h',
'SmartDeviceLink/SDLEnum.h',
'SmartDeviceLink/SDLErrorConstants.h',
'SmartDeviceLink/SDLFile.h',
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index e7f2bcf08..e75e5a10c 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -380,6 +380,9 @@
1FF7DABA1F75B2A800B46C30 /* SDLFocusableItemLocator.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FF7DAB91F75B2A800B46C30 /* SDLFocusableItemLocator.h */; };
1FF7DABC1F75B2BF00B46C30 /* SDLFocusableItemLocator.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FF7DABB1F75B2BF00B46C30 /* SDLFocusableItemLocator.m */; };
1FF7DAC01F75CF6C00B46C30 /* SDLHapticManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FF7DABF1F75CF6C00B46C30 /* SDLHapticManagerSpec.m */; };
+ 2BF2F84F20ED004000A26EF2 /* SDLAudioStreamingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 2BF2F84D20ED004000A26EF2 /* SDLAudioStreamingIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 2BF2F85020ED004000A26EF2 /* SDLAudioStreamingIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BF2F84E20ED004000A26EF2 /* SDLAudioStreamingIndicator.m */; };
+ 2BF2F85220ED068200A26EF2 /* SDLAudioStreamingIndicatorSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BF2F85120ED068200A26EF2 /* SDLAudioStreamingIndicatorSpec.m */; };
332A914F1CED9CC60043824C /* SDLAppInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 332A913D1CED87F80043824C /* SDLAppInfo.m */; };
332A91501CED9CF10043824C /* SDLAppInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 332A913C1CED87F80043824C /* SDLAppInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
5D00AC671F140F0A004000D9 /* SDLSystemCapabilityType.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D00AC651F140F0A004000D9 /* SDLSystemCapabilityType.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -1358,6 +1361,8 @@
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 */; };
+ 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 */; };
EED5CA001F4D18DC00F04000 /* SDLRAWH264Packetizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EED5C9FF1F4D18DC00F04000 /* SDLRAWH264Packetizer.h */; };
EED5CA021F4D18EC00F04000 /* SDLRAWH264Packetizer.m in Sources */ = {isa = PBXBuildFile; fileRef = EED5CA011F4D18EC00F04000 /* SDLRAWH264Packetizer.m */; };
@@ -1825,6 +1830,9 @@
1FF7DAB91F75B2A800B46C30 /* SDLFocusableItemLocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLFocusableItemLocator.h; sourceTree = "<group>"; };
1FF7DABB1F75B2BF00B46C30 /* SDLFocusableItemLocator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLFocusableItemLocator.m; sourceTree = "<group>"; };
1FF7DABF1F75CF6C00B46C30 /* SDLHapticManagerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLHapticManagerSpec.m; path = ProxySpecs/SDLHapticManagerSpec.m; sourceTree = "<group>"; };
+ 2BF2F84D20ED004000A26EF2 /* SDLAudioStreamingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLAudioStreamingIndicator.h; sourceTree = "<group>"; };
+ 2BF2F84E20ED004000A26EF2 /* SDLAudioStreamingIndicator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLAudioStreamingIndicator.m; sourceTree = "<group>"; };
+ 2BF2F85120ED068200A26EF2 /* SDLAudioStreamingIndicatorSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLAudioStreamingIndicatorSpec.m; sourceTree = "<group>"; };
332A913C1CED87F80043824C /* SDLAppInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLAppInfo.h; sourceTree = "<group>"; };
332A913D1CED87F80043824C /* SDLAppInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLAppInfo.m; sourceTree = "<group>"; };
5D00AC651F140F0A004000D9 /* SDLSystemCapabilityType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLSystemCapabilityType.h; sourceTree = "<group>"; };
@@ -2848,6 +2856,9 @@
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>"; };
+ 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>"; };
EED5C9FD1F4D18D100F04000 /* SDLH264Packetizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLH264Packetizer.h; sourceTree = "<group>"; };
EED5C9FF1F4D18DC00F04000 /* SDLRAWH264Packetizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLRAWH264Packetizer.h; sourceTree = "<group>"; };
EED5CA011F4D18EC00F04000 /* SDLRAWH264Packetizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLRAWH264Packetizer.m; sourceTree = "<group>"; };
@@ -2924,6 +2935,7 @@
162E81E21A9BDE8A00906325 /* SDLAmbientLightStatusSpec.m */,
162E81E31A9BDE8A00906325 /* SDLAppHMITypeSpec.m */,
162E81E41A9BDE8A00906325 /* SDLAppInterfaceUnregisteredReasonSpec.m */,
+ 2BF2F85120ED068200A26EF2 /* SDLAudioStreamingIndicatorSpec.m */,
162E81E51A9BDE8A00906325 /* SDLAudioStreamingStateSpec.m */,
162E81E61A9BDE8A00906325 /* SDLAudioTypeSpec.m */,
162E81E71A9BDE8A00906325 /* SDLBitsPerSampleSpec.m */,
@@ -4154,6 +4166,8 @@
5D61FA551A84238A00846EE7 /* SDLAppHMIType.m */,
5D61FA561A84238A00846EE7 /* SDLAppInterfaceUnregisteredReason.h */,
5D61FA571A84238A00846EE7 /* SDLAppInterfaceUnregisteredReason.m */,
+ 2BF2F84D20ED004000A26EF2 /* SDLAudioStreamingIndicator.h */,
+ 2BF2F84E20ED004000A26EF2 /* SDLAudioStreamingIndicator.m */,
5D61FA5A1A84238A00846EE7 /* SDLAudioStreamingState.h */,
5D61FA5B1A84238A00846EE7 /* SDLAudioStreamingState.m */,
5D61FA5C1A84238A00846EE7 /* SDLAudioType.h */,
@@ -4546,6 +4560,8 @@
5DB1BCE21D2455FD002FFC37 /* Connection Manager */,
5D6035D0202CD46200A429C9 /* SDLSpecUtilities.h */,
5D6035D1202CD46200A429C9 /* SDLSpecUtilities.m */,
+ EEA41D43210BA89B0006CB6E /* TestTCPServer.h */,
+ EEA41D44210BA8CF0006CB6E /* TestTCPServer.m */,
);
name = "Test Utilities";
sourceTree = "<group>";
@@ -4603,6 +4619,7 @@
5D59DD451B14FDD000BE744D /* ProxySpecs */,
5DB92D201AC47AC400C15BB0 /* UtilitiesSpecs */,
1680B1041A9CD7AD00DBD79E /* ProtocolSpecs */,
+ EE5D1B31208EBC7100D17216 /* TransportSpecs */,
162E81E01A9BDE8A00906325 /* RPCSpecs */,
5D61FA2D1A84237100846EE7 /* Supporting Files */,
167ED9451A9BCE5D00797BE5 /* SwiftSpec.swift */,
@@ -5572,6 +5589,14 @@
name = "@categories";
sourceTree = "<group>";
};
+ EE5D1B31208EBC7100D17216 /* TransportSpecs */ = {
+ isa = PBXGroup;
+ children = (
+ EE5D1B32208EBCA900D17216 /* SDLTCPTransportSpec.m */,
+ );
+ path = TransportSpecs;
+ sourceTree = "<group>";
+ };
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@@ -5590,6 +5615,7 @@
1EB59CA3202D92F600343A61 /* SDLMassageMode.h in Headers */,
1EB59CAF202D97AC00343A61 /* SDLMassageCushionFirmness.h in Headers */,
1EAA470B2032BAE5000FE74B /* SDLOnRCStatus.h in Headers */,
+ 2BF2F84F20ED004000A26EF2 /* SDLAudioStreamingIndicator.h in Headers */,
1EAA475120356CD2000FE74B /* SDLDistanceUnit.h in Headers */,
1EAA4755203583BC000FE74B /* SDLHMISettingsControlData.h in Headers */,
1EAA4735203458CE000FE74B /* SDLSRGBColor.h in Headers */,
@@ -6588,6 +6614,7 @@
5DAB5F572098E5D100A020C8 /* SDLProtocolConstants.m in Sources */,
5D61FC9F1A84238C00846EE7 /* SDLEncodedSyncPData.m in Sources */,
5D61FE061A84238C00846EE7 /* SDLVehicleDataResultCode.m in Sources */,
+ 2BF2F85020ED004000A26EF2 /* SDLAudioStreamingIndicator.m in Sources */,
5D61FCA41A84238C00846EE7 /* SDLEndAudioPassThru.m in Sources */,
5D339CEB207C066E000CC364 /* SDLMenuCell.m in Sources */,
8818ADD92100FC18007D6F19 /* SDLTurnSignal.m in Sources */,
@@ -6963,6 +6990,7 @@
162E82F01A9BDE8B00906325 /* SDLPowerModeQualificationStatusSpec.m in Sources */,
162E82CD1A9BDE8A00906325 /* SDLAudioStreamingStateSpec.m in Sources */,
1EE8C4461F3837D200FDC2CF /* SDLModuleDataSpec.m in Sources */,
+ EEA41D45210BA8CF0006CB6E /* TestTCPServer.m in Sources */,
1E89B0DE2031636000A47992 /* SDLSeatControlDataSpec.m in Sources */,
162E831A1A9BDE8B00906325 /* SDLOnLockScreenStatusSpec.m in Sources */,
162E83431A9BDE8B00906325 /* SDLSyncPDataSpec.m in Sources */,
@@ -7033,6 +7061,7 @@
5D0A9F911F15550400CC80DD /* SDLSystemCapabilityTypeSpec.m in Sources */,
5DBF0D601F3B3DB4008AF2C9 /* SDLControlFrameVideoStartServiceAckSpec.m in Sources */,
162E83311A9BDE8B00906325 /* SDLListFilesSpec.m in Sources */,
+ EE5D1B33208EBCA900D17216 /* SDLTCPTransportSpec.m in Sources */,
1EAA477C2036BD6D000FE74B /* SDLSDLSRGBColorSpec.m in Sources */,
DA9F7EB01DCC063400ACAE48 /* SDLLocationDetailsSpec.m in Sources */,
5DC978261B7A38640012C2F1 /* SDLGlobalsSpec.m in Sources */,
@@ -7113,6 +7142,7 @@
162E83731A9BDE8B00906325 /* SDLBeltStatusSpec.m in Sources */,
162E83551A9BDE8B00906325 /* SDLEndAudioPassThruResponseSpec.m in Sources */,
162E83251A9BDE8B00906325 /* SDLAlertSpec.m in Sources */,
+ 2BF2F85220ED068200A26EF2 /* SDLAudioStreamingIndicatorSpec.m in Sources */,
5D6035D2202CD46200A429C9 /* SDLSpecUtilities.m in Sources */,
DA9F7EA81DCC060B00ACAE48 /* SDLGetWaypointsResponseSpec.m in Sources */,
162E830A1A9BDE8B00906325 /* SDLVehicleDataTypeSpec.m in Sources */,
diff --git a/SmartDeviceLink.podspec b/SmartDeviceLink.podspec
index 1211b4f9b..c37e56f27 100644
--- a/SmartDeviceLink.podspec
+++ b/SmartDeviceLink.podspec
@@ -38,6 +38,7 @@ ss.public_header_files = [
'SmartDeviceLink/SDLAudioControlCapabilities.h',
'SmartDeviceLink/SDLAudioPassThruCapabilities.h',
'SmartDeviceLink/SDLAudioStreamingState.h',
+'SmartDeviceLink/SDLAudioStreamingIndicator.h',
'SmartDeviceLink/SDLAudioStreamManager.h',
'SmartDeviceLink/SDLAudioStreamManagerDelegate.h',
'SmartDeviceLink/SDLStreamingAudioManagerType.h',
diff --git a/SmartDeviceLink/SDLAudioStreamingIndicator.h b/SmartDeviceLink/SDLAudioStreamingIndicator.h
new file mode 100644
index 000000000..61590002f
--- /dev/null
+++ b/SmartDeviceLink/SDLAudioStreamingIndicator.h
@@ -0,0 +1,36 @@
+// SDLAudioStreamingIndicator.h
+//
+
+#import "SDLEnum.h"
+
+
+/**
+ * Enumeration listing possible indicators of audio streaming changes
+ *
+ * @since SDL 4.6
+ */
+typedef SDLEnum SDLAudioStreamingIndicator SDL_SWIFT_ENUM;
+
+/**
+ * Default playback indicator.
+ * By default the playback indicator should be PLAY_PAUSE when:
+ * - the media app is newly registered on the head unit (after RegisterAppInterface)
+ * - the media app was closed by the user (App enteres HMI_NONE)
+ * - the app sends SetMediaClockTimer with audioStreamingIndicator not set to any value
+ */
+extern SDLAudioStreamingIndicator const SDLAudioStreamingIndicatorPlayPause;
+
+/**
+ * Indicates that a button press of the Play/Pause button starts the audio playback.
+ */
+extern SDLAudioStreamingIndicator const SDLAudioStreamingIndicatorPlay;
+
+/**
+ * Indicates that a button press of the Play/Pause button pauses the current audio playback.
+ */
+extern SDLAudioStreamingIndicator const SDLAudioStreamingIndicatorPause;
+
+/**
+ * Indicates that a button press of the Play/Pause button stops the current audio playback.
+ */
+extern SDLAudioStreamingIndicator const SDLAudioStreamingIndicatorStop;
diff --git a/SmartDeviceLink/SDLAudioStreamingIndicator.m b/SmartDeviceLink/SDLAudioStreamingIndicator.m
new file mode 100644
index 000000000..5e4a328a2
--- /dev/null
+++ b/SmartDeviceLink/SDLAudioStreamingIndicator.m
@@ -0,0 +1,9 @@
+// SDLAudioStreamingIndicator.m
+//
+
+#import "SDLAudioStreamingIndicator.h"
+
+SDLAudioStreamingIndicator const SDLAudioStreamingIndicatorPlayPause = @"PLAY_PAUSE";
+SDLAudioStreamingIndicator const SDLAudioStreamingIndicatorPlay = @"PLAY";
+SDLAudioStreamingIndicator const SDLAudioStreamingIndicatorPause = @"PAUSE";
+SDLAudioStreamingIndicator const SDLAudioStreamingIndicatorStop = @"STOP";
diff --git a/SmartDeviceLink/SDLError.h b/SmartDeviceLink/SDLError.h
index 9711d8e51..4a0cbd7ca 100644
--- a/SmartDeviceLink/SDLError.h
+++ b/SmartDeviceLink/SDLError.h
@@ -23,6 +23,7 @@ extern SDLErrorDomain *const SDLErrorDomainTextAndGraphicManager;
extern SDLErrorDomain *const SDLErrorDomainSoftButtonManager;
extern SDLErrorDomain *const SDLErrorDomainMenuManager;
extern SDLErrorDomain *const SDLErrorDomainChoiceSetManager;
+extern SDLErrorDomain *const SDLErrorDomainTransport;
@interface NSError (SDLErrors)
@@ -65,6 +66,13 @@ extern SDLErrorDomain *const SDLErrorDomainChoiceSetManager;
+ (NSError *)sdl_choiceSetManager_choiceDeletionFailed:(NSDictionary *)userInfo;
+ (NSError *)sdl_choiceSetManager_choiceUploadFailed:(NSDictionary *)userInfo;
+#pragma mark Transport
+
++ (NSError *)sdl_transport_unknownError;
++ (NSError *)sdl_transport_connectionRefusedError;
++ (NSError *)sdl_transport_connectionTimedOutError;
++ (NSError *)sdl_transport_networkDownError;
+
@end
@interface NSException (SDLExceptions)
diff --git a/SmartDeviceLink/SDLError.m b/SmartDeviceLink/SDLError.m
index 4f067339b..2597a563d 100644
--- a/SmartDeviceLink/SDLError.m
+++ b/SmartDeviceLink/SDLError.m
@@ -18,6 +18,7 @@ SDLErrorDomain *const SDLErrorDomainTextAndGraphicManager = @"com.sdl.textandgra
SDLErrorDomain *const SDLErrorDomainSoftButtonManager = @"com.sdl.softbuttonmanager.error";
SDLErrorDomain *const SDLErrorDomainMenuManager = @"com.sdl.menumanager.error";
SDLErrorDomain *const SDLErrorDomainChoiceSetManager = @"com.sdl.choicesetmanager.error";
+SDLErrorDomain *const SDLErrorDomainTransport = @"com.sdl.transport.error";
@implementation NSError (SDLErrors)
@@ -217,6 +218,44 @@ SDLErrorDomain *const SDLErrorDomainChoiceSetManager = @"com.sdl.choicesetmanage
return [NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorUploadFailed userInfo:userInfo];
}
+#pragma mark Transport
+
++ (NSError *)sdl_transport_unknownError {
+ NSDictionary<NSString *, NSString *> *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"TCP connection error", nil),
+ NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"TCP connection cannot be established due to unknown error.", nil),
+ NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure that correct IP address and TCP port number are specified, and the phone is connected to the correct Wi-Fi network.", nil)
+ };
+ return [NSError errorWithDomain:SDLErrorDomainTransport code:SDLTransportErrorUnknown userInfo:userInfo];
+}
+
++ (NSError *)sdl_transport_connectionRefusedError {
+ NSDictionary<NSString *, NSString *> *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"TCP connection cannot be established", nil),
+ NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The TCP connection is refused by head unit. Possible causes are that the specified TCP port number is not correct, or SDL Core is not running properly on the head unit.", nil),
+ NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure that correct IP address and TCP port number are specified. Also, make sure that SDL Core on the head unit enables TCP transport.", nil)
+ };
+ return [NSError errorWithDomain:SDLErrorDomainTransport code:SDLTransportErrorConnectionRefused userInfo:userInfo];
+}
+
++ (NSError *)sdl_transport_connectionTimedOutError {
+ NSDictionary<NSString *, NSString *> *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"TCP connection timed out", nil),
+ NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The TCP connection cannot be established within a given time. Possible causes are that the specified IP address is not correct, or the connection is blocked by a firewall.", nil),
+ NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure that correct IP address and TCP port number are specified. Also, make sure that the head unit's system configuration accepts TCP connections.", nil)
+ };
+ return [NSError errorWithDomain:SDLErrorDomainTransport code:SDLTransportErrorConnectionTimedOut userInfo:userInfo];
+}
+
++ (NSError *)sdl_transport_networkDownError {
+ NSDictionary<NSString *, NSString *> *userInfo = @{
+ NSLocalizedDescriptionKey: NSLocalizedString(@"Network is not available", nil),
+ NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"TCP connection cannot be established because the phone is not connected to the network. Possible causes are: Wi-Fi being disabled on the phone or the phone is connected to a wrong Wi-Fi network.", nil),
+ NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Make sure that the phone is connected to the Wi-Fi network that has the head unit on it. Also, make sure that correct IP address and TCP port number are specified.", nil)
+ };
+ return [NSError errorWithDomain:SDLErrorDomainTransport code:SDLTransportErrorNetworkDown userInfo:userInfo];
+}
+
@end
diff --git a/SmartDeviceLink/SDLErrorConstants.h b/SmartDeviceLink/SDLErrorConstants.h
index 260203c95..8503d8be8 100644
--- a/SmartDeviceLink/SDLErrorConstants.h
+++ b/SmartDeviceLink/SDLErrorConstants.h
@@ -120,3 +120,28 @@ typedef NS_ENUM(NSInteger, SDLChoiceSetManagerError) {
SDLChoiceSetManagerErrorDeletionFailed = -2,
SDLChoiceSetManagerErrorUploadFailed = -3,
};
+
+/**
+ * Errors associated with transport.
+ */
+typedef NS_ENUM(NSInteger, SDLTransportError) {
+ /**
+ * Connection cannot be established due to a reason not listed here.
+ */
+ SDLTransportErrorUnknown = -1,
+ /**
+ * TCP connection is refused.
+ * Probably specified port number is invalid, or SDL Core is not running on the head unit.
+ */
+ SDLTransportErrorConnectionRefused = -2,
+ /**
+ * TCP connection cannot be established within given time.
+ * Probably because of wrong IP address, or the connection may be blocked by a firewall.
+ */
+ SDLTransportErrorConnectionTimedOut = -3,
+ /**
+ * TCP connection cannot be established since network is down.
+ * Probably the phone is not connected to the correct network.
+ */
+ SDLTransportErrorNetworkDown = -4,
+};
diff --git a/SmartDeviceLink/SDLIAPSession.m b/SmartDeviceLink/SDLIAPSession.m
index 47047ece9..98cc35813 100644
--- a/SmartDeviceLink/SDLIAPSession.m
+++ b/SmartDeviceLink/SDLIAPSession.m
@@ -11,7 +11,7 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const IOStreamThreadName = @"com.smartdevicelink.iostream";
-NSTimeInterval const StreamThreadWaitSecs = 1.0;
+NSTimeInterval const StreamThreadWaitSecs = 10.0;
@interface SDLIAPSession ()
@@ -80,7 +80,6 @@ NSTimeInterval const StreamThreadWaitSecs = 1.0;
}
- (void)stop {
- // This method must be called on the main thread
if ([NSThread isMainThread]) {
[self sdl_stop];
} else {
@@ -91,6 +90,7 @@ NSTimeInterval const StreamThreadWaitSecs = 1.0;
}
- (void)sdl_stop {
+ NSAssert(NSThread.isMainThread, @"%@ must only be called on the main thread", NSStringFromSelector(_cmd));
if (self.isDataSession) {
[self.ioStreamThread cancel];
@@ -137,6 +137,9 @@ NSTimeInterval const StreamThreadWaitSecs = 1.0;
if (bytesWritten < 0) {
if (ostream.streamError != nil) {
[self sdl_handleOutputStreamWriteError:ostream.streamError];
+ } else {
+ // The write operation failed but there is no further information about the error. This can occur when disconnecting from an external accessory.
+ SDLLogE(@"Output stream write operation failed");
}
} else if (bytesWritten == bytesRemaining) {
// Remove the data from the queue
@@ -173,13 +176,10 @@ NSTimeInterval const StreamThreadWaitSecs = 1.0;
[self startStream:self.easession.outputStream];
SDLLogD(@"Starting the accessory event loop");
- do {
- if (self.sendDataQueue.count > 0 && !self.sendDataQueue.frontDequeued) {
- [self sdl_dequeueAndWriteToOutputStream];
- }
- // The principle here is to give the event loop enough time to process stream events while also allowing it to handle new enqueued data buffers in a timely manner. We're capping the run loop CPU time at 0.25s maximum before it will return control to the rest of the loop.
+ while (!NSThread.currentThread.cancelled) {
+ // Enqueued data will be written to and read from the streams in the runloop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25f]];
- } while (![NSThread currentThread].cancelled);
+ }
SDLLogD(@"Closing the accessory session for id: %tu, name: %@", self.easession.accessory.connectionID, self.easession.accessory.name);
diff --git a/SmartDeviceLink/SDLIAPTransport.m b/SmartDeviceLink/SDLIAPTransport.m
index df623d980..5283ae652 100644
--- a/SmartDeviceLink/SDLIAPTransport.m
+++ b/SmartDeviceLink/SDLIAPTransport.m
@@ -549,28 +549,33 @@ int const ProtocolIndexTimeoutSeconds = 10;
- (SDLStreamEndHandler)sdl_dataStreamEndedHandler {
__weak typeof(self) weakSelf = self;
-
return ^(NSStream *stream) {
+ NSAssert(!NSThread.isMainThread, @"%@ should only be called on the IO thread", NSStringFromSelector(_cmd));
__strong typeof(weakSelf) strongSelf = weakSelf;
+
SDLLogD(@"Data stream ended");
- if (strongSelf.session != nil) {
- // The handler will be called on the IO thread, but the session stop method must be called on the main thread and we need to wait for the session to stop before nil'ing it out. To do this, we use dispatch_sync() on the main thread.
- dispatch_sync(dispatch_get_main_queue(), ^{
- [strongSelf.session stop];
- });
+ if (strongSelf.session == nil) {
+ SDLLogD(@"Data session is nil");
+ return;
+ }
+ // The handler will be called on the IO thread, but the session stop method must be called on the main thread
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [strongSelf.session stop];
strongSelf.session.streamDelegate = nil;
strongSelf.session = nil;
- }
- // We don't call sdl_retryEstablishSession here because the stream end event usually fires when the accessory is disconnected
+
+ // We don't call sdl_retryEstablishSession here because the stream end event usually fires when the accessory is disconnected
+ });
+
+ // To prevent deadlocks the handler must return to the runloop and not block the thread
};
}
- (SDLStreamHasBytesHandler)sdl_dataStreamHasBytesHandler {
__weak typeof(self) weakSelf = self;
-
return ^(NSInputStream *istream) {
+ NSAssert(!NSThread.isMainThread, @"%@ should only be called on the IO thread", NSStringFromSelector(_cmd));
__strong typeof(weakSelf) strongSelf = weakSelf;
-
uint8_t buf[[[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC]];
while (istream.streamStatus == NSStreamStatusOpen && istream.hasBytesAvailable) {
// It is necessary to check the stream status and whether there are bytes available because the dataStreamHasBytesHandler is executed on the IO thread and the accessory disconnect notification arrives on the main thread, causing data to be passed to the delegate while the main thread is tearing down the transport.
@@ -597,18 +602,20 @@ int const ProtocolIndexTimeoutSeconds = 10;
- (SDLStreamErrorHandler)sdl_dataStreamErroredHandler {
__weak typeof(self) weakSelf = self;
-
return ^(NSStream *stream) {
+ NSAssert(!NSThread.isMainThread, @"%@ should only be called on the IO thread", NSStringFromSelector(_cmd));
__strong typeof(weakSelf) strongSelf = weakSelf;
SDLLogE(@"Data stream error");
- dispatch_sync(dispatch_get_main_queue(), ^{
+ dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf.session stop];
+ strongSelf.session.streamDelegate = nil;
+ strongSelf.session = nil;
+ if (![LegacyProtocolString isEqualToString:strongSelf.session.protocol]) {
+ [strongSelf sdl_retryEstablishSession];
+ }
});
- strongSelf.session.streamDelegate = nil;
- strongSelf.session = nil;
- if (![LegacyProtocolString isEqualToString:strongSelf.session.protocol]) {
- [strongSelf sdl_retryEstablishSession];
- }
+
+ // To prevent deadlocks the handler must return to the runloop and not block the thread
};
}
diff --git a/SmartDeviceLink/SDLLogFileModuleMap.m b/SmartDeviceLink/SDLLogFileModuleMap.m
index 218ffa42c..c9aa4150b 100644
--- a/SmartDeviceLink/SDLLogFileModuleMap.m
+++ b/SmartDeviceLink/SDLLogFileModuleMap.m
@@ -85,7 +85,7 @@
}
+ (SDLLogFileModule *)sdl_screenManagerChoiceSetModule {
- return [SDLLogFileModule moduleWithName:@"Screen/ChoiceSet" files:[NSSet setWithArray:@[@"SDLChoiceSetManager", @"SDLCheckChoiceVROptionalOperation", @"SDLDeleteChoicesOperation", @"SDLPreloadChoicesOperation", @"SDLPresentChoiceSetOperation", @"SDLPresentKeyboardOperation"]]];
+ return [SDLLogFileModule moduleWithName:@"Screen/ChoiceSet" files:[NSSet setWithArray:@[@"SDLChoiceSetManager", @"SDLCheckChoiceVROptionalOperation", @"SDLDeleteChoicesOperation", @"SDLPreloadChoicesOperation", @"SDLPresentChoiceSetOperation", @"SDLPresentKeyboardOperation", @"SDLChoiceSet"]]];
}
diff --git a/SmartDeviceLink/SDLNames.h b/SmartDeviceLink/SDLNames.h
index 959c76295..c1c1466ae 100644
--- a/SmartDeviceLink/SDLNames.h
+++ b/SmartDeviceLink/SDLNames.h
@@ -45,6 +45,7 @@ extern SDLName const SDLNameAudioControlData;
extern SDLName const SDLNameAudioPassThruCapabilities;
extern SDLName const SDLNameAudioPassThruDisplayText1;
extern SDLName const SDLNameAudioPassThruDisplayText2;
+extern SDLName const SDLNameAudioStreamingIndicator;
extern SDLName const SDLNameAudioStreamingState;
extern SDLName const SDLNameAudioType;
extern SDLName const SDLNameAutoCompleteText;
diff --git a/SmartDeviceLink/SDLNames.m b/SmartDeviceLink/SDLNames.m
index 8dcfa3c0d..43ff54d33 100644
--- a/SmartDeviceLink/SDLNames.m
+++ b/SmartDeviceLink/SDLNames.m
@@ -43,6 +43,7 @@ SDLName const SDLNameAudioControlData = @"audioControlData";
SDLName const SDLNameAudioPassThruCapabilities = @"audioPassThruCapabilities";
SDLName const SDLNameAudioPassThruDisplayText1 = @"audioPassThruDisplayText1";
SDLName const SDLNameAudioPassThruDisplayText2 = @"audioPassThruDisplayText2";
+SDLName const SDLNameAudioStreamingIndicator = @"audioStreamingIndicator";
SDLName const SDLNameAudioStreamingState = @"audioStreamingState";
SDLName const SDLNameAudioType = @"audioType";
SDLName const SDLNameAutoCompleteText = @"autoCompleteText";
diff --git a/SmartDeviceLink/SDLNotificationConstants.h b/SmartDeviceLink/SDLNotificationConstants.h
index 1a0133118..a2a371d3f 100644
--- a/SmartDeviceLink/SDLNotificationConstants.h
+++ b/SmartDeviceLink/SDLNotificationConstants.h
@@ -106,6 +106,7 @@ extern SDLNotificationUserInfoKey const SDLNotificationUserInfoObject;
#pragma mark - General notifications
extern SDLNotificationName const SDLTransportDidDisconnect;
extern SDLNotificationName const SDLTransportDidConnect;
+extern SDLNotificationName const SDLTransportConnectError;
extern SDLNotificationName const SDLDidReceiveError;
extern SDLNotificationName const SDLDidReceiveLockScreenIcon;
extern SDLNotificationName const SDLDidBecomeReady;
diff --git a/SmartDeviceLink/SDLNotificationConstants.m b/SmartDeviceLink/SDLNotificationConstants.m
index 7bda755a1..35f12b473 100644
--- a/SmartDeviceLink/SDLNotificationConstants.m
+++ b/SmartDeviceLink/SDLNotificationConstants.m
@@ -16,6 +16,7 @@ SDLNotificationUserInfoKey const SDLNotificationUserInfoObject = @"SDLNotificati
#pragma mark - General notifications
SDLNotificationName const SDLTransportDidDisconnect = @"com.sdl.transport.disconnect";
SDLNotificationName const SDLTransportDidConnect = @"com.sdl.transport.connect";
+SDLNotificationName const SDLTransportConnectError = @"com.sdl.transport.connectError";
SDLNotificationName const SDLDidReceiveError = @"com.sdl.general.error";
SDLNotificationName const SDLDidReceiveLockScreenIcon = @"com.sdl.general.lockscreenIconReceived";
SDLNotificationName const SDLDidBecomeReady = @"com.sdl.notification.managerReady";
diff --git a/SmartDeviceLink/SDLNotificationDispatcher.m b/SmartDeviceLink/SDLNotificationDispatcher.m
index d28842852..f2a4b6950 100644
--- a/SmartDeviceLink/SDLNotificationDispatcher.m
+++ b/SmartDeviceLink/SDLNotificationDispatcher.m
@@ -59,6 +59,10 @@ NS_ASSUME_NONNULL_BEGIN
[self postNotificationName:SDLTransportDidDisconnect infoObject:nil];
}
+- (void)onTransportError:(NSError *)error {
+ [self postNotificationName:SDLTransportConnectError infoObject:error];
+}
+
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincompatible-pointer-types"
diff --git a/SmartDeviceLink/SDLProtocol.m b/SmartDeviceLink/SDLProtocol.m
index 89bd5aeb8..405fdd3bc 100644
--- a/SmartDeviceLink/SDLProtocol.m
+++ b/SmartDeviceLink/SDLProtocol.m
@@ -106,6 +106,14 @@ NS_ASSUME_NONNULL_BEGIN
[self handleBytesFromTransport:receivedData];
}
+- (void)onError:(NSError *)error {
+ for (id<SDLProtocolListener> listener in self.protocolDelegateTable.allObjects) {
+ if ([listener respondsToSelector:@selector(onTransportError:)]) {
+ [listener onTransportError:error];
+ }
+ }
+}
+
#pragma mark - Start Service
- (void)startServiceWithType:(SDLServiceType)serviceType payload:(nullable NSData *)payload {
diff --git a/SmartDeviceLink/SDLProtocolListener.h b/SmartDeviceLink/SDLProtocolListener.h
index 35d6a7623..90685a46c 100644
--- a/SmartDeviceLink/SDLProtocolListener.h
+++ b/SmartDeviceLink/SDLProtocolListener.h
@@ -80,6 +80,15 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)onError:(NSString *)info exception:(NSException *)e;
+/**
+ * Called when an error is notified from transport.
+ *
+ * Note: currently, this is used only by TCP transport.
+ *
+ * @param error The type of the error
+ */
+- (void)onTransportError:(NSError *)error;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLProxy.m b/SmartDeviceLink/SDLProxy.m
index 69ee0618f..5c78bc68b 100644
--- a/SmartDeviceLink/SDLProxy.m
+++ b/SmartDeviceLink/SDLProxy.m
@@ -243,6 +243,10 @@ static float DefaultConnectionTimeout = 45.0;
[self invokeMethodOnDelegates:@selector(onError:) withObject:e];
}
+- (void)onTransportError:(NSError *)error {
+ [self invokeMethodOnDelegates:@selector(onTransportError:) withObject:error];
+}
+
- (void)handleProtocolStartServiceACKMessage:(SDLProtocolMessage *)startServiceACK {
// Turn off the timer, the start session response came back
[self.startSessionTimer cancel];
diff --git a/SmartDeviceLink/SDLSetMediaClockTimer.h b/SmartDeviceLink/SDLSetMediaClockTimer.h
index bc26a8ed9..db415916f 100644
--- a/SmartDeviceLink/SDLSetMediaClockTimer.h
+++ b/SmartDeviceLink/SDLSetMediaClockTimer.h
@@ -4,6 +4,7 @@
#import "SDLRPCRequest.h"
#import "SDLUpdateMode.h"
+#import "SDLAudioStreamingIndicator.h"
@class SDLStartTime;
@@ -22,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface SDLSetMediaClockTimer : SDLRPCRequest
+- (instancetype)initWithUpdateMode:(SDLUpdateMode)updateMode hours:(UInt8)hours minutes:(UInt8)minutes seconds:(UInt8)seconds audioStreamingIndicator:(SDLAudioStreamingIndicator)audioStreamingIndicator;
+
- (instancetype)initWithUpdateMode:(SDLUpdateMode)updateMode hours:(UInt8)hours minutes:(UInt8)minutes seconds:(UInt8)seconds;
- (instancetype)initWithUpdateMode:(SDLUpdateMode)updateMode;
@@ -60,6 +63,16 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (strong, nonatomic) SDLUpdateMode updateMode;
+/**
+ * The audio streaming indicator used for a play/pause button.
+ *
+ * @discussion Set the indicator icon of a play/pause button depending on the
+ * current audio playback. This parameter is optional. If omitted the last indicator sent
+ * will not change.
+ */
+@property (strong, nonatomic, nullable) SDLAudioStreamingIndicator audioStreamingIndicator;
+
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLSetMediaClockTimer.m b/SmartDeviceLink/SDLSetMediaClockTimer.m
index 793fd1493..ac14c215e 100644
--- a/SmartDeviceLink/SDLSetMediaClockTimer.m
+++ b/SmartDeviceLink/SDLSetMediaClockTimer.m
@@ -18,6 +18,16 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+- (instancetype)initWithUpdateMode:(SDLUpdateMode)updateMode hours:(UInt8)hours minutes:(UInt8)minutes seconds:(UInt8)seconds audioStreamingIndicator:(SDLAudioStreamingIndicator)audioStreamingIndicator {
+ self = [self initWithUpdateMode:updateMode hours:hours minutes:minutes seconds:seconds];
+ if (!self) {
+ return nil;
+ }
+
+ self.audioStreamingIndicator = audioStreamingIndicator;
+
+ return self;
+}
- (instancetype)initWithUpdateMode:(SDLUpdateMode)updateMode hours:(UInt8)hours minutes:(UInt8)minutes seconds:(UInt8)seconds {
self = [self initWithUpdateMode:updateMode];
@@ -65,6 +75,14 @@ NS_ASSUME_NONNULL_BEGIN
return [parameters sdl_objectForName:SDLNameUpdateMode];
}
+- (void)setAudioStreamingIndicator:(nullable SDLAudioStreamingIndicator)audioStreamingIndicator {
+ [parameters sdl_setObject:audioStreamingIndicator forName:SDLNameAudioStreamingIndicator];
+}
+
+- (nullable SDLAudioStreamingIndicator)audioStreamingIndicator {
+ return [parameters sdl_objectForName:SDLNameAudioStreamingIndicator];
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m b/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m
index 8c69b3c55..b95d3fc9d 100644
--- a/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m
+++ b/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m
@@ -208,6 +208,10 @@ typedef void(^SDLVideoCapabilityResponseHandler)(SDLVideoStreamingCapability *_N
return [self.videoStreamStateMachine isCurrentState:SDLVideoStreamManagerStateReady];
}
+- (BOOL)isVideoSuspended {
+ return [self.videoStreamStateMachine isCurrentState:SDLVideoStreamManagerStateSuspended];
+}
+
- (BOOL)isVideoStreamingPaused {
return !(self.isVideoConnected && self.isHmiStateVideoStreamCapable && self.isAppStateVideoStreamCapable);
}
@@ -250,7 +254,7 @@ typedef void(^SDLVideoCapabilityResponseHandler)(SDLVideoStreamingCapability *_N
[self sdl_sendBackgroundFrames];
[self.touchManager cancelPendingTouches];
- if ([self.videoStreamStateMachine.currentState isEqualToString:SDLVideoStreamManagerStateReady]) {
+ if (self.isVideoConnected) {
[self.videoStreamStateMachine transitionToState:SDLVideoStreamManagerStateSuspended];
} else {
[self sdl_stopVideoSession];
@@ -263,7 +267,7 @@ typedef void(^SDLVideoCapabilityResponseHandler)(SDLVideoStreamingCapability *_N
SDLLogD(@"App became active");
if (!self.protocol) { return; }
- if ([self.videoStreamStateMachine.currentState isEqualToString:SDLVideoStreamManagerStateSuspended]) {
+ if (self.isVideoSuspended) {
[self.videoStreamStateMachine transitionToState:SDLVideoStreamManagerStateReady];
} else {
[self sdl_startVideoSession];
@@ -610,7 +614,8 @@ typedef void(^SDLVideoCapabilityResponseHandler)(SDLVideoStreamingCapability *_N
return;
}
- if (self.isVideoConnected) {
+
+ if (self.isVideoConnected || self.isVideoSuspended) {
[self.videoStreamStateMachine transitionToState:SDLVideoStreamManagerStateShuttingDown];
}
}
diff --git a/SmartDeviceLink/SDLTCPTransport.h b/SmartDeviceLink/SDLTCPTransport.h
index 6349bb1a8..5b7ac09f0 100644
--- a/SmartDeviceLink/SDLTCPTransport.h
+++ b/SmartDeviceLink/SDLTCPTransport.h
@@ -5,9 +5,7 @@
NS_ASSUME_NONNULL_BEGIN
-@interface SDLTCPTransport : NSObject <SDLTransportType> {
- _Nullable CFSocketRef socket;
-}
+@interface SDLTCPTransport : NSObject <SDLTransportType, NSStreamDelegate>
/**
* Convenience init
diff --git a/SmartDeviceLink/SDLTCPTransport.m b/SmartDeviceLink/SDLTCPTransport.m
index e4dd5e745..f56641fe7 100644
--- a/SmartDeviceLink/SDLTCPTransport.m
+++ b/SmartDeviceLink/SDLTCPTransport.m
@@ -1,43 +1,45 @@
+//
// SDLTCPTransport.m
+// SmartDeviceLink
+//
+// Created by Sho Amano on 2018/04/23.
+// Copyright © 2018 Xevo Inc. All rights reserved.
//
-
#import "SDLTCPTransport.h"
-#import "SDLLogConstants.h"
+#import "SDLMutableDataQueue.h"
+#import "SDLError.h"
#import "SDLLogMacros.h"
-#import "SDLLogManager.h"
-#import "SDLHexUtility.h"
#import <errno.h>
-#import <netdb.h>
-#import <netinet/in.h>
-#import <signal.h>
-#import <stdio.h>
-#import <sys/socket.h>
-#import <sys/types.h>
-#import <sys/wait.h>
-#import <unistd.h>
NS_ASSUME_NONNULL_BEGIN
-// C function forward declarations.
-int call_socket(const char *hostname, const char *port);
-static void TCPCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info);
-
-@interface SDLTCPTransport () {
- dispatch_queue_t _sendQueue;
-}
-
+NSString *const TCPIOThreadName = @"com.smartdevicelink.tcpiothread";
+NSTimeInterval const IOThreadWaitSecs = 1.0;
+NSUInteger const DefaultReceiveBufferSize = 16 * 1024;
+NSTimeInterval ConnectionTimeoutSecs = 30.0;
+
+@interface SDLTCPTransport ()
+
+@property (nullable, nonatomic, strong) NSThread *ioThread;
+@property (nonatomic, strong) dispatch_semaphore_t ioThreadStoppedSemaphore;
+@property (nonatomic, assign) NSUInteger receiveBufferSize;
+@property (nonatomic, strong) SDLMutableDataQueue *sendDataQueue;
+@property (nullable, nonatomic, strong) NSInputStream *inputStream;
+@property (nullable, nonatomic, strong) NSOutputStream *outputStream;
+@property (nonatomic, assign) BOOL outputStreamHasSpace;
+@property (nullable, nonatomic, strong) NSTimer *connectionTimer;
+@property (nonatomic, assign) BOOL transportConnected;
+@property (nonatomic, assign) BOOL transportErrorNotified;
@end
-
@implementation SDLTCPTransport
- (instancetype)init {
if (self = [super init]) {
- _sendQueue = dispatch_queue_create("com.sdl.transport.tcp.transmit", DISPATCH_QUEUE_SERIAL);
- SDLLogD(@"TCP Transport initialization");
+ _receiveBufferSize = DefaultReceiveBufferSize;
+ _sendDataQueue = [[SDLMutableDataQueue alloc] init];
}
-
return self;
}
@@ -52,125 +54,302 @@ static void TCPCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef
}
- (void)dealloc {
+ SDLLogD(@"SDLTCPTransport dealloc");
[self disconnect];
}
+#pragma mark - Stream Lifecycle
+
+// Note: When a connection is refused (e.g. TCP port number is not correct) or timed out (e.g. invalid IP address), then onError will be notified.
- (void)connect {
- __weak typeof(self) weakself = self;
- [[NSOperationQueue mainQueue] addOperationWithBlock:^{
- __strong typeof(self) strongself = weakself;
- SDLLogD(@"Attempting to connect");
-
- int sock_fd = call_socket([self.hostName UTF8String], [self.portNumber UTF8String]);
- if (sock_fd < 0) {
- SDLLogE(@"Server not ready, connection failed");
- return;
- }
-
- CFSocketContext socketCtxt = {0, (__bridge void *)(self), NULL, NULL, NULL};
- strongself->socket = CFSocketCreateWithNative(kCFAllocatorDefault, sock_fd, kCFSocketDataCallBack | kCFSocketConnectCallBack, (CFSocketCallBack)&TCPCallback, &socketCtxt);
- CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, strongself->socket, 0);
- CFRunLoopRef loop = CFRunLoopGetCurrent();
- CFRunLoopAddSource(loop, source, kCFRunLoopDefaultMode);
- CFRelease(source);
- }];
+ if (self.ioThread != nil) {
+ SDLLogW(@"TCP transport is already connected");
+ return;
+ }
+
+ unsigned int port;
+ int num = [self.portNumber intValue];
+ if (0 <= num && num <= 65535) {
+ port = (unsigned int)num;
+ } else {
+ // specify an invalid port, so that once connection is initiated we will receive an error through delegate
+ port = 65536;
+ }
+
+ self.ioThread = [[NSThread alloc] initWithTarget:self selector:@selector(sdl_tcpTransportEventLoop) object:nil];
+ self.ioThread.name = TCPIOThreadName;
+ self.ioThreadStoppedSemaphore = dispatch_semaphore_create(0);
+
+ CFReadStreamRef readStream = NULL;
+ CFWriteStreamRef writeStream = NULL;
+ CFStringRef hostName = (__bridge CFStringRef)self.hostName;
+
+ CFStreamCreatePairWithSocketToHost(NULL, hostName, port, &readStream, &writeStream);
+
+ // this transport is mainly for video streaming
+ CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVideo);
+ CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVideo);
+
+ self.inputStream = (__bridge_transfer NSInputStream *)readStream;
+ self.outputStream = (__bridge_transfer NSOutputStream *)writeStream;
+
+ [self.ioThread start];
+}
+
+- (void)disconnect {
+ if (self.ioThread == nil) {
+ // already disconnected
+ return;
+ }
+
+ SDLLogD(@"Disconnecting TCP transport");
+
+ [self sdl_cancelIOThread];
+
+ long ret = dispatch_semaphore_wait(self.ioThreadStoppedSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(IOThreadWaitSecs * NSEC_PER_SEC)));
+ if (ret == 0) {
+ SDLLogD(@"TCP transport thread stopped");
+ } else {
+ SDLLogE(@"Failed to stop TCP transport thread");
+ }
+ self.ioThread = nil;
+
+ self.inputStream = nil;
+ self.outputStream = nil;
+
+ [self.sendDataQueue removeAllObjects];
+ self.transportErrorNotified = NO;
+ self.transportConnected = NO;
}
+#pragma mark - Data Transmission
+
- (void)sendData:(NSData *)msgBytes {
- dispatch_async(_sendQueue, ^{
- @autoreleasepool {
- SDLLogBytes(msgBytes, SDLLogBytesDirectionTransmit);
- CFSocketError e = CFSocketSendData(self->socket, NULL, (__bridge CFDataRef)msgBytes, 10000);
- if (e != kCFSocketSuccess) {
- NSString *errorCause = nil;
- switch (e) {
- case kCFSocketTimeout:
- errorCause = @"Socket Timeout Error.";
- break;
-
- case kCFSocketError:
- default:
- errorCause = @"Socket Error.";
- break;
- }
-
- SDLLogE(@"Socket send error: %@", errorCause);
+ [self.sendDataQueue enqueueBuffer:msgBytes.mutableCopy];
+
+ [self performSelector:@selector(sdl_writeToStream) onThread:self.ioThread withObject:nil waitUntilDone:NO];
+}
+
+#pragma mark - Run loop
+
+- (void)sdl_tcpTransportEventLoop {
+ @autoreleasepool {
+ [self sdl_setupStream:self.inputStream];
+ [self sdl_setupStream:self.outputStream];
+
+ // JFYI: NSStream itself has a connection timeout (about 1 minute). If you specify a large timeout value, you may get the NSStream's timeout event first.
+ self.connectionTimer = [NSTimer scheduledTimerWithTimeInterval:ConnectionTimeoutSecs target:self selector:@selector(sdl_onConnectionTimedOut:) userInfo:nil repeats:NO];
+
+ // these will initiate a connection to remote server
+ SDLLogD(@"Connecting to %@:%@ ...", self.hostName, self.portNumber);
+ [self.inputStream open];
+ [self.outputStream open];
+
+ while (![self.ioThread isCancelled]) {
+ BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
+ if (!ret) {
+ SDLLogW(@"Failed to start TCP transport run loop");
+ break;
}
}
- });
-}
+ SDLLogD(@"TCP transport run loop terminated");
-- (void)disconnect {
- SDLLogD(@"Disconnect connection");
-
- if (socket != nil) {
- CFSocketInvalidate(socket);
- CFRelease(socket);
- socket = nil;
+ [self sdl_teardownStream:self.inputStream];
+ [self sdl_teardownStream:self.outputStream];
+
+ [self.connectionTimer invalidate];
+
+ dispatch_semaphore_signal(self.ioThreadStoppedSemaphore);
}
}
-@end
+- (void)sdl_setupStream:(NSStream *)stream {
+ stream.delegate = self;
+ [stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+}
+
+- (void)sdl_teardownStream:(NSStream *)stream {
+ NSStreamStatus streamStatus = stream.streamStatus;
+ if (streamStatus != NSStreamStatusNotOpen && streamStatus != NSStreamStatusClosed) {
+ [stream close];
+ }
+ [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+ [stream setDelegate:nil];
+}
-// C functions
-int call_socket(const char *hostname, const char *port) {
- int status, sock;
- struct addrinfo hints;
- struct addrinfo *servinfo;
+- (void)sdl_cancelIOThread {
+ [self.ioThread cancel];
+ // wake up the run loop in case we don't have any I/O event
+ [self performSelector:@selector(sdl_doNothing) onThread:self.ioThread withObject:nil waitUntilDone:NO];
+}
- memset(&hints, 0, sizeof hints);
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
+#pragma mark - NSStreamDelegate
+// this method runs only on the I/O thread (i.e. invoked from the run loop)
+
+- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
+ switch (eventCode) {
+ case NSStreamEventNone: {
+ // nothing to do
+ } break;
+ case NSStreamEventOpenCompleted: {
+ // We will get two NSStreamEventOpenCompleted events (for both input and output streams) and we don't need both. Let's use the one of output stream since we need to make sure that output stream is ready before Proxy sending Start Service frame.
+ if (aStream == self.outputStream) {
+ SDLLogD(@"TCP transport connected");
+ [self.connectionTimer invalidate];
+ self.transportConnected = YES;
+ [self.delegate onTransportConnected];
+ }
+ } break;
+ case NSStreamEventHasBytesAvailable: {
+ [self sdl_readFromStream];
+ } break;
+ case NSStreamEventHasSpaceAvailable: {
+ self.outputStreamHasSpace = YES;
+ [self sdl_writeToStream];
+ } break;
+ case NSStreamEventErrorOccurred: {
+ SDLLogW(@"TCP transport error occurred with %@ stream: %@", aStream == self.inputStream ? @"input" : @"output", aStream.streamError);
+ [self sdl_onStreamError:aStream];
+ } break;
+ case NSStreamEventEndEncountered: {
+ SDLLogD(@"TCP transport %@ stream end encountered", aStream == self.inputStream ? @"input" : @"output");
+ [self sdl_onStreamEnd:aStream];
+ } break;
+ }
+}
- //no host name?, no problem, get local host
- if (hostname == nil) {
- char localhost[128];
- gethostname(localhost, sizeof localhost);
- hostname = (const char *)&localhost;
+#pragma mark - Stream event handlers
+// these methods run only on the I/O thread (i.e. invoked from the run loop)
+
+- (void)sdl_readFromStream {
+ NSAssert([[NSThread currentThread] isEqual:self.ioThread], @"sdl_readFromStream is called on a wrong thread!");
+
+ BytePtr buffer = malloc(self.receiveBufferSize);
+ NSInteger readBytes = [self.inputStream read:buffer maxLength:self.receiveBufferSize];
+ if (readBytes < 0) {
+ SDLLogW(@"TCP transport read error: %@", self.inputStream.streamError);
+ [self sdl_onStreamError:self.inputStream];
+ free(buffer);
+ return;
+ } else if (readBytes == 0) {
+ SDLLogD(@"TCP transport input stream closed");
+ [self sdl_onStreamEnd:self.inputStream];
+ free(buffer);
+ return;
}
- //getaddrinfo setup
- if ((status = getaddrinfo(hostname, port, &hints, &servinfo)) != 0) {
- fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
- return (-1);
+ NSData *data = [NSData dataWithBytesNoCopy:buffer length:(NSUInteger)readBytes freeWhenDone:YES];
+ [self.delegate onDataReceived:data];
+}
+
+- (void)sdl_writeToStream {
+ NSAssert([[NSThread currentThread] isEqual:self.ioThread], @"sdl_writeToStream is called on a wrong thread!");
+
+ if (!self.outputStreamHasSpace || [self.sendDataQueue count] == 0) {
+ return;
}
- //get socket
- if ((sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) < 0)
- return (-1);
+ NSMutableData *buffer = [self.sendDataQueue frontBuffer];
+ NSUInteger bufferLen = buffer.length;
+
+ NSInteger bytesWritten = [self.outputStream write:buffer.bytes maxLength:bufferLen];
+ if (bytesWritten < 0) {
+ SDLLogW(@"TCP transport write error: %@", self.outputStream.streamError);
+ [self sdl_onStreamError:self.outputStream];
+ return;
+ } else if (bytesWritten == 0) {
+ SDLLogD(@"TCP transport output stream closed");
+ [self sdl_onStreamEnd:self.outputStream];
+ return;
+ }
- //connect
- if (connect(sock, servinfo->ai_addr, servinfo->ai_addrlen) < 0) {
- close(sock);
- return (-1);
+ if (bytesWritten == bufferLen) {
+ // we have consumed all of data in this buffer
+ [self.sendDataQueue popBuffer];
+ } else {
+ [buffer replaceBytesInRange:NSMakeRange(0, (NSUInteger)bytesWritten) withBytes:NULL length:0];
}
- freeaddrinfo(servinfo); // free the linked-list
- return (sock);
+ // the output stream may still have some spaces, but let's wait for another NSStreamEventHasSpaceAvailable event before writing
+ self.outputStreamHasSpace = NO;
}
-static void TCPCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
- if (kCFSocketConnectCallBack == type) {
- SDLTCPTransport *transport = (__bridge SDLTCPTransport *)info;
- [transport.delegate onTransportConnected];
- } else if (kCFSocketDataCallBack == type) {
- SDLTCPTransport *transport = (__bridge SDLTCPTransport *)info;
+- (void)sdl_onConnectionTimedOut:(NSTimer *)timer {
+ NSAssert([[NSThread currentThread] isEqual:self.ioThread], @"sdl_onConnectionTimedOut is called on a wrong thread!");
- // Check if Core disconnected from us
- if (CFDataGetLength((CFDataRef)data) <= 0) {
- SDLLogW(@"Remote system terminated connection, data packet length 0");
- [transport.delegate onTransportDisconnected];
+ SDLLogW(@"TCP connection timed out");
+ [self sdl_cancelIOThread];
- return;
- }
+ if (!self.transportErrorNotified) {
+ NSAssert(!self.transportConnected, @"transport should not be connected in this case");
+ [self.delegate onError:[NSError sdl_transport_connectionTimedOutError]];
+ self.transportErrorNotified = YES;
+ }
+}
+
+- (void)sdl_onStreamError:(NSStream *)stream {
+ NSAssert([[NSThread currentThread] isEqual:self.ioThread], @"sdl_onStreamError is called on a wrong thread!");
+
+ // stop I/O thread
+ [self sdl_cancelIOThread];
+
+ // avoid notifying multiple error events
+ if (self.transportErrorNotified) {
+ return;
+ }
- // Handle the data we received
- NSData *convertedData = [NSData dataWithBytes:(UInt8 *)CFDataGetBytePtr((CFDataRef)data) length:(NSUInteger)CFDataGetLength((CFDataRef)data)];
- SDLLogBytes(convertedData, SDLLogBytesDirectionReceive);
- [transport.delegate onDataReceived:convertedData];
+ if (self.transportConnected) {
+ // transport is disconnected while running
+ [self.delegate onTransportDisconnected];
+ self.transportErrorNotified = YES;
+ } else if ([stream.streamError.domain isEqualToString:NSPOSIXErrorDomain]) {
+ // connection error
+
+ // According to Apple's document "Error Objects, Domains, and Codes", the 'code' values of NSPOSIXErrorDomain are actually errno values.
+ NSError *error;
+ switch (stream.streamError.code) {
+ case ECONNREFUSED: {
+ SDLLogD(@"TCP connection error: ECONNREFUSED");
+ error = [NSError sdl_transport_connectionRefusedError];
+ } break;
+ case ETIMEDOUT: {
+ SDLLogD(@"TCP connection error: ETIMEDOUT");
+ error = [NSError sdl_transport_connectionTimedOutError];
+ } break;
+ case ENETDOWN: {
+ SDLLogD(@"TCP connection error: ENETDOWN");
+ error = [NSError sdl_transport_networkDownError];
+ } break;
+ case ENETUNREACH: {
+ // This is just for safe. I did not observe ENETUNREACH error on iPhone.
+ SDLLogD(@"TCP connection error: ENETUNREACH");
+ error = [NSError sdl_transport_networkDownError];
+ } break;
+ default: {
+ SDLLogD(@"TCP connection error: unknown error %ld", (long)stream.streamError.code);
+ error = [NSError sdl_transport_unknownError];
+ } break;
+ }
+ [self.delegate onError:error];
+ self.transportErrorNotified = YES;
} else {
- SDLLogW(@"Unhandled callback type: %lu", type);
+ SDLLogE(@"Unhandled stream error! %@", stream.streamError);
}
}
+- (void)sdl_onStreamEnd:(NSStream *)stream {
+ NSAssert([[NSThread currentThread] isEqual:self.ioThread], @"sdl_onStreamEnd is called on a wrong thread!");
+
+ [self sdl_cancelIOThread];
+
+ if (!self.transportErrorNotified) {
+ [self.delegate onTransportDisconnected];
+ self.transportErrorNotified = YES;
+ }
+}
+
+- (void)sdl_doNothing {}
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLTransportDelegate.h b/SmartDeviceLink/SDLTransportDelegate.h
index f81e2c043..c8350918a 100644
--- a/SmartDeviceLink/SDLTransportDelegate.h
+++ b/SmartDeviceLink/SDLTransportDelegate.h
@@ -21,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
* @param receivedData The data received from Core
*/
- (void)onDataReceived:(NSData *)receivedData;
+- (void)onError:(NSError *)error;
@end
diff --git a/SmartDeviceLink/SmartDeviceLink.h b/SmartDeviceLink/SmartDeviceLink.h
index dae9556bd..1ab1ae83f 100644
--- a/SmartDeviceLink/SmartDeviceLink.h
+++ b/SmartDeviceLink/SmartDeviceLink.h
@@ -233,6 +233,7 @@ FOUNDATION_EXPORT const unsigned char SmartDeviceLinkVersionString[];
#import "SDLAppHMIType.h"
#import "SDLAppInterfaceUnregisteredReason.h"
#import "SDLAudioStreamingState.h"
+#import "SDLAudioStreamingIndicator.h"
#import "SDLAudioType.h"
#import "SDLBitsPerSample.h"
#import "SDLButtonEventMode.h"
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m
index 33062b9db..857ca6b83 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m
@@ -304,6 +304,17 @@ describe(@"the streaming video manager", ^{
expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
});
});
+
+ context(@"and hmi state changes to background after app becomes inactive", ^{
+ beforeEach(^{
+ [streamingLifecycleManager.appStateMachine setToState:SDLAppStateInactive fromOldState:nil callEnterTransition:YES];
+ sendNotificationForHMILevel(SDLHMILevelBackground, SDLVideoStreamingStateNotStreamable);
+ });
+
+ it(@"should close the stream", ^{
+ expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
+ });
+ });
});
});
diff --git a/SmartDeviceLinkTests/RPCSpecs/EnumSpecs/SDLAudioStreamingIndicatorSpec.m b/SmartDeviceLinkTests/RPCSpecs/EnumSpecs/SDLAudioStreamingIndicatorSpec.m
new file mode 100644
index 000000000..17dc3ef67
--- /dev/null
+++ b/SmartDeviceLinkTests/RPCSpecs/EnumSpecs/SDLAudioStreamingIndicatorSpec.m
@@ -0,0 +1,24 @@
+//
+// SDLAudioStreamingIndicatorSpec.m
+// SmartDeviceLink
+
+
+#import <Foundation/Foundation.h>
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLAudioStreamingIndicator.h"
+
+QuickSpecBegin(SDLAudioStreamingIndicatorStatusSpec)
+
+describe(@"Individual Enum Value Tests", ^ {
+ it(@"Should match internal values", ^ {
+ expect(SDLAudioStreamingIndicatorPlayPause).to(equal(@"PLAY_PAUSE"));
+ expect(SDLAudioStreamingIndicatorPlay).to(equal(@"PLAY"));
+ expect(SDLAudioStreamingIndicatorPause).to(equal(@"PAUSE"));
+ expect(SDLAudioStreamingIndicatorStop).to(equal(@"STOP"));
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/RPCSpecs/RequestSpecs/SDLSetMediaClockTimerSpec.m b/SmartDeviceLinkTests/RPCSpecs/RequestSpecs/SDLSetMediaClockTimerSpec.m
index bf8243ab8..351a6e558 100644
--- a/SmartDeviceLinkTests/RPCSpecs/RequestSpecs/SDLSetMediaClockTimerSpec.m
+++ b/SmartDeviceLinkTests/RPCSpecs/RequestSpecs/SDLSetMediaClockTimerSpec.m
@@ -26,10 +26,12 @@ describe(@"Getter/Setter Tests", ^ {
testRequest.startTime = time1;
testRequest.endTime = time2;
testRequest.updateMode = SDLUpdateModeCountUp;
+ testRequest.audioStreamingIndicator = SDLAudioStreamingIndicatorPlayPause;
expect(testRequest.startTime).to(equal(time1));
expect(testRequest.endTime).to(equal(time2));
expect(testRequest.updateMode).to(equal(SDLUpdateModeCountUp));
+ expect(testRequest.audioStreamingIndicator).to(equal(SDLAudioStreamingIndicatorPlayPause));
});
it(@"Should get correctly when initialized", ^ {
@@ -37,13 +39,16 @@ describe(@"Getter/Setter Tests", ^ {
@{SDLNameParameters:
@{SDLNameStartTime:time1,
SDLNameEndTime:time2,
- SDLNameUpdateMode:SDLUpdateModeCountUp},
+ SDLNameUpdateMode:SDLUpdateModeCountUp,
+ SDLNameAudioStreamingIndicator:SDLAudioStreamingIndicatorPlayPause
+ },
SDLNameOperationName:SDLNameSetMediaClockTimer}} mutableCopy];
SDLSetMediaClockTimer* testRequest = [[SDLSetMediaClockTimer alloc] initWithDictionary:dict];
expect(testRequest.startTime).to(equal(time1));
expect(testRequest.endTime).to(equal(time2));
expect(testRequest.updateMode).to(equal(SDLUpdateModeCountUp));
+ expect(testRequest.audioStreamingIndicator).to(equal(SDLAudioStreamingIndicatorPlayPause));
});
it(@"Should return nil if not set", ^ {
@@ -52,6 +57,7 @@ describe(@"Getter/Setter Tests", ^ {
expect(testRequest.startTime).to(beNil());
expect(testRequest.endTime).to(beNil());
expect(testRequest.updateMode).to(beNil());
+ expect(testRequest.audioStreamingIndicator).to(beNil());
});
});
diff --git a/SmartDeviceLinkTests/TestUtilities/TestTCPServer.h b/SmartDeviceLinkTests/TestUtilities/TestTCPServer.h
new file mode 100644
index 000000000..9cbd0ec39
--- /dev/null
+++ b/SmartDeviceLinkTests/TestUtilities/TestTCPServer.h
@@ -0,0 +1,71 @@
+//
+// TestTCPServer.h
+// SmartDeviceLink-iOS
+//
+// Created by Sho Amano on 2018/07/27.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Delegate to receive various events from the test TCP server
+ */
+@protocol TestTCPServerDelegate
+- (void)onClientConnected;
+- (void)onClientDataReceived:(NSData *)data;
+- (void)onClientShutdown;
+- (void)onClientError;
+@end
+
+@interface TestTCPServer : NSObject
+
+/**
+ * Sets up a TCP server that listens on specified host and port
+ *
+ * Note that this server cannot accept more than one connections from client(s).
+ *
+ * @param hostName Host name that the server will listen on
+ * @param portNumber TCP port number of the server
+ * @return YES when initialization is successful, NO otherwise
+ */
+- (BOOL)setup:(NSString *)hostName port:(NSString *)port;
+
+/**
+ * Shuts down the server, forcefully closing client connection
+ *
+ * @return YES when the server is successfully stopped, NO otherwise
+ */
+- (BOOL)teardown;
+
+/**
+ * Asynchronously sends data to connected client
+ *
+ * @param data Data to send
+ */
+- (void)send:(NSData *)data;
+
+/**
+ * Gracefully shuts down the connection between client.
+ *
+ * This method triggers shutdown(SHUT_WR) which is to notify that the server does not have any more data to send.
+ *
+ * @return YES if shutdown process is succeeded, NO if it's failed or client is not connected
+ */
+- (BOOL)shutdownClient;
+
+/**
+ * The delegate to receive server events
+ */
+@property (nullable, nonatomic, weak) id<TestTCPServerDelegate> delegate;
+
+/**
+ * Configure this flag to YES to enable SO_REUSEADDR option
+ */
+@property (nonatomic, assign) BOOL enableSOReuseAddr;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLinkTests/TestUtilities/TestTCPServer.m b/SmartDeviceLinkTests/TestUtilities/TestTCPServer.m
new file mode 100644
index 000000000..c7b332949
--- /dev/null
+++ b/SmartDeviceLinkTests/TestUtilities/TestTCPServer.m
@@ -0,0 +1,370 @@
+//
+// TestTCPServer.m
+// SmartDeviceLinkTests
+//
+// Created by Sho Amano on 2018/07/27.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <Nimble/Nimble.h>
+
+#import "TestTCPServer.h"
+
+#import <sys/types.h>
+#import <sys/socket.h>
+#import <netdb.h>
+#import <sys/select.h>
+#import <sys/time.h>
+#import <fcntl.h>
+#import <string.h>
+#import <errno.h>
+
+#define MAX_SERVER_SOCKET_NUM (16)
+#define RECV_BUF_SIZE (1024)
+#define THREAD_STOP_WAIT_SEC (1.0)
+
+@interface TestTCPServer() {
+ int _serverSockets[MAX_SERVER_SOCKET_NUM];
+ int _internalSockets[2];
+ int _clientSocket; // supports only one client
+}
+
+@property (nullable, nonatomic, strong) NSThread *thread;
+@property (nonatomic, strong) dispatch_semaphore_t threadStoppedSemaphore;
+@property (nonatomic, strong) NSMutableArray<NSMutableData*> *sendData;
+@end
+
+@implementation TestTCPServer
+
+- (instancetype)init {
+ if (self = [super init]) {
+ for (unsigned int i = 0; i < MAX_SERVER_SOCKET_NUM; i++) {
+ _serverSockets[i] = -1;
+ }
+ _sendData = [[NSMutableArray alloc] init];
+ _enableSOReuseAddr = YES;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self teardown];
+}
+
+- (BOOL)setup:(NSString *)hostName port:(NSString *)port {
+ int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, _internalSockets);
+ if (ret < 0) {
+ NSLog(@"TestTCPServer: socketpair() failed");
+ return NO;
+ }
+ if (!([self configureSocket:_internalSockets[0]] && [self configureSocket:_internalSockets[1]])) {
+ return NO;
+ }
+
+ struct addrinfo hints, *res;
+ hints.ai_family = PF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_PASSIVE /* server socket */
+ | AI_NUMERICSERV /* 2nd arg is numeric port number */
+ | AI_ALL | AI_V4MAPPED; /* return both IPv4 and IPv6 addresses */
+
+ ret = getaddrinfo([hostName UTF8String], [port UTF8String], &hints, &res);
+ if (ret != 0) {
+ NSLog(@"Error: TestTCPServer getaddrinfo() failed, %s", gai_strerror(ret));
+ return NO;
+ }
+
+ int socketNum = 0;
+ for (struct addrinfo *info = res; info != NULL && socketNum < (MAX_SERVER_SOCKET_NUM - 1); info = info->ai_next) {
+ int sock = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
+ if (sock < 0) {
+ NSLog(@"Error: TestTCPServer server socket creation failed");
+ continue;
+ }
+
+ if (![self configureServerSocket:sock]) {
+ close(sock);
+ continue;
+ }
+
+ ret = bind(sock, info->ai_addr, info->ai_addrlen);
+ if (ret < 0) {
+ NSLog(@"Error: TestTCPServer server socket bind() failed: %s", strerror(errno));
+ close(sock);
+ continue;
+ }
+
+ ret = listen(sock, 5);
+ if (ret < 0) {
+ NSLog(@"Error: TestTCPServer server socket listen() failed: %s", strerror(errno));
+ close(sock);
+ continue;
+ }
+
+ _serverSockets[socketNum] = sock;
+ socketNum++;
+ }
+ freeaddrinfo(res);
+
+ _clientSocket = -1;
+
+ // create a thread and run
+ self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
+ self.threadStoppedSemaphore = dispatch_semaphore_create(0);
+ NSLog(@"TestTCPServer: starting TCP server");
+ [self.thread start];
+
+ return YES;
+}
+
+- (BOOL)teardown {
+ if (self.thread == nil) {
+ return YES;
+ }
+
+ BOOL result = YES;
+
+ // wake up select() and let it stop
+ shutdown(_internalSockets[1], SHUT_WR);
+
+ long ret = dispatch_semaphore_wait(self.threadStoppedSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(THREAD_STOP_WAIT_SEC * NSEC_PER_SEC)));
+ if (ret != 0) {
+ NSLog(@"Error: TestTCPServer thread doesn't stop");
+ result = NO;
+ }
+ self.thread = nil;
+
+ for (unsigned int i = 0; i < MAX_SERVER_SOCKET_NUM; i++) {
+ if (_serverSockets[i] >= 0) {
+ close(_serverSockets[i]);
+ _serverSockets[i] = -1;
+ }
+ }
+ if (_internalSockets[0] >= 0) {
+ close(_internalSockets[0]);
+ _internalSockets[0] = -1;
+ }
+ if (_internalSockets[1] >= 0) {
+ close(_internalSockets[1]);
+ _internalSockets[1] = -1;
+ }
+
+ [self.sendData removeAllObjects];
+ return result;
+}
+
+- (void)send:(NSData *)data {
+ [self.sendData addObject:[data mutableCopy]];
+
+ // wake up select()
+ char buf[1] = {'a'};
+ send(_internalSockets[1], buf, sizeof(buf), 0);
+}
+
+- (BOOL)shutdownClient {
+ if (_clientSocket < 0) {
+ // client is not connected
+ return NO;
+ }
+ int ret = shutdown(_clientSocket, SHUT_WR);
+ if (ret != 0) {
+ NSLog(@"TestTCPServer: shutdown() for client socket failed: %s", strerror(errno));
+ return NO;
+ }
+ return YES;
+}
+
+- (void)run:(id)userInfo {
+ BOOL running = YES;
+ BOOL internalFailure = NO;
+ int ret;
+
+ while (running) {
+ fd_set readfds;
+ fd_set writefds;
+ int maxFd = 0;
+
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+
+ for (unsigned int i = 0; _serverSockets[i] >= 0; i++) {
+ FD_SET(_serverSockets[i], &readfds);
+ if (_serverSockets[i] > maxFd) {
+ maxFd = _serverSockets[i];
+ }
+ }
+ FD_SET(_internalSockets[0], &readfds);
+ if (_internalSockets[0] > maxFd) {
+ maxFd = _internalSockets[0];
+ }
+
+ if (_clientSocket >= 0) {
+ FD_SET(_clientSocket, &readfds);
+ if ([self.sendData count] > 0) {
+ FD_SET(_clientSocket, &writefds);
+ }
+
+ if (_clientSocket > maxFd) {
+ maxFd = _clientSocket;
+ }
+ }
+
+ ret = select(maxFd + 1, &readfds, &writefds, NULL, NULL);
+ if (ret < 0) {
+ NSLog(@"Error: TestTCPServer TCP server select() failed");
+ internalFailure = YES;
+ break;
+ }
+
+ // client socket check
+ if (_clientSocket >= 0) {
+ if (FD_ISSET(_clientSocket, &readfds)) {
+ char buf[RECV_BUF_SIZE];
+ ssize_t recvLen = recv(_clientSocket, buf, sizeof(buf), 0);
+ if (recvLen < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ // this is not an error
+ } else {
+ NSLog(@"TestTCPServer: recv() for client socket failed: %s", strerror(errno));
+ [self.delegate onClientError];
+ close(_clientSocket);
+ _clientSocket = -1;
+ }
+ } else if (recvLen == 0) {
+ [self.delegate onClientShutdown];
+ // keep the socket open in case we have some more data to send
+ } else {
+ NSData *data = [NSData dataWithBytes:buf length:recvLen];
+ [self.delegate onClientDataReceived:data];
+ }
+ }
+ if (FD_ISSET(_clientSocket, &writefds)) {
+ NSMutableData *data = self.sendData[0];
+ ssize_t sentLen = send(_clientSocket, data.bytes, data.length, 0);
+ if (sentLen < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ // this is not an error
+ } else {
+ NSLog(@"TestTCPServer: send() for client socket failed: %s", strerror(errno));
+ [self.delegate onClientError];
+ close(_clientSocket);
+ _clientSocket = -1;
+ }
+ } else if (sentLen > 0) {
+ if (data.length == (NSUInteger)sentLen) {
+ [self.sendData removeObjectAtIndex:0];
+ } else {
+ [data replaceBytesInRange:NSMakeRange(0, sentLen) withBytes:NULL length:0];
+ }
+ }
+ }
+ }
+
+ // server socket check
+ for (unsigned int i = 0; _serverSockets[i] >= 0; i++) {
+ int sock = _serverSockets[i];
+ if (FD_ISSET(sock, &readfds)) {
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ ret = accept(sock, (struct sockaddr *)&addr, &addrlen);
+ if (ret < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ // this is not an error
+ continue;
+ } else {
+ NSLog(@"Error: TestTCPServer TCP server accept() failed: %s", strerror(errno));
+ internalFailure = YES;
+ running = NO;
+ break;
+ }
+ }
+
+ if (_clientSocket >= 0) {
+ NSLog(@"Error: TestTCPServer TCP server received more than one connections");
+ }
+
+ if (![self configureSocket:ret]) {
+ close(ret);
+ internalFailure = YES;
+ running = NO;
+ break;
+ };
+
+ _clientSocket = ret;
+ [self.delegate onClientConnected];
+ }
+ }
+
+ // internal pipe check
+ if (FD_ISSET(_internalSockets[0], &readfds)) {
+ char buf[16];
+ ssize_t recvLen = recv(_internalSockets[0], buf, sizeof(buf), 0);
+ if (recvLen < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ // this is not an error
+ } else {
+ NSLog(@"Error: TestTCPServer TCP server recv() failed for internal pipe: %s", strerror(errno));
+ internalFailure = YES;
+ break;
+ }
+ } else if (recvLen == 0) {
+ NSLog(@"TestTCPServer: stopping TCP server");
+ break;
+ }
+ }
+ }
+
+ if (_clientSocket >= 0) {
+ close(_clientSocket);
+ _clientSocket = -1;
+ }
+
+ expect(internalFailure == NO);
+
+ dispatch_semaphore_signal(self.threadStoppedSemaphore);
+}
+
+- (BOOL)configureSocket:(int)sock {
+ // make the socket non-blocking
+ int flags;
+ flags = fcntl(sock, F_GETFL, 0);
+ if (flags == -1) {
+ NSLog(@"Error: TestTCPServer fcntl (F_GETFL) failed");
+ return NO;
+ }
+ int ret = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+ if (ret == -1) {
+ NSLog(@"Error: TestTCPServer fcntl (F_SETFL) failed: %s", strerror(errno));
+ return NO;
+ }
+
+ // don't generate SIGPIPE signal
+ int val = 1;
+ ret = setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val));
+ if (ret != 0) {
+ NSLog(@"Error: TestTCPServer setsockopt() failed");
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)configureServerSocket:(int)sock {
+ if (![self configureSocket:sock]) {
+ return NO;
+ }
+
+ if (self.enableSOReuseAddr) {
+ int val = 1;
+ int ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+ if (ret != 0) {
+ NSLog(@"Error: TestTCPServer setsockopt() failed");
+ return NO;
+ }
+ }
+
+ return YES;
+}
+@end
diff --git a/SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m b/SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m
new file mode 100644
index 000000000..e9a25f834
--- /dev/null
+++ b/SmartDeviceLinkTests/TransportSpecs/SDLTCPTransportSpec.m
@@ -0,0 +1,291 @@
+//
+// SDLTCPTransportSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Sho Amano on 2018/04/24.
+// Copyright © 2018 Xevo Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLTCPTransport.h"
+#import "SDLError.h"
+#import "TestTCPServer.h"
+
+#import <stdio.h>
+
+@interface SDLTCPTransport ()
+// verify some internal properties
+@property (nullable, nonatomic, strong) NSThread *ioThread;
+@property (nullable, nonatomic, strong) NSInputStream *inputStream;
+@property (nullable, nonatomic, strong) NSOutputStream *outputStream;
+@end
+
+QuickSpecBegin(SDLTCPTransportSpec)
+
+describe(@"SDLTCPTransport", ^ {
+ __block SDLTCPTransport *transport = nil;
+ __block id transportDelegateMock = nil;
+ __block TestTCPServer *server = nil;
+ __block id serverDelegateMock = nil;
+
+ beforeEach(^{
+ transport = [[SDLTCPTransport alloc] init];
+ transport.hostName = @"localhost";
+ transport.portNumber = @"12345";
+ transportDelegateMock = OCMProtocolMock(@protocol(SDLTransportDelegate));
+ transport.delegate = transportDelegateMock;
+
+ server = [[TestTCPServer alloc] init];
+ serverDelegateMock = OCMProtocolMock(@protocol(TestTCPServerDelegate));
+ server.delegate = serverDelegateMock;
+ });
+
+ afterEach(^{
+ transport.delegate = nil;
+ server.delegate = nil;
+
+ [transport disconnect];
+ transport = nil;
+ transportDelegateMock = nil;
+
+ [server teardown];
+ server = nil;
+ serverDelegateMock = nil;
+ });
+
+ it(@"Should be able to connect to specified TCP server and disconnect from it", ^ {
+ BOOL ret = [server setup:@"localhost" port:@"12345"];
+ expect(ret);
+
+ OCMExpect([serverDelegateMock onClientConnected]);
+ OCMExpect([transportDelegateMock onTransportConnected]);
+
+ [transport connect];
+
+ OCMVerifyAllWithDelay(serverDelegateMock, 0.5);
+ OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
+
+ expect(transport.ioThread != nil);
+ expect(transport.inputStream != nil);
+ expect(transport.outputStream != nil);
+
+ [transport disconnect];
+
+ expect(transport.ioThread == nil);
+ expect(transport.inputStream == nil);
+ expect(transport.outputStream == nil);
+ });
+
+ it(@"Should invoke onError delegate when connection is refused", ^ {
+ // Start the server without SO_REUSEADDR then close it. Then the port will not be owned by anybody for a while.
+ server.enableSOReuseAddr = NO;
+ BOOL ret = [server setup:@"localhost" port:@"12346"];
+ expect(ret);
+ [server teardown];
+ server = nil;
+
+ OCMExpect([transportDelegateMock onError:[OCMArg checkWithBlock:^BOOL(NSError *error) {
+ if (error.domain == SDLErrorDomainTransport && error.code == SDLTransportErrorConnectionRefused) {
+ return YES;
+ } else {
+ return NO;
+ }
+ }]]);
+
+ transport.portNumber = @"12346";
+ [transport connect];
+
+ OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
+
+ [transport disconnect];
+
+ expect(transport.ioThread == nil);
+ expect(transport.inputStream == nil);
+ expect(transport.outputStream == nil);
+ });
+
+ it(@"Should invoke onError delegate when connection is timed out", ^ {
+ OCMExpect([transportDelegateMock onError:[OCMArg checkWithBlock:^BOOL(NSError *error) {
+ if (error.domain == SDLErrorDomainTransport && error.code == SDLTransportErrorConnectionTimedOut) {
+ return YES;
+ } else {
+ return NO;
+ }
+ }]]);
+
+ transport.hostName = @"127.0.0.2";
+ [transport connect];
+
+ // timeout value should be longer than 'ConnectionTimeoutSecs' in SDLTCPTransport
+ OCMVerifyAllWithDelay(transportDelegateMock, 60.0);
+
+ [transport disconnect];
+
+ expect(transport.ioThread == nil);
+ expect(transport.inputStream == nil);
+ expect(transport.outputStream == nil);
+ });
+
+ it(@"Should invoke onError delegate when input parameter is invalid", ^ {
+ OCMExpect([transportDelegateMock onError:[OCMArg checkWithBlock:^BOOL(NSError *error) {
+ if (error.domain == SDLErrorDomainTransport && error.code == SDLTransportErrorUnknown) {
+ return YES;
+ } else {
+ return NO;
+ }
+ }]]);
+
+ transport.portNumber = @"abcde";
+ [transport connect];
+
+ OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
+
+ [transport disconnect];
+
+ expect(transport.ioThread == nil);
+ expect(transport.inputStream == nil);
+ expect(transport.outputStream == nil);
+ });
+
+ it(@"Should send out data when send is called", ^ {
+ BOOL ret = [server setup:@"localhost" port:@"12345"];
+ expect(ret);
+
+ char buf[256];
+ snprintf(buf, sizeof(buf), "This is dummy message.");
+ NSData *testData = [NSData dataWithBytes:buf length:strlen(buf)];
+ NSMutableData *receivedData = [[NSMutableData alloc] init];
+
+ OCMExpect([serverDelegateMock onClientConnected]);
+ OCMStub([serverDelegateMock onClientDataReceived:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
+ __unsafe_unretained NSData *data;
+ [invocation getArgument:&data atIndex:2]; // first argument is index 2
+ [receivedData appendData:data];
+ NSLog(@"mock server received %lu bytes", data.length);
+ });
+
+ OCMExpect([transportDelegateMock onTransportConnected]);
+
+ [transport connect];
+ [transport sendData:testData];
+
+ OCMVerifyAllWithDelay(serverDelegateMock, 0.5);
+ OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
+
+ [NSThread sleepForTimeInterval:0.5];
+ expect([receivedData isEqualToData:testData]);
+
+ [transport disconnect];
+ });
+
+ it(@"Should send out data even if send is called some time after", ^ {
+ BOOL ret = [server setup:@"localhost" port:@"12345"];
+ expect(ret);
+
+ char buf1[256], buf2[256];
+ snprintf(buf1, sizeof(buf1), "This is another dummy message.");
+ snprintf(buf2, sizeof(buf2), "followed by 12345678901234567890123456");
+ NSData *testData1 = [NSData dataWithBytes:buf1 length:strlen(buf1)];
+ NSData *testData2 = [NSData dataWithBytes:buf2 length:strlen(buf2)];
+ NSMutableData *expectedData = [NSMutableData dataWithData:testData1];
+ [expectedData appendData:testData2];
+
+ __block NSMutableData *receivedData = [[NSMutableData alloc] init];
+
+ OCMExpect([serverDelegateMock onClientConnected]);
+ OCMStub([serverDelegateMock onClientDataReceived:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
+ __unsafe_unretained NSData *data;
+ [invocation getArgument:&data atIndex:2]; // first argument is index 2
+ [receivedData appendData:data];
+ NSLog(@"mock server received %lu bytes", data.length);
+ });
+
+ OCMExpect([transportDelegateMock onTransportConnected]);
+
+ [transport connect];
+
+ // check that transport still sends out data long after NSStreamEventHasSpaceAvailable event
+ [NSThread sleepForTimeInterval:1.0];
+ [transport sendData:testData1];
+ [transport sendData:testData2];
+
+ OCMVerifyAllWithDelay(serverDelegateMock, 0.5);
+ OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
+
+ [NSThread sleepForTimeInterval:0.5];
+ expect([receivedData isEqualToData:expectedData]);
+
+ // don't receive further delegate events
+ server.delegate = nil;
+
+ [transport disconnect];
+ });
+
+ it(@"Should invoke onDataReceived delegate when received some data", ^ {
+ BOOL ret = [server setup:@"localhost" port:@"12345"];
+ expect(ret);
+
+ char buf1[256], buf2[256];
+ snprintf(buf1, sizeof(buf1), "This is test data.");
+ snprintf(buf2, sizeof(buf2), "This is another chunk of data.");
+ NSData *testData1 = [NSData dataWithBytes:buf1 length:strlen(buf1)];
+ NSData *testData2 = [NSData dataWithBytes:buf2 length:strlen(buf2)];
+ NSMutableData *expectedData = [NSMutableData dataWithData:testData1];
+ [expectedData appendData:testData2];
+
+ OCMExpect([transportDelegateMock onTransportConnected]);
+
+ NSMutableData *receivedData = [[NSMutableData alloc] init];
+ OCMStub([transportDelegateMock onDataReceived:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) {
+ __unsafe_unretained NSData *data;
+ [invocation getArgument:&data atIndex:2]; // first argument is index 2
+ [receivedData appendData:data];
+ NSLog(@"client received %lu bytes", data.length);
+ });
+
+ OCMExpect([serverDelegateMock onClientConnected]);
+
+ [transport connect];
+
+ // wait until connected
+ OCMVerifyAllWithDelay(serverDelegateMock, 0.5);
+ [server send:testData1];
+ [server send:testData2];
+
+ OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
+
+ [NSThread sleepForTimeInterval:0.5];
+ expect([receivedData isEqualToData:expectedData]);
+
+ [transport disconnect];
+ });
+
+ it(@"Should generate disconnected event after peer closed connection", ^ {
+ BOOL ret = [server setup:@"localhost" port:@"12345"];
+ expect(ret);
+
+ OCMExpect([serverDelegateMock onClientConnected]);
+ OCMExpect([transportDelegateMock onTransportConnected]);
+
+ [transport connect];
+
+ OCMVerifyAllWithDelay(serverDelegateMock, 0.5);
+ OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
+
+ OCMExpect([transportDelegateMock onTransportDisconnected]);
+
+ // Close the writing connection. This will notify the client that peer closed the connection.
+ ret = [server shutdownClient];
+ expect(ret);
+
+ OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
+
+ [transport disconnect];
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLink_Example/MenuManager.m b/SmartDeviceLink_Example/MenuManager.m
index 1a7a86db3..083d368d8 100644
--- a/SmartDeviceLink_Example/MenuManager.m
+++ b/SmartDeviceLink_Example/MenuManager.m
@@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN
NSMutableArray *submenuItems = [[NSMutableArray alloc] init];
NSArray<NSString *> *allVehicleDataTypes = [self sdlex_allVehicleDataTypes];
for (NSString *vehicleDataType in allVehicleDataTypes) {
- SDLMenuCell *cell = [[SDLMenuCell alloc] initWithTitle:vehicleDataType icon:nil voiceCommands:@[vehicleDataType] handler:^(SDLTriggerSource _Nonnull triggerSource) {
+ SDLMenuCell *cell = [[SDLMenuCell alloc] initWithTitle:vehicleDataType icon:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {
[VehicleDataManager getAllVehicleDataWithManager:manager triggerSource:triggerSource vehicleDataType:vehicleDataType];
}];
[submenuItems addObject:cell];