diff options
author | Jacob Keeler <jacob.keeler@livioradio.com> | 2018-07-06 08:48:13 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-06 08:48:13 -0700 |
commit | f80e75e030ab3e628c5202e2ed84095ebb861856 (patch) | |
tree | 1ffbedcb2f4d1120e57c55e74e3fb758621ab079 | |
parent | 92d03d64236864a191c5e8ab9fa3c1c35111a486 (diff) | |
parent | c3802dd5446363a840835c9111eb64b043b0d8f1 (diff) | |
download | sdl_core-f80e75e030ab3e628c5202e2ed84095ebb861856.tar.gz |
Merge pull request #2164 from XevoInc/feat/mt_transport_changes
Feat/mt transport changes
98 files changed, 8661 insertions, 334 deletions
diff --git a/src/appMain/smartDeviceLink.ini b/src/appMain/smartDeviceLink.ini index aa15b7c5fe..444affc06c 100644 --- a/src/appMain/smartDeviceLink.ini +++ b/src/appMain/smartDeviceLink.ini @@ -190,6 +190,9 @@ OpenAttemptTimeoutMs = 500 [TransportManager] ; Listening port form incoming TCP mobile connection TCPAdapterPort = 12345 +; Name of the network interface that Core will listen on for incoming TCP connection, e.g. eth0. +; If the name is omitted, Core will listen on all network interfaces by binding to INADDR_ANY. +TCPAdapterNetworkInterface = [ProtocolHandler] ; SDL supported protocol version @@ -264,6 +267,57 @@ AttemptsToOpenResumptionDB = 5 ; Timeout between attempts during opening DB in milliseconds OpenAttemptTimeoutMsResumptionDB = 500 +[TransportRequiredForResumption] +; This section specifies transport types that are required to trigger resumption for each +; AppHMIType. App has to be connected through at least one of the transports listed (either as +; the primary transport or secondary transport) to trigger resumption. If the app is not +; connected with any of the transports listed, its HMIlevel will be kept in NONE and the state +; stays in NOT_AUDIBLE. +; In case an app has multiple AppHMIType, requirements of all of the AppHMITypes are applied. +; +; Possible AppHMITypes: Default, Communication, Media, Messaging, Navigation, Information, +; Social, BackgroundProcess, Testing, System, Projection, RemoteControl, +; EmptyApp +; Possible transport types: TCP_WIFI, IAP_CARPLAY, IAP_USB_HOST_MODE, IAP_USB_DEVICE_MODE, +; IAP_USB, AOA_USB, IAP_BLUETOOTH, SPP_BLUETOOTH +; +; The default behavior is to always enable resumption. If an AppHMIType is not listed in this +; section, resumption is enabled for an app with the AppHMIType. +; On the other hand, if you want to disable resumption and always keep an app in NONE and +; NOT_AUDIBLE state after registration, specify an empty value for the AppHMIType. +; +; NAVIGATION apps, PROJECTION apps and apps that declare themselves as media apps have a +; special exception. When these apps do not have any of the transports listed here, they will +; be still resumed into particular HMIlevel defined in LowBandwidthTransportResumptionLevel +; section. + +;DefaultTransportRequiredForResumption = +;CommunicationTransportRequiredForResumption = +;MediaTransportRequiredForResumption = +;MessagingTransportRequiredForResumption = +;NavigationTransportRequiredForResumption = +;InformationTransportRequiredForResumption = +;SocialTransportRequiredForResumption = +;BackgroundProcessTransportRequiredForResumption = +;TestingTransportRequiredForResumption = +;SystemTransportRequiredForResumption = +;ProjectionTransportRequiredForResumption = +;RemoteControlTransportRequiredForResumption = +; "EmptyAppTransportRequiredForResumption" applies to apps that don't specify any AppHMIType +;EmptyAppTransportRequiredForResumption = + +[LowBandwidthTransportResumptionLevel] +; The HMI Level that an app will resume to if no high bandwidth connection is active. +; High bandwidth connections for each app type are defined under TransportRequiredForResumption +; section. +; Possible values: NONE, BACKGROUND, LIMITED and FULL +; this is for NAVIGATION apps +;NavigationLowBandwidthResumptionLevel = +; this is for PROJECTION apps +;ProjectionLowBandwidthResumptionLevel = +; this is for apps who declare themselves as media apps. (Don't be confused with AppHMIType=MEDIA.) +;MediaLowBandwidthResumptionLevel = + [AppLaunch] ; time in milliseconds started from device connection - after expiring SDL remotely launches all known not-yet-registered apps from this device AppLaunchWaitTime = 5000 @@ -279,3 +333,25 @@ MaxNumberOfiOSDevice = 10 WaitTimeBetweenApps = 4000 ; App Launch on iOS devices SDL feature enabler/disabler EnableAppLaunchIOS = true + +[MultipleTransports] +; Whether multiple-transports feature is enabled +;MultipleTransportsEnabled = true + +; Comma-separated list of transports that can be used as Secondary Transport for each Primary Transport. +; Possible values are: WiFi, USB and Bluetooth. +; Core will not suggest Secondary Transport if the value is empty. +;SecondaryTransportForBluetooth = WiFi +;SecondaryTransportForUSB = +;SecondaryTransportForWiFi = + +[ServicesMap] +; A matrix to specify which service is allowed on which transports. The transports are listed +; in preferred order. If a transport is not listed, then the service is not allowed +; to run on the transport. +; Only video and audio services are configurable. +; If the entry of a service is completely omitted, the service will be allowed on all transports. +; Possible values are: IAP_BLUETOOTH, IAP_USB, IAP_USB_HOST_MODE, IAP_USB_DEVICE_MODE, IAP_CARPLAY, SPP_BLUETOOTH, AOA_USB and TCP_WIFI. +; Note: this configuration is applied even if multiple-transports feature is not enabled. +;AudioServiceTransports = TCP_WIFI, IAP_CARPLAY, IAP_USB_HOST_MODE, IAP_USB_DEVICE_MODE, IAP_USB, AOA_USB +;VideoServiceTransports = TCP_WIFI, IAP_CARPLAY, IAP_USB_HOST_MODE, IAP_USB_DEVICE_MODE, IAP_USB, AOA_USB diff --git a/src/components/application_manager/include/application_manager/application.h b/src/components/application_manager/include/application_manager/application.h index 0166624ef2..df5819c1eb 100644 --- a/src/components/application_manager/include/application_manager/application.h +++ b/src/components/application_manager/include/application_manager/application.h @@ -551,6 +551,12 @@ class Application : public virtual InitialApplicationData, const = 0; virtual const std::string& app_icon_path() const = 0; virtual connection_handler::DeviceHandle device() const = 0; + /** + * @brief Returns handle of the device on which secondary transport of this + * app is running + * @return handle of the device on which secondary transport is running + */ + virtual connection_handler::DeviceHandle secondary_device() const = 0; /** * @brief sets true if application has sent TTS GlobalProperties @@ -588,6 +594,13 @@ class Application : public virtual InitialApplicationData, virtual void set_app_allowed(const bool allowed) = 0; DEPRECATED virtual void set_device( connection_handler::DeviceHandle device) = 0; + /** + * @brief Sets the handle of the device on which secondary transport of this + * app is running + * @param handle of the device on which secondary transport is running + */ + virtual void set_secondary_device( + connection_handler::DeviceHandle secondary_device) = 0; virtual uint32_t get_grammar_id() const = 0; virtual void set_grammar_id(uint32_t value) = 0; @@ -598,6 +611,24 @@ class Application : public virtual InitialApplicationData, virtual void set_is_resuming(bool is_resuming) = 0; virtual bool is_resuming() const = 0; + /** + * @brief Remembers the HMI level which the app would resume into if high- + * bandwidth transport were available. + * @param level The HMI level which the app would resume into. Specify + * INVALID_ENUM to clear the state. + */ + virtual void set_deferred_resumption_hmi_level( + mobile_api::HMILevel::eType level) = 0; + /** + * @brief Returns the HMI level which the app would resume into if high- + * bandwidth transport were available. + * + * A value of INVALID_ENUM indicates that the app does not have deferred + * HMI level. + * @return HMI level which the app would resume into + */ + virtual mobile_api::HMILevel::eType deferred_resumption_hmi_level() const = 0; + virtual bool AddFile(const AppFile& file) = 0; virtual const AppFilesMap& getAppFiles() const = 0; diff --git a/src/components/application_manager/include/application_manager/application_impl.h b/src/components/application_manager/include/application_manager/application_impl.h index 496f591a50..91eaffa0a0 100644 --- a/src/components/application_manager/include/application_manager/application_impl.h +++ b/src/components/application_manager/include/application_manager/application_impl.h @@ -175,6 +175,7 @@ class ApplicationImpl : public virtual Application, const; const std::string& app_icon_path() const; connection_handler::DeviceHandle device() const; + connection_handler::DeviceHandle secondary_device() const; const std::string& mac_address() const OVERRIDE; const std::string& bundle_id() const OVERRIDE; void set_bundle_id(const std::string& bundle_id) OVERRIDE; @@ -191,6 +192,7 @@ class ApplicationImpl : public virtual Application, bool set_app_icon_path(const std::string& path); void set_app_allowed(const bool allowed); void set_device(connection_handler::DeviceHandle device); + void set_secondary_device(connection_handler::DeviceHandle secondary_device); virtual uint32_t get_grammar_id() const; virtual void set_grammar_id(uint32_t value); bool is_audio() const OVERRIDE; @@ -202,6 +204,24 @@ class ApplicationImpl : public virtual Application, virtual void set_is_resuming(bool is_resuming); virtual bool is_resuming() const; + /** + * @brief Remembers the HMI level which the app would resume into if high- + * bandwidth transport were available. + * @param level The HMI level which the app would resume into. Specify + * INVALID_ENUM to clear the state. + */ + void set_deferred_resumption_hmi_level( + mobile_api::HMILevel::eType level) OVERRIDE; + /** + * @brief Returns the HMI level which the app would resume into if high- + * bandwidth transport were available. + * + * A value of INVALID_ENUM indicates that the app does not have deferred + * HMI level. + * @return HMI level which the app would resume into + */ + mobile_api::HMILevel::eType deferred_resumption_hmi_level() const OVERRIDE; + bool AddFile(const AppFile& file); bool UpdateFile(const AppFile& file); bool DeleteFile(const std::string& file_name); @@ -449,6 +469,7 @@ class ApplicationImpl : public virtual Application, bool audio_streaming_suspended_; sync_primitives::Lock video_streaming_suspended_lock_; sync_primitives::Lock audio_streaming_suspended_lock_; + sync_primitives::Lock streaming_stop_lock_; bool is_app_allowed_; bool has_been_activated_; @@ -462,6 +483,7 @@ class ApplicationImpl : public virtual Application, std::string app_icon_path_; std::string mac_address_; connection_handler::DeviceHandle device_id_; + connection_handler::DeviceHandle secondary_device_id_; std::string bundle_id_; AppFilesMap app_files_; std::set<mobile_apis::ButtonName::eType> subscribed_buttons_; @@ -469,6 +491,7 @@ class ApplicationImpl : public virtual Application, protocol_handler::MajorProtocolVersion protocol_version_; bool is_voice_communication_application_; sync_primitives::atomic_bool is_resuming_; + mobile_api::HMILevel::eType deferred_resumption_hmi_level_; bool is_hash_changed_during_suspend_; uint32_t video_stream_retry_number_; diff --git a/src/components/application_manager/include/application_manager/application_manager_impl.h b/src/components/application_manager/include/application_manager/application_manager_impl.h index 7433a5560f..c7ba78c60e 100644 --- a/src/components/application_manager/include/application_manager/application_manager_impl.h +++ b/src/components/application_manager/include/application_manager/application_manager_impl.h @@ -671,6 +671,10 @@ class ApplicationManagerImpl const int32_t& session_key, const protocol_handler::ServiceType& type, const connection_handler::CloseSessionReason& close_reason) OVERRIDE; + void OnSecondaryTransportStartedCallback( + const connection_handler::DeviceHandle device_handle, + const int32_t session_key) OVERRIDE; + void OnSecondaryTransportEndedCallback(const int32_t session_key) OVERRIDE; /** * @brief Check if application with specified app_id has NAVIGATION HMI type @@ -818,6 +822,18 @@ class ApplicationManagerImpl ApplicationConstSharedPtr application) const; /** + * @brief Checks if required transport for resumption is available + * + * The required transport can be configured through smartDeviceLink.ini file. + * + * @param application an instance of the app to check + * @return true if the app is connected through one of the required + * transports, false otherwise + */ + bool CheckResumptionRequiredTransportAvailable( + ApplicationConstSharedPtr application) const; + + /** * Getter for resume_controller * @return Resume Controller */ @@ -1140,6 +1156,14 @@ class ApplicationManagerImpl mobile_apis::AppHMIType::eType StringToAppHMIType(std::string str); /** + * @brief Returns a string representation of AppHMIType + * @param type an enum value of AppHMIType + * @return string representation of the enum value + */ + const std::string AppHMITypeToString( + mobile_apis::AppHMIType::eType type) const; + + /** * @brief Method compares arrays of app HMI type * @param from_policy contains app HMI type from policy * @param from_application contains app HMI type from application @@ -1396,6 +1420,15 @@ class ApplicationManagerImpl const std::string& mac_address); /** + * @brief Converts device handle to transport type string used in + * smartDeviceLink.ini file, e.g. "TCP_WIFI" + * @param device_handle A device handle + * @return string representation of the transport of the device + */ + const std::string GetTransportTypeProfileString( + connection_handler::DeviceHandle device_handle) const; + + /** * @brief Converts BSON object containing video parameters to * smart object's map object * @param output the smart object to add video parameters @@ -1523,6 +1556,12 @@ class ApplicationManagerImpl mutable sync_primitives::Lock reregister_wait_list_lock_; + // This is a cache to remember DeviceHandle of secondary transports. Only used + // during RegisterApplication(). + typedef std::map<int32_t, connection_handler::DeviceHandle> DeviceMap; + + DeviceMap secondary_transport_devices_cache_; + #ifdef TELEMETRY_MONITOR AMTelemetryObserver* metric_observer_; #endif // TELEMETRY_MONITOR diff --git a/src/components/application_manager/include/application_manager/message_helper.h b/src/components/application_manager/include/application_manager/message_helper.h index 6590098a01..763b6c3c7a 100644 --- a/src/components/application_manager/include/application_manager/message_helper.h +++ b/src/components/application_manager/include/application_manager/message_helper.h @@ -286,6 +286,24 @@ class MessageHelper { ApplicationManager& app_mngr); /* + * @brief Create Common.DeviceInfo struct from device handle + * @param device_handle device handle of the app + * @param session_observer instance of SessionObserver to retrieve device + * information + * @param policy_handler instance of PolicyHandlerInterface to get the value + * of 'isSDLAllowed' + * @param app_mngr instance of ApplicationManager + * @param output smart object to store created Common.DeviceInfo struct + * @return true on success, false otherwise + */ + static bool CreateDeviceInfo( + connection_handler::DeviceHandle device_handle, + const protocol_handler::SessionObserver& session_observer, + const policy::PolicyHandlerInterface& policy_handler, + ApplicationManager& app_mngr, + smart_objects::SmartObject* output); + + /* * @brief Create Common.HMIApplication struct application instance * @param app : applicaton instace * @param output smart object to store Common.HMIApplication struct diff --git a/src/components/application_manager/include/application_manager/resumption/resume_ctrl.h b/src/components/application_manager/include/application_manager/resumption/resume_ctrl.h index 0ec58f2419..6d26b6a617 100644 --- a/src/components/application_manager/include/application_manager/resumption/resume_ctrl.h +++ b/src/components/application_manager/include/application_manager/resumption/resume_ctrl.h @@ -158,6 +158,13 @@ class ResumeCtrl { app_mngr::ApplicationSharedPtr application) = 0; /** + * @brief Retry resumption of an app if it has been disabled or limited + * due to absence of high-bandwidth transport. + * @param app_id ID of the app to resume + */ + virtual void RetryResumption(const uint32_t app_id) = 0; + + /** * @brief Check if there are all files need for resumption * @param application that is need to be restored * @return true if it all files exist, otherwise return false @@ -264,6 +271,8 @@ class ResumeCtrl { #ifdef BUILD_TESTS virtual void set_resumption_storage( utils::SharedPtr<ResumptionData> mock_storage) = 0; + + virtual bool get_resumption_active() const = 0; #endif // BUILD_TESTS }; diff --git a/src/components/application_manager/include/application_manager/resumption/resume_ctrl_impl.h b/src/components/application_manager/include/application_manager/resumption/resume_ctrl_impl.h index d7ff621c95..9e084af66b 100644 --- a/src/components/application_manager/include/application_manager/resumption/resume_ctrl_impl.h +++ b/src/components/application_manager/include/application_manager/resumption/resume_ctrl_impl.h @@ -178,6 +178,13 @@ class ResumeCtrlImpl : public ResumeCtrl, app_mngr::ApplicationSharedPtr application) OVERRIDE; /** + * @brief Retry resumption of an app if it has been disabled or limited + * due to absence of high-bandwidth transport. + * @param app_id ID of the app to resume + */ + void RetryResumption(const uint32_t app_id) OVERRIDE; + + /** * @brief Check if there are all files need for resumption * @param application that is need to be restored * @return true if it all files exist, otherwise return false @@ -298,6 +305,8 @@ class ResumeCtrlImpl : public ResumeCtrl, #ifdef BUILD_TESTS void set_resumption_storage( utils::SharedPtr<ResumptionData> mock_storage) OVERRIDE; + + bool get_resumption_active() const OVERRIDE; #endif // BUILD_TESTS private: /** @@ -500,6 +509,19 @@ class ResumeCtrlImpl : public ResumeCtrl, const application_manager::ApplicationSharedPtr application) const; /** + * @brief Retrieve the HMI level of the app when high-bandwidth transport + *isn't available + * + * The value is configured through smartDeviceLink.ini file + * + * @param application an instance of the app + * @return HMI level that the app is allowed when high-bandwidth transport + *isn't available + */ + mobile_apis::HMILevel::eType GetHmiLevelOnLowBandwidthTransport( + app_mngr::ApplicationConstSharedPtr application) const; + + /** *@brief Mapping applications to time_stamps * wait for timer to resume HMI Level * diff --git a/src/components/application_manager/include/application_manager/smart_object_keys.h b/src/components/application_manager/include/application_manager/smart_object_keys.h index 80e475a680..ba4b5d113a 100644 --- a/src/components/application_manager/include/application_manager/smart_object_keys.h +++ b/src/components/application_manager/include/application_manager/smart_object_keys.h @@ -218,6 +218,7 @@ extern const char* did_location; extern const char* app_list; extern const char* device_list; extern const char* device_info; +extern const char* secondary_device_info; extern const char* name; extern const char* id; extern const char* isSDLAllowed; diff --git a/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/hmi/navi_audio_start_stream_request.cc b/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/hmi/navi_audio_start_stream_request.cc index 3d2370d55b..a9698d36ab 100644 --- a/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/hmi/navi_audio_start_stream_request.cc +++ b/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/hmi/navi_audio_start_stream_request.cc @@ -63,7 +63,10 @@ AudioStartStreamRequest::AudioStartStreamRequest( << "; retry_number_ = " << retry_number_); } -AudioStartStreamRequest::~AudioStartStreamRequest() {} +AudioStartStreamRequest::~AudioStartStreamRequest() { + // see comment in NaviStartStreamRequest + unsubscribe_from_event(hmi_apis::FunctionID::Navigation_StartAudioStream); +} void AudioStartStreamRequest::Run() { LOG4CXX_AUTO_TRACE(logger_); diff --git a/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/hmi/navi_start_stream_request.cc b/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/hmi/navi_start_stream_request.cc index e44d89f93a..f3d971acba 100644 --- a/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/hmi/navi_start_stream_request.cc +++ b/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/hmi/navi_start_stream_request.cc @@ -63,7 +63,11 @@ NaviStartStreamRequest::NaviStartStreamRequest( << "; retry_number_ = " << retry_number_); } -NaviStartStreamRequest::~NaviStartStreamRequest() {} +NaviStartStreamRequest::~NaviStartStreamRequest() { + // unsubscribe_from_all_events() in EventObserver's destructor isn't enough; + // we must unsubscribe before this NaviStartStreamRequest instance is removed + unsubscribe_from_event(hmi_apis::FunctionID::Navigation_StartStream); +} void NaviStartStreamRequest::Run() { LOG4CXX_AUTO_TRACE(logger_); diff --git a/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/mobile/register_app_interface_request.cc b/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/mobile/register_app_interface_request.cc index ea353c8d90..20302be585 100644 --- a/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/mobile/register_app_interface_request.cc +++ b/src/components/application_manager/rpc_plugins/sdl_rpc_plugin/src/commands/mobile/register_app_interface_request.cc @@ -938,31 +938,27 @@ void RegisterAppInterfaceRequest::SendOnAppRegisteredNotificationToHMI( application[strings::request_subtype] = SmartObject(SmartType_Array); } - application[strings::device_info] = SmartObject(SmartType_Map); - smart_objects::SmartObject& device_info = application[strings::device_info]; const protocol_handler::SessionObserver& session_observer = application_manager_.connection_handler().get_session_observer(); - std::string device_name; - std::string mac_address; - std::string transport_type; - const connection_handler::DeviceHandle handle = application_impl.device(); - if (-1 == - session_observer.GetDataOnDeviceID( - handle, &device_name, NULL, &mac_address, &transport_type)) { - LOG4CXX_ERROR(logger_, - "Failed to extract information for device " << handle); - } - device_info[strings::name] = device_name; - device_info[strings::id] = mac_address; - - const policy::DeviceConsent device_consent = - GetPolicyHandler().GetUserConsentForDevice(mac_address); - device_info[strings::isSDLAllowed] = - policy::DeviceConsent::kDeviceAllowed == device_consent; - - device_info[strings::transport_type] = - application_manager_.GetDeviceTransportType(transport_type); + application[strings::device_info] = SmartObject(SmartType_Map); + smart_objects::SmartObject& device_info = application[strings::device_info]; + MessageHelper::CreateDeviceInfo(application_impl.device(), + session_observer, + GetPolicyHandler(), + application_manager_, + &device_info); + + if (application_impl.secondary_device() != 0) { + application[strings::secondary_device_info] = SmartObject(SmartType_Map); + smart_objects::SmartObject& secondary_device_info = + application[strings::secondary_device_info]; + MessageHelper::CreateDeviceInfo(application_impl.secondary_device(), + session_observer, + GetPolicyHandler(), + application_manager_, + &secondary_device_info); + } const smart_objects::SmartObject* day_color_scheme = application_impl.day_color_scheme(); diff --git a/src/components/application_manager/src/application_impl.cc b/src/components/application_manager/src/application_impl.cc index 3242dace4d..38829e79c9 100644 --- a/src/components/application_manager/src/application_impl.cc +++ b/src/components/application_manager/src/application_impl.cc @@ -124,11 +124,13 @@ ApplicationImpl::ApplicationImpl( , list_files_in_none_count_(0) , mac_address_(mac_address) , device_id_(device_id) + , secondary_device_id_(0) , usage_report_(mobile_app_id, statistics_manager) , protocol_version_( protocol_handler::MajorProtocolVersion::PROTOCOL_VERSION_3) , is_voice_communication_application_(false) , is_resuming_(false) + , deferred_resumption_hmi_level_(mobile_api::HMILevel::eType::INVALID_ENUM) , is_hash_changed_during_suspend_(false) , video_stream_retry_number_(0) , audio_stream_retry_number_(0) @@ -389,6 +391,10 @@ connection_handler::DeviceHandle ApplicationImpl::device() const { return device_id_; } +connection_handler::DeviceHandle ApplicationImpl::secondary_device() const { + return secondary_device_id_; +} + const std::string& ApplicationImpl::mac_address() const { return mac_address_; } @@ -502,6 +508,9 @@ void ApplicationImpl::StopStreamingForce( using namespace protocol_handler; LOG4CXX_AUTO_TRACE(logger_); + // see the comment in StopStreaming() + sync_primitives::AutoLock lock(streaming_stop_lock_); + SuspendStreaming(service_type); if (service_type == ServiceType::kMobileNav) { @@ -516,6 +525,12 @@ void ApplicationImpl::StopStreaming( using namespace protocol_handler; LOG4CXX_AUTO_TRACE(logger_); + // since WakeUpStreaming() is called from another thread, it is possible that + // the stream will be restarted after we call SuspendStreaming() and before + // we call StopXxxStreaming(). To avoid such timing issue, make sure that + // we run SuspendStreaming() and StopXxxStreaming() atomically. + sync_primitives::AutoLock lock(streaming_stop_lock_); + SuspendStreaming(service_type); if (service_type == ServiceType::kMobileNav && video_streaming_approved()) { @@ -566,6 +581,10 @@ void ApplicationImpl::WakeUpStreaming( using namespace protocol_handler; LOG4CXX_AUTO_TRACE(logger_); + // See the comment in StopStreaming(). Also, please make sure that we acquire + // streaming_stop_lock_ then xxx_streaming_suspended_lock_ in this order! + sync_primitives::AutoLock lock(streaming_stop_lock_); + if (ServiceType::kMobileNav == service_type) { sync_primitives::AutoLock lock(video_streaming_suspended_lock_); if (video_streaming_suspended_) { @@ -649,6 +668,11 @@ void ApplicationImpl::set_device(connection_handler::DeviceHandle device) { device_id_ = device; } +void ApplicationImpl::set_secondary_device( + connection_handler::DeviceHandle secondary_device) { + secondary_device_id_ = secondary_device; +} + uint32_t ApplicationImpl::get_grammar_id() const { return grammar_id_; } @@ -694,6 +718,16 @@ bool ApplicationImpl::is_resuming() const { return is_resuming_; } +void ApplicationImpl::set_deferred_resumption_hmi_level( + mobile_api::HMILevel::eType level) { + deferred_resumption_hmi_level_ = level; +} + +mobile_api::HMILevel::eType ApplicationImpl::deferred_resumption_hmi_level() + const { + return deferred_resumption_hmi_level_; +} + bool ApplicationImpl::AddFile(const AppFile& file) { if (app_files_.count(file.file_name) == 0) { LOG4CXX_INFO(logger_, diff --git a/src/components/application_manager/src/application_manager_impl.cc b/src/components/application_manager/src/application_manager_impl.cc index 35deddfc8d..ad9886969b 100644 --- a/src/components/application_manager/src/application_manager_impl.cc +++ b/src/components/application_manager/src/application_manager_impl.cc @@ -93,7 +93,13 @@ DeviceTypes devicesType = { hmi_apis::Common_TransportType::BLUETOOTH), std::make_pair(std::string("BLUETOOTH_IOS"), hmi_apis::Common_TransportType::BLUETOOTH), - std::make_pair(std::string("WIFI"), hmi_apis::Common_TransportType::WIFI)}; + std::make_pair(std::string("WIFI"), hmi_apis::Common_TransportType::WIFI), + std::make_pair(std::string("USB_IOS_HOST_MODE"), + hmi_apis::Common_TransportType::USB_IOS), + std::make_pair(std::string("USB_IOS_DEVICE_MODE"), + hmi_apis::Common_TransportType::USB_IOS), + std::make_pair(std::string("CARPLAY_WIRELESS_IOS"), + hmi_apis::Common_TransportType::WIFI)}; } /** @@ -222,6 +228,8 @@ ApplicationManagerImpl::~ApplicationManagerImpl() { navi_app_to_stop_.clear(); navi_app_to_end_stream_.clear(); + + secondary_transport_devices_cache_.clear(); } DataAccessor<ApplicationSet> ApplicationManagerImpl::applications() const { @@ -644,6 +652,18 @@ ApplicationSharedPtr ApplicationManagerImpl::RegisterApplication( apps_size_ = applications_.size(); applications_list_lock_ptr_->Release(); + // It is possible that secondary transport of this app has been already + // established. Make sure that the information is reflected to application + // instance. + // Also, make sure that this is done *after* we updated applications_ list to + // avoid timing issues. + DeviceMap::iterator itr = + secondary_transport_devices_cache_.find(connection_key); + if (secondary_transport_devices_cache_.end() != itr) { + connection_handler::DeviceHandle secondary_device_handle = itr->second; + application->set_secondary_device(secondary_device_handle); + } + return application; } @@ -1181,6 +1201,79 @@ mobile_apis::HMILevel::eType ApplicationManagerImpl::GetDefaultHmiLevel( return default_hmi; } +bool ApplicationManagerImpl::CheckResumptionRequiredTransportAvailable( + ApplicationConstSharedPtr application) const { + using namespace mobile_apis; + LOG4CXX_AUTO_TRACE(logger_); + + const std::map<std::string, std::vector<std::string> >& transport_map = + get_settings().transport_required_for_resumption_map(); + + // retrieve transport type string used in .ini file + const std::string transport_type = + GetTransportTypeProfileString(application->device()); + const std::string secondary_transport_type = + GetTransportTypeProfileString(application->secondary_device()); + + const smart_objects::SmartObject* app_types_array = application->app_types(); + if (app_types_array == NULL || app_types_array->length() == 0) { + // This app does not have any AppHMIType. In this case, check "EMPTY_APP" + // entry + std::map<std::string, std::vector<std::string> >::const_iterator it = + transport_map.find(std::string("EMPTY_APP")); + if (it == transport_map.end()) { + // if "EMPTY_APP" is not specified, resumption is always enabled + return true; + } + const std::vector<std::string>& required_transport_list = it->second; + + for (std::vector<std::string>::const_iterator itr = + required_transport_list.begin(); + itr != required_transport_list.end(); + ++itr) { + if (transport_type == *itr || secondary_transport_type == *itr) { + return true; + } + } + return false; + } else { + // check all AppHMITypes that the app has + for (size_t i = 0; i < app_types_array->length(); i++) { + const std::string app_type_string = + AppHMITypeToString(static_cast<mobile_apis::AppHMIType::eType>( + app_types_array->getElement(i).asUInt())); + bool transport_is_found = false; + + std::map<std::string, std::vector<std::string> >::const_iterator it = + transport_map.find(app_type_string); + if (it == transport_map.end()) { + // this AppHMIType is not listed in .ini file, so resumption is always + // enabled + continue; + } + + const std::vector<std::string>& required_transport_list = it->second; + for (std::vector<std::string>::const_iterator itr = + required_transport_list.begin(); + itr != required_transport_list.end(); + ++itr) { + if (transport_type == *itr || secondary_transport_type == *itr) { + transport_is_found = true; + break; + } + } + + // if neither primary or secondary transport type is included in the list, + // then resumption will be disabled + if (!transport_is_found) { + return false; + } + } + + return true; + } +} + uint32_t ApplicationManagerImpl::GenerateGrammarID() { return rand(); } @@ -1605,6 +1698,79 @@ void ApplicationManagerImpl::OnServiceEndedCallback( } } +void ApplicationManagerImpl::OnSecondaryTransportStartedCallback( + const connection_handler::DeviceHandle device_handle, + const int32_t session_key) { + LOG4CXX_AUTO_TRACE(logger_); + + if (device_handle == 0) { + LOG4CXX_WARN(logger_, + "Invalid device handle passed for secondary transport of app " + << session_key); + return; + } + + secondary_transport_devices_cache_[session_key] = device_handle; + + { + sync_primitives::AutoLock auto_lock(applications_list_lock_ptr_); + ApplicationSharedPtr app = application(session_key); + if (!app) { + // It is possible that secondary transport is established prior to + // RegisterAppInterface request being processed. In this case, we will + // update the app's information during RegisterApplication(). + LOG4CXX_DEBUG(logger_, + "Application with id: " << session_key << " is not found"); + return; + } + app->set_secondary_device(device_handle); + } + + // notify the event to HMI through BC.UpdateAppList request + SendUpdateAppList(); + + // if resumption has not been enabled, run it now + resume_controller().RetryResumption(session_key); +} + +void ApplicationManagerImpl::OnSecondaryTransportEndedCallback( + const int32_t session_key) { + LOG4CXX_AUTO_TRACE(logger_); + + DeviceMap::iterator it = secondary_transport_devices_cache_.find(session_key); + if (it == secondary_transport_devices_cache_.end()) { + LOG4CXX_WARN( + logger_, + "Unknown session_key specified while removing secondary transport: " + << session_key); + } else { + secondary_transport_devices_cache_.erase(it); + } + + { + sync_primitives::AutoLock auto_lock(applications_list_lock_ptr_); + ApplicationSharedPtr app = application(session_key); + if (!app) { + LOG4CXX_DEBUG(logger_, + "Application with id: " << session_key << " is not found"); + return; + } + + connection_handler::DeviceHandle device_handle = app->secondary_device(); + if (device_handle == 0) { + LOG4CXX_WARN(logger_, + "Secondary transport of app " << session_key + << " is not found"); + return; + } + + app->set_secondary_device(0); + } + + // notify the event to HMI through BC.UpdateAppList request + SendUpdateAppList(); +} + bool ApplicationManagerImpl::CheckAppIsNavi(const uint32_t app_id) const { LOG4CXX_AUTO_TRACE(logger_); ApplicationSharedPtr app = application(app_id); @@ -3179,6 +3345,39 @@ mobile_apis::AppHMIType::eType ApplicationManagerImpl::StringToAppHMIType( } } +const std::string ApplicationManagerImpl::AppHMITypeToString( + mobile_apis::AppHMIType::eType type) const { + LOG4CXX_AUTO_TRACE(logger_); + switch (type) { + case mobile_apis::AppHMIType::DEFAULT: + return "DEFAULT"; + case mobile_apis::AppHMIType::COMMUNICATION: + return "COMMUNICATION"; + case mobile_apis::AppHMIType::MEDIA: + return "MEDIA"; + case mobile_apis::AppHMIType::MESSAGING: + return "MESSAGING"; + case mobile_apis::AppHMIType::NAVIGATION: + return "NAVIGATION"; + case mobile_apis::AppHMIType::INFORMATION: + return "INFORMATION"; + case mobile_apis::AppHMIType::SOCIAL: + return "SOCIAL"; + case mobile_apis::AppHMIType::BACKGROUND_PROCESS: + return "BACKGROUND_PROCESS"; + case mobile_apis::AppHMIType::TESTING: + return "TESTING"; + case mobile_apis::AppHMIType::SYSTEM: + return "SYSTEM"; + case mobile_apis::AppHMIType::PROJECTION: + return "PROJECTION"; + case mobile_apis::AppHMIType::REMOTE_CONTROL: + return "REMOTE_CONTROL"; + default: + return "INVALID_ENUM"; + } +} + bool ApplicationManagerImpl::CompareAppHMIType( const smart_objects::SmartObject& from_policy, const smart_objects::SmartObject& from_application) { @@ -3478,6 +3677,16 @@ const std::set<int32_t> ApplicationManagerImpl::GetAppsSubscribedForWayPoints() return subscribed_way_points_apps_list_; } +// retrieve transport type string used in .ini file, e.g. "TCP_WIFI" +const std::string ApplicationManagerImpl::GetTransportTypeProfileString( + connection_handler::DeviceHandle device_handle) const { + LOG4CXX_AUTO_TRACE(logger_); + + return connection_handler() + .get_session_observer() + .TransportTypeProfileStringFromDeviceHandle(device_handle); +} + static hmi_apis::Common_VideoStreamingProtocol::eType ConvertVideoProtocol( const char* str) { if (strcmp(str, "RAW") == 0) { diff --git a/src/components/application_manager/src/message_helper/message_helper.cc b/src/components/application_manager/src/message_helper/message_helper.cc index 1f1ff4ddf0..3905920d05 100644 --- a/src/components/application_manager/src/message_helper/message_helper.cc +++ b/src/components/application_manager/src/message_helper/message_helper.cc @@ -1566,6 +1566,40 @@ smart_objects::SmartObjectSPtr MessageHelper::CreateAddVRCommandToHMI( return vr_command; } +bool MessageHelper::CreateDeviceInfo( + connection_handler::DeviceHandle device_handle, + const protocol_handler::SessionObserver& session_observer, + const policy::PolicyHandlerInterface& policy_handler, + ApplicationManager& app_mngr, + smart_objects::SmartObject* output) { + DCHECK_OR_RETURN(output, false); + + std::string device_name; + std::string mac_address; + std::string transport_type; + if (-1 == + session_observer.GetDataOnDeviceID( + device_handle, &device_name, NULL, &mac_address, &transport_type)) { + LOG4CXX_ERROR(logger_, + "Failed to extract information for device " << device_handle); + } + + smart_objects::SmartObject& device_info_map = *output; + device_info_map = smart_objects::SmartObject(smart_objects::SmartType_Map); + + device_info_map[strings::name] = device_name; + device_info_map[strings::id] = mac_address; + device_info_map[strings::transport_type] = + app_mngr.GetDeviceTransportType(transport_type); + + const policy::DeviceConsent device_consent = + policy_handler.GetUserConsentForDevice(mac_address); + device_info_map[strings::isSDLAllowed] = + policy::DeviceConsent::kDeviceAllowed == device_consent; + + return true; +} + bool MessageHelper::CreateHMIApplicationStruct( ApplicationConstSharedPtr app, const protocol_handler::SessionObserver& session_observer, @@ -1587,15 +1621,6 @@ bool MessageHelper::CreateHMIApplicationStruct( const smart_objects::SmartObject* day_color_scheme = app->day_color_scheme(); const smart_objects::SmartObject* night_color_scheme = app->night_color_scheme(); - std::string device_name; - std::string mac_address; - std::string transport_type; - if (-1 == - session_observer.GetDataOnDeviceID( - app->device(), &device_name, NULL, &mac_address, &transport_type)) { - LOG4CXX_ERROR(logger_, - "Failed to extract information for device " << app->device()); - } message = smart_objects::SmartObject(smart_objects::SmartType_Map); message[strings::app_name] = app->name(); @@ -1634,15 +1659,22 @@ bool MessageHelper::CreateHMIApplicationStruct( message[strings::device_info] = smart_objects::SmartObject(smart_objects::SmartType_Map); - message[strings::device_info][strings::name] = device_name; - message[strings::device_info][strings::id] = mac_address; - const policy::DeviceConsent device_consent = - policy_handler.GetUserConsentForDevice(mac_address); - message[strings::device_info][strings::isSDLAllowed] = - policy::DeviceConsent::kDeviceAllowed == device_consent; + smart_objects::SmartObject& device_info = message[strings::device_info]; + CreateDeviceInfo( + app->device(), session_observer, policy_handler, app_mngr, &device_info); + + if (app->secondary_device() != 0) { + message[strings::secondary_device_info] = + smart_objects::SmartObject(smart_objects::SmartType_Map); + smart_objects::SmartObject& secondary_device_info = + message[strings::secondary_device_info]; + CreateDeviceInfo(app->secondary_device(), + session_observer, + policy_handler, + app_mngr, + &secondary_device_info); + } - message[strings::device_info][strings::transport_type] = - app_mngr.GetDeviceTransportType(transport_type); return true; } diff --git a/src/components/application_manager/src/resumption/resume_ctrl_impl.cc b/src/components/application_manager/src/resumption/resume_ctrl_impl.cc index b7fc9f0b70..2cdab710f1 100644 --- a/src/components/application_manager/src/resumption/resume_ctrl_impl.cc +++ b/src/components/application_manager/src/resumption/resume_ctrl_impl.cc @@ -54,6 +54,12 @@ namespace resumption { using namespace application_manager; +static mobile_api::HMILevel::eType PickHigherHmiLevel( + mobile_api::HMILevel::eType val1, mobile_api::HMILevel::eType val2); +static mobile_api::HMILevel::eType PickLowerHmiLevel( + mobile_api::HMILevel::eType val1, mobile_api::HMILevel::eType val2); +static mobile_api::HMILevel::eType ConvertHmiLevelString(const std::string str); + CREATE_LOGGERPTR_GLOBAL(logger_, "Resumption") ResumeCtrlImpl::ResumeCtrlImpl(ApplicationManager& application_manager) @@ -76,6 +82,11 @@ void ResumeCtrlImpl::set_resumption_storage( utils::SharedPtr<ResumptionData> mock_storage) { resumption_storage_ = mock_storage; } + +bool ResumeCtrlImpl::get_resumption_active() const { + sync_primitives::AutoLock auto_lock(queue_lock_); + return is_resumption_active_; +} #endif // BUILD_TESTS bool ResumeCtrlImpl::Init(resumption::LastState& last_state) { @@ -158,10 +169,38 @@ bool ResumeCtrlImpl::RestoreAppHMIState(ApplicationSharedPtr application) { if (result) { DCHECK_OR_RETURN(application, false); if (saved_app.keyExists(strings::hmi_level)) { - const HMILevel::eType saved_hmi_level = - static_cast<mobile_apis::HMILevel::eType>( - saved_app[strings::hmi_level].asInt()); - LOG4CXX_DEBUG(logger_, "Saved HMI Level is : " << saved_hmi_level); + HMILevel::eType saved_hmi_level; + if (HMILevel::eType::INVALID_ENUM != + application->deferred_resumption_hmi_level()) { + saved_hmi_level = application->deferred_resumption_hmi_level(); + LOG4CXX_INFO(logger_, + "Retry resuming into HMI level : " << saved_hmi_level); + application->set_deferred_resumption_hmi_level( + HMILevel::eType::INVALID_ENUM); + } else { + saved_hmi_level = static_cast<mobile_apis::HMILevel::eType>( + saved_app[strings::hmi_level].asInt()); + LOG4CXX_DEBUG(logger_, "Saved HMI Level is : " << saved_hmi_level); + } + + // Check one of the high-bandwidth transports (configured through + // smartDeviceLink.ini file) is available. If not, then the HMI level to + // resume into will be modified. + if (!application_manager_.CheckResumptionRequiredTransportAvailable( + application)) { + mobile_apis::HMILevel::eType low_bandwidth_level = + GetHmiLevelOnLowBandwidthTransport(application); + + application->set_deferred_resumption_hmi_level(saved_hmi_level); + + saved_hmi_level = + PickLowerHmiLevel(saved_hmi_level, low_bandwidth_level); + LOG4CXX_DEBUG( + logger_, + "High-bandwidth transport not available, app will resume into : " + << saved_hmi_level); + } + return SetAppHMIState(application, saved_hmi_level, true); } else { result = false; @@ -176,9 +215,23 @@ bool ResumeCtrlImpl::RestoreAppHMIState(ApplicationSharedPtr application) { bool ResumeCtrlImpl::SetupDefaultHMILevel(ApplicationSharedPtr application) { LOG4CXX_AUTO_TRACE(logger_); DCHECK_OR_RETURN(application, false); - mobile_apis::HMILevel::eType default_hmi = + mobile_apis::HMILevel::eType hmi_level = application_manager_.GetDefaultHmiLevel(application); - return SetAppHMIState(application, default_hmi, false); + + // Check one of the high-bandwidth transports (configured through + // smartDeviceLink.ini file) is available. If not, then the HMI level to + // resume into will be modified. + if (!application_manager_.CheckResumptionRequiredTransportAvailable( + application)) { + mobile_apis::HMILevel::eType low_bandwidth_level = + GetHmiLevelOnLowBandwidthTransport(application); + hmi_level = PickLowerHmiLevel(hmi_level, low_bandwidth_level); + LOG4CXX_DEBUG( + logger_, + "High-bandwidth transport not available, default HMI level is set to : " + << hmi_level); + } + return SetAppHMIState(application, hmi_level, false); } void ResumeCtrlImpl::ApplicationResumptiOnTimer() { @@ -353,6 +406,28 @@ bool ResumeCtrlImpl::StartResumptionOnlyHMILevel( return result; } +void ResumeCtrlImpl::RetryResumption(const uint32_t app_id) { + LOG4CXX_AUTO_TRACE(logger_); + + ApplicationSharedPtr app = application_manager_.application(app_id); + if (!app) { + LOG4CXX_WARN(logger_, "Invalid app_id = " << app_id); + return; + } + + { + sync_primitives::AutoLock auto_lock(queue_lock_); + // check and update resumption deferred flag in queue_lock_ + if (mobile_api::HMILevel::eType::INVALID_ENUM == + app->deferred_resumption_hmi_level()) { + LOG4CXX_DEBUG(logger_, "No need to retry resumption for app: " << app_id); + return; + } + } + + AddToResumptionTimerQueue(app_id); +} + void ResumeCtrlImpl::StartAppHmiStateResumption( ApplicationSharedPtr application) { using namespace date_time; @@ -375,6 +450,11 @@ void ResumeCtrlImpl::StartAppHmiStateResumption( LOG4CXX_INFO(logger_, "Resume application " << application->policy_app_id()); RestoreAppHMIState(application); + if (mobile_apis::HMILevel::eType::INVALID_ENUM != + application->deferred_resumption_hmi_level()) { + // the application has not been fully resumed + return; + } RemoveApplicationFromSaved(application); } else { LOG4CXX_INFO(logger_, @@ -677,8 +757,14 @@ bool ResumeCtrlImpl::CheckAppRestrictions( LOG4CXX_AUTO_TRACE(logger_); DCHECK_OR_RETURN(saved_app.keyExists(strings::hmi_level), false); - const HMILevel::eType hmi_level = - static_cast<HMILevel::eType>(saved_app[strings::hmi_level].asInt()); + HMILevel::eType hmi_level; + if (mobile_api::HMILevel::eType::INVALID_ENUM != + application->deferred_resumption_hmi_level()) { + hmi_level = application->deferred_resumption_hmi_level(); + } else { + hmi_level = + static_cast<HMILevel::eType>(saved_app[strings::hmi_level].asInt()); + } const bool result = Compare<HMILevel::eType, EQ, ONE>( hmi_level, HMILevel::HMI_FULL, HMILevel::HMI_LIMITED) ? true @@ -754,14 +840,19 @@ void ResumeCtrlImpl::ProcessHMIRequests( void ResumeCtrlImpl::AddToResumptionTimerQueue(const uint32_t app_id) { LOG4CXX_AUTO_TRACE(logger_); + bool run_resumption = false; queue_lock_.Acquire(); waiting_for_timer_.push_back(app_id); + + if (!is_resumption_active_) { + is_resumption_active_ = true; + run_resumption = true; + } queue_lock_.Release(); LOG4CXX_DEBUG(logger_, "Application ID " << app_id << " have been added" " to resumption queue."); - if (!is_resumption_active_) { - is_resumption_active_ = true; + if (run_resumption) { restore_hmi_level_timer_.Start( application_manager_.get_settings().app_resuming_timeout(), timer::kSingleShot); @@ -826,4 +917,97 @@ bool ResumeCtrlImpl::IsAppDataResumptionExpired( return max_ign_off_count <= application[strings::ign_off_count].asInt(); } +mobile_apis::HMILevel::eType ResumeCtrlImpl::GetHmiLevelOnLowBandwidthTransport( + ApplicationConstSharedPtr application) const { + using namespace mobile_apis; + LOG4CXX_AUTO_TRACE(logger_); + + const ApplicationManagerSettings& settings = + application_manager_.get_settings(); + const std::string& level_for_navi_app = + settings.navigation_lowbandwidth_resumption_level(); + const std::string& level_for_projection_app = + settings.projection_lowbandwidth_resumption_level(); + const std::string& level_for_media_app = + settings.media_lowbandwidth_resumption_level(); + + HMILevel::eType result_level = HMILevel::HMI_NONE; + HMILevel::eType level; + + // NAVIGATION, PROJECTION and media apps have special exceptions. Their HMI + // level can be configured through .ini file. when the app has multiple + // AppHMIType, then the highest level will be applied. + if (application->is_navi()) { + level = ConvertHmiLevelString(level_for_navi_app); + LOG4CXX_DEBUG(logger_, + "NAVIGATION apps may have level " + << level + << " while high-bandwidth transport is not available."); + result_level = PickHigherHmiLevel(level, result_level); + } + if (application->mobile_projection_enabled()) { + level = ConvertHmiLevelString(level_for_projection_app); + LOG4CXX_DEBUG(logger_, + "PROJECTION apps may have level " + << level + << " while high-bandwidth transport is not available."); + result_level = PickHigherHmiLevel(level, result_level); + } + if (application->is_media_application()) { + level = ConvertHmiLevelString(level_for_media_app); + LOG4CXX_DEBUG(logger_, + "media apps may have level " + << level + << " while high-bandwidth transport is not available."); + result_level = PickHigherHmiLevel(level, result_level); + } + + return result_level; +} + +static mobile_api::HMILevel::eType PickHigherHmiLevel( + mobile_api::HMILevel::eType val1, mobile_api::HMILevel::eType val2) { + using namespace mobile_apis; + + if (val1 == HMILevel::INVALID_ENUM) { + return val2; + } else if (val2 == HMILevel::INVALID_ENUM) { + return val1; + } + + // smaller enum value has higher level + return val1 < val2 ? val1 : val2; +} + +static mobile_api::HMILevel::eType PickLowerHmiLevel( + mobile_api::HMILevel::eType val1, mobile_api::HMILevel::eType val2) { + using namespace mobile_apis; + + if (val1 == HMILevel::INVALID_ENUM) { + return val2; + } else if (val2 == HMILevel::INVALID_ENUM) { + return val1; + } + + // bigger enum value has lower level + return val1 > val2 ? val1 : val2; +} + +static mobile_api::HMILevel::eType ConvertHmiLevelString( + const std::string str) { + using namespace mobile_apis; + + if ("BACKGROUND" == str) { + return HMILevel::HMI_BACKGROUND; + } else if ("FULL" == str) { + return HMILevel::HMI_FULL; + } else if ("LIMITED" == str) { + return HMILevel::HMI_LIMITED; + } else if ("NONE" == str) { + return HMILevel::HMI_NONE; + } else { + return HMILevel::HMI_NONE; + } +} + } // namespce resumption diff --git a/src/components/application_manager/src/smart_object_keys.cc b/src/components/application_manager/src/smart_object_keys.cc index 5514ad688a..0e221885f1 100644 --- a/src/components/application_manager/src/smart_object_keys.cc +++ b/src/components/application_manager/src/smart_object_keys.cc @@ -182,6 +182,7 @@ const char* did_location = "didLocation"; const char* app_list = "appList"; const char* device_list = "deviceList"; const char* device_info = "deviceInfo"; +const char* secondary_device_info = "secondaryDeviceInfo"; const char* name = "name"; const char* id = "id"; const char* isSDLAllowed = "isSDLAllowed"; diff --git a/src/components/application_manager/test/application_impl_test.cc b/src/components/application_manager/test/application_impl_test.cc index be2986e719..3fe646d710 100644 --- a/src/components/application_manager/test/application_impl_test.cc +++ b/src/components/application_manager/test/application_impl_test.cc @@ -64,6 +64,7 @@ using namespace mobile_apis; namespace custom_str = utils::custom_string; using ::testing::_; +using ::testing::Mock; using ::testing::Return; using ::testing::ReturnRef; using ::testing::AtLeast; @@ -106,6 +107,11 @@ class ApplicationImplTest : public ::testing::Test { HmiStatePtr initial_state = CreateTestHmiState(); app_impl->SetInitialState(initial_state); } + + virtual void TearDown() OVERRIDE { + Mock::VerifyAndClearExpectations(MockMessageHelper::message_helper_mock()); + } + HmiStatePtr CreateTestHmiState(); HmiStatePtr TestAddHmiState(HMILevel::eType hmi_lvl, @@ -827,6 +833,29 @@ TEST_F(ApplicationImplTest, PushPopMobileMessage) { EXPECT_TRUE(messages.empty()); } +TEST_F(ApplicationImplTest, SetSecondaryDeviceTest) { + connection_handler::DeviceHandle initial_device = + app_impl->secondary_device(); + EXPECT_EQ(0u, initial_device); + + connection_handler::DeviceHandle device = 123; + app_impl->set_secondary_device(device); + + EXPECT_EQ(device, app_impl->secondary_device()); +} + +TEST_F(ApplicationImplTest, SetDeferredResumptionHMILevelTest) { + using namespace mobile_api::HMILevel; + HMILevel::eType initial_deferred_level = + app_impl->deferred_resumption_hmi_level(); + EXPECT_EQ(HMILevel::eType::INVALID_ENUM, initial_deferred_level); + + HMILevel::eType deferred_level = HMILevel::eType::HMI_FULL; + app_impl->set_deferred_resumption_hmi_level(deferred_level); + + EXPECT_EQ(deferred_level, app_impl->deferred_resumption_hmi_level()); +} + } // namespace application_manager_test } // namespace components } // namespace test diff --git a/src/components/application_manager/test/application_manager_impl_test.cc b/src/components/application_manager/test/application_manager_impl_test.cc index c13a47aae5..41d1ddc128 100644 --- a/src/components/application_manager/test/application_manager_impl_test.cc +++ b/src/components/application_manager/test/application_manager_impl_test.cc @@ -71,6 +71,7 @@ namespace policy_test = test::components::policy_handler_test; namespace con_test = connection_handler_test; using testing::_; +using ::testing::An; using ::testing::Matcher; using ::testing::ByRef; using ::testing::DoAll; @@ -110,10 +111,10 @@ class ApplicationManagerImplTest : public ::testing::Test { { logger::create_log_message_loop_thread(); - Mock::VerifyAndClearExpectations(&mock_message_helper_); + Mock::VerifyAndClearExpectations(mock_message_helper_); } ~ApplicationManagerImplTest() { - Mock::VerifyAndClearExpectations(&mock_message_helper_); + Mock::VerifyAndClearExpectations(mock_message_helper_); } protected: @@ -185,6 +186,14 @@ class ApplicationManagerImplTest : public ::testing::Test { SetArgPointee<4u>(connection_type), Return(0))); } + + bool CheckResumptionRequiredTransportAvailableTest( + smart_objects::SmartObject* app_types_array, + connection_handler::DeviceHandle primary_device_handle, + std::string primary_transport_device_string, + connection_handler::DeviceHandle secondary_device_handle, + std::string secondary_transport_device_string); + uint32_t app_id_; NiceMock<policy_test::MockPolicySettings> mock_policy_settings_; utils::SharedPtr<NiceMock<resumption_test::MockResumptionData> > @@ -688,6 +697,86 @@ TEST_F(ApplicationManagerImplTest, } TEST_F(ApplicationManagerImplTest, + OnSecondaryTransportStartedCallback_BeforeAppRegistration) { + const connection_handler::DeviceHandle device_handle = 1; + const int32_t session_key = 123; + + // make sure that BC.UpdateAppList is not invoked + EXPECT_CALL(*mock_message_helper_, + CreateModuleInfoSO( + hmi_apis::FunctionID::BasicCommunication_UpdateAppList, _)) + .Times(0); + + app_manager_impl_->OnSecondaryTransportStartedCallback(device_handle, + session_key); +} + +TEST_F(ApplicationManagerImplTest, + OnSecondaryTransportStartedCallback_AfterAppRegistration) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + AddMockApplication(); + EXPECT_CALL(*mock_app_ptr_, app_id()).WillRepeatedly(Return(app_id_)); + + const connection_handler::DeviceHandle device_handle = 1; + const int32_t session_key = app_id_; + + EXPECT_CALL(*mock_app_ptr_, set_secondary_device(device_handle)).Times(1); + // called by ResumeCtrlImpl::RetryResumption() + EXPECT_CALL(*mock_app_ptr_, deferred_resumption_hmi_level()) + .WillOnce(Return(mobile_api::HMILevel::eType::INVALID_ENUM)); + + smart_objects::SmartObject dummy_object(SmartType_Map); + SmartObjectSPtr sptr = MakeShared<SmartObject>(dummy_object); + + EXPECT_CALL(*mock_message_helper_, + CreateModuleInfoSO( + hmi_apis::FunctionID::BasicCommunication_UpdateAppList, _)) + .WillOnce(Return(sptr)); + + app_manager_impl_->OnSecondaryTransportStartedCallback(device_handle, + session_key); +} + +TEST_F(ApplicationManagerImplTest, + OnSecondaryTransportEndedCallback_AfterAppRegistration) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + const connection_handler::DeviceHandle device_handle = 1; + const int32_t session_key = app_id_; + + AddMockApplication(); + EXPECT_CALL(*mock_app_ptr_, app_id()).WillRepeatedly(Return(app_id_)); + EXPECT_CALL(*mock_app_ptr_, secondary_device()) + .WillRepeatedly(Return(device_handle)); + + EXPECT_CALL(*mock_app_ptr_, set_secondary_device(0)).Times(1); + + smart_objects::SmartObject dummy_object(SmartType_Map); + SmartObjectSPtr sptr = MakeShared<SmartObject>(dummy_object); + + EXPECT_CALL(*mock_message_helper_, + CreateModuleInfoSO( + hmi_apis::FunctionID::BasicCommunication_UpdateAppList, _)) + .WillOnce(Return(sptr)); + + app_manager_impl_->OnSecondaryTransportEndedCallback(session_key); +} + +TEST_F(ApplicationManagerImplTest, + OnSecondaryTransportEndedCallback_BeforeAppRegistration) { + const int32_t session_key = app_id_; + + // make sure that BC.UpdateAppList is not invoked + EXPECT_CALL(*mock_message_helper_, + CreateModuleInfoSO( + hmi_apis::FunctionID::BasicCommunication_UpdateAppList, _)) + .Times(0); + + app_manager_impl_->OnSecondaryTransportEndedCallback(session_key); +} + +TEST_F(ApplicationManagerImplTest, OnDeviceSwitchingStart_ExpectPutAppsInWaitList) { utils::SharedPtr<MockApplication> switching_app_ptr = utils::MakeShared<MockApplication>(); @@ -963,6 +1052,324 @@ TEST_F(ApplicationManagerImplTest, UnregisterAnotherAppDuringAudioPassThru) { } } +static std::map<std::string, std::vector<std::string> > CreateTransportMap() { + /* + * DefaultTransportRequiredForResumption = TCP_WIFI, IAP_USB, SPP_BLUETOOTH + * MediaTransportRequiredForResumption = TCP_WIFI, AOA_USB + * NavigationTransportRequiredForResumption = AOA_USB, SPP_BLUETOOTH + * TestingTransportRequiredForResumption = + * EmptyAppTransportRequiredForResumption = TCP_WIFI + */ + std::string TCP_WIFI("TCP_WIFI"); + std::string IAP_USB("IAP_USB"); + std::string SPP_BLUETOOTH("SPP_BLUETOOTH"); + std::string AOA_USB("AOA_USB"); + + std::vector<std::string> default_transports; + default_transports.push_back(TCP_WIFI); + default_transports.push_back(IAP_USB); + default_transports.push_back(SPP_BLUETOOTH); + std::vector<std::string> media_transports; + media_transports.push_back(TCP_WIFI); + media_transports.push_back(AOA_USB); + std::vector<std::string> navi_transports; + navi_transports.push_back(AOA_USB); + navi_transports.push_back(SPP_BLUETOOTH); + std::vector<std::string> testing_transports; + std::vector<std::string> empty_transports; + empty_transports.push_back(TCP_WIFI); + + std::map<std::string, std::vector<std::string> > transport_map; + transport_map[std::string("DEFAULT")] = default_transports; + transport_map[std::string("MEDIA")] = media_transports; + transport_map[std::string("NAVIGATION")] = navi_transports; + transport_map[std::string("TESTING")] = testing_transports; + transport_map[std::string("EMPTY_APP")] = empty_transports; + + return transport_map; +} + +bool ApplicationManagerImplTest::CheckResumptionRequiredTransportAvailableTest( + smart_objects::SmartObject* app_types_array, + connection_handler::DeviceHandle primary_device_handle, + std::string primary_transport_device_string, + connection_handler::DeviceHandle secondary_device_handle, + std::string secondary_transport_device_string) { + EXPECT_CALL(*mock_app_ptr_, app_types()) + .WillRepeatedly(Return(app_types_array)); + + std::map<std::string, std::vector<std::string> > transport_map = + CreateTransportMap(); + + EXPECT_CALL(mock_application_manager_settings_, + transport_required_for_resumption_map()) + .WillRepeatedly(ReturnRef(transport_map)); + + EXPECT_CALL(*mock_app_ptr_, device()) + .WillRepeatedly(Return(primary_device_handle)); + EXPECT_CALL(*mock_app_ptr_, secondary_device()) + .WillRepeatedly(Return(secondary_device_handle)); + + EXPECT_CALL(mock_session_observer_, + TransportTypeProfileStringFromDeviceHandle(primary_device_handle)) + .WillOnce(Return(primary_transport_device_string)); + + if (secondary_device_handle != 0) { + EXPECT_CALL( + mock_session_observer_, + TransportTypeProfileStringFromDeviceHandle(secondary_device_handle)) + .WillOnce(Return(secondary_transport_device_string)); + } else { + EXPECT_CALL(mock_session_observer_, + TransportTypeProfileStringFromDeviceHandle( + secondary_device_handle)).WillOnce(Return(std::string(""))); + } + + return app_manager_impl_->CheckResumptionRequiredTransportAvailable( + mock_app_ptr_); +} + +TEST_F(ApplicationManagerImplTest, + CheckResumptionRequiredTransportAvailableTest_PrimaryOnly_Success) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + smart_objects::SmartObject app_types_array(SmartType_Array); + app_types_array[0] = mobile_apis::AppHMIType::eType::DEFAULT; + + const connection_handler::DeviceHandle primary_device_handle = 1; + const connection_handler::DeviceHandle secondary_device_handle = 0; + + // refer to transport_adapter_impl.cc + std::string primary_transport_device_string("SPP_BLUETOOTH"); + std::string secondary_transport_device_string(""); + + // - The app is DEFAULT. + // - A DEFAULT app is allowed for resumption if either primary or secondary + // transport is TCP_WIFI, IAP_USB or SPP_BLUETOOTH. + // - We have SPP_BLUETOOTH for primary transport. + // -> Conclusion: the app has required transport. + bool result = CheckResumptionRequiredTransportAvailableTest( + &app_types_array, + primary_device_handle, + primary_transport_device_string, + secondary_device_handle, + secondary_transport_device_string); + EXPECT_TRUE(result); +} + +TEST_F(ApplicationManagerImplTest, + CheckResumptionRequiredTransportAvailableTest_PrimaryOnly_NotListed) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + smart_objects::SmartObject app_types_array(SmartType_Array); + app_types_array[0] = mobile_apis::AppHMIType::eType::SOCIAL; + + const connection_handler::DeviceHandle primary_device_handle = 1; + const connection_handler::DeviceHandle secondary_device_handle = 0; + + std::string primary_transport_device_string("SPP_BLUETOOTH"); + std::string secondary_transport_device_string(""); + + // - The app is SOCIAL. + // - We do not have an entry in .ini file for SOCIAL apps. + // -> In this case, resumption is always enabled for backward compatibility. + bool result = CheckResumptionRequiredTransportAvailableTest( + &app_types_array, + primary_device_handle, + primary_transport_device_string, + secondary_device_handle, + secondary_transport_device_string); + EXPECT_TRUE(result); +} + +TEST_F(ApplicationManagerImplTest, + CheckResumptionRequiredTransportAvailableTest_PrimaryOnly_Disabled) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + smart_objects::SmartObject app_types_array(SmartType_Array); + app_types_array[0] = mobile_apis::AppHMIType::eType::TESTING; + + const connection_handler::DeviceHandle primary_device_handle = 1; + const connection_handler::DeviceHandle secondary_device_handle = 0; + + std::string primary_transport_device_string("SPP_BLUETOOTH"); + std::string secondary_transport_device_string(""); + + // - The app is TESTING. + // - We do not have any transports allowed for TESTING apps. + // -> In this case, resumption is always disabled. + bool result = CheckResumptionRequiredTransportAvailableTest( + &app_types_array, + primary_device_handle, + primary_transport_device_string, + secondary_device_handle, + secondary_transport_device_string); + EXPECT_FALSE(result); +} + +TEST_F(ApplicationManagerImplTest, + CheckResumptionRequiredTransportAvailableTest_PrimaryOnly_NoAppTypes) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + smart_objects::SmartObject app_types_array(SmartType_Array); + // we don't specify any app type + + const connection_handler::DeviceHandle primary_device_handle = 1; + const connection_handler::DeviceHandle secondary_device_handle = 0; + + std::string primary_transport_device_string("SPP_BLUETOOTH"); + std::string secondary_transport_device_string(""); + + // - The app doesn't specify AppHMIType. + // - .ini file specifies TCP_WIFI for EMPTY_APP entry. + // -> The app does not have required transport. + bool result = CheckResumptionRequiredTransportAvailableTest( + &app_types_array, + primary_device_handle, + primary_transport_device_string, + secondary_device_handle, + secondary_transport_device_string); + EXPECT_FALSE(result); +} + +TEST_F(ApplicationManagerImplTest, + CheckResumptionRequiredTransportAvailableTest_PrimaryOnly_NoAppTypes2) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + const connection_handler::DeviceHandle primary_device_handle = 1; + const connection_handler::DeviceHandle secondary_device_handle = 0; + + std::string primary_transport_device_string("SPP_BLUETOOTH"); + std::string secondary_transport_device_string(""); + + // - The app doesn't specify AppHMIType. + // - .ini file specifies TCP_WIFI for EMPTY_APP entry. + // -> The app does not have required transport. + bool result = CheckResumptionRequiredTransportAvailableTest( + NULL, + primary_device_handle, + primary_transport_device_string, + secondary_device_handle, + secondary_transport_device_string); + EXPECT_FALSE(result); +} + +TEST_F(ApplicationManagerImplTest, + CheckResumptionRequiredTransportAvailableTest_TwoTransports_Success) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + smart_objects::SmartObject app_types_array(SmartType_Array); + app_types_array[0] = mobile_apis::AppHMIType::eType::MEDIA; + + const connection_handler::DeviceHandle primary_device_handle = 1; + const connection_handler::DeviceHandle secondary_device_handle = 2; + + // refer to transport_adapter_impl.cc + std::string primary_transport_device_string("SPP_BLUETOOTH"); + std::string secondary_transport_device_string("TCP_WIFI"); + + // - The app is MEDIA. + // - A MEDIA app is allowed for resumption if either primary or secondary + // transport is TCP_WIFI or AOA_USB. + // - We have TCP_WIFI for secondary transport. + // -> Conclusion: the app has required transport. + bool result = CheckResumptionRequiredTransportAvailableTest( + &app_types_array, + primary_device_handle, + primary_transport_device_string, + secondary_device_handle, + secondary_transport_device_string); + EXPECT_TRUE(result); +} + +TEST_F(ApplicationManagerImplTest, + CheckResumptionRequiredTransportAvailableTest_TwoTransports_Failure) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + smart_objects::SmartObject app_types_array(SmartType_Array); + app_types_array[0] = mobile_apis::AppHMIType::eType::NAVIGATION; + + const connection_handler::DeviceHandle primary_device_handle = 1; + const connection_handler::DeviceHandle secondary_device_handle = 2; + + // refer to transport_adapter_impl.cc + std::string primary_transport_device_string("IAP_USB"); + std::string secondary_transport_device_string("TCP_WIFI"); + + // - The app is NAVIGATION. + // - A NAVIGATION app is allowed for resumption if either primary or secondary + // transport is AOA_USB or SPP_BLUETOOTH. + // - We have IAP_USB for primary and TCP_WIFI for secondary transport. + // -> Conclusion: the app does not have required transport. + bool result = CheckResumptionRequiredTransportAvailableTest( + &app_types_array, + primary_device_handle, + primary_transport_device_string, + secondary_device_handle, + secondary_transport_device_string); + EXPECT_FALSE(result); +} + +TEST_F(ApplicationManagerImplTest, + CheckResumptionRequiredTransportAvailableTest_MultipleAppTypes_Failure) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + smart_objects::SmartObject app_types_array(SmartType_Array); + app_types_array[0] = mobile_apis::AppHMIType::eType::MEDIA; + app_types_array[1] = mobile_apis::AppHMIType::eType::NAVIGATION; + + const connection_handler::DeviceHandle primary_device_handle = 1; + const connection_handler::DeviceHandle secondary_device_handle = 2; + + std::string primary_transport_device_string("IAP_USB"); + std::string secondary_transport_device_string("TCP_WIFI"); + + // - The app is MEDIA and NAVIGATION. + // - A MEDIA app is allowed for resumption if either primary or secondary + // transport is TCP_WIFI or AOA_USB. + // - A NAVIGATION app is allowed for resumption if either primary or secondary + // transport is AOA_USB or SPP_BLUETOOTH. + // - We have IAP_USB for primary and TCP_WIFI is secondary + // -> Conclusion: the app does NOT have required transport. + bool result = CheckResumptionRequiredTransportAvailableTest( + &app_types_array, + primary_device_handle, + primary_transport_device_string, + secondary_device_handle, + secondary_transport_device_string); + EXPECT_FALSE(result); +} + +TEST_F(ApplicationManagerImplTest, + CheckResumptionRequiredTransportAvailableTest_MultipleAppTypes_Empty) { + using namespace NsSmartDeviceLink::NsSmartObjects; + + smart_objects::SmartObject app_types_array(SmartType_Array); + app_types_array[0] = mobile_apis::AppHMIType::eType::NAVIGATION; + app_types_array[1] = mobile_apis::AppHMIType::eType::SYSTEM; + + const connection_handler::DeviceHandle primary_device_handle = 1; + const connection_handler::DeviceHandle secondary_device_handle = 2; + + std::string primary_transport_device_string("IAP_USB"); + std::string secondary_transport_device_string("TCP_WIFI"); + + // - The app is NAVIGATION and SYSTEM. + // - A NAVIGATION app is allowed for resumption if either primary or secondary + // transport is AOA_USB or SPP_BLUETOOTH. + // - .ini file does not have an entry for SYSTEM apps. So any transport is + // allowed. + // - We have SPP_BLUETOOTH for primary and TCP_WIFI is secondary + // -> Conclusion: the app does NOT have required transport. + bool result = CheckResumptionRequiredTransportAvailableTest( + &app_types_array, + primary_device_handle, + primary_transport_device_string, + secondary_device_handle, + secondary_transport_device_string); + EXPECT_FALSE(result); +} + TEST_F(ApplicationManagerImplTest, RegisterApplication_PathToTheIconExists_IconWasSet) { file_system::CreateDirectory(kDirectoryName); diff --git a/src/components/application_manager/test/include/application_manager/commands/commands_test.h b/src/components/application_manager/test/include/application_manager/commands/commands_test.h index ab392c5ba0..ddd0db2cbe 100644 --- a/src/components/application_manager/test/include/application_manager/commands/commands_test.h +++ b/src/components/application_manager/test/include/application_manager/commands/commands_test.h @@ -158,14 +158,16 @@ class CommandsTest : public ::testing::Test { protected: virtual void InitCommand(const uint32_t& timeout) { + timeout_ = timeout; ON_CALL(app_mngr_, get_settings()) .WillByDefault(ReturnRef(app_mngr_settings_)); ON_CALL(app_mngr_settings_, default_timeout()) - .WillByDefault(ReturnRef(timeout)); + .WillByDefault(ReturnRef(timeout_)); } CommandsTest() - : mock_message_helper_(*am::MockMessageHelper::message_helper_mock()) { + : mock_message_helper_(*am::MockMessageHelper::message_helper_mock()) + , timeout_(0) { ON_CALL(app_mngr_, hmi_interfaces()) .WillByDefault(ReturnRef(mock_hmi_interfaces_)); ON_CALL(mock_hmi_interfaces_, GetInterfaceFromFunction(_)) @@ -223,6 +225,9 @@ class CommandsTest : public ::testing::Test { MobileResult::DATA_NOT_AVAILABLE); link_hmi_to_mob_result(HMIResult::READ_ONLY, MobileResult::READ_ONLY); } + + private: + uint32_t timeout_; }; MATCHER_P(MobileResultCodeIs, result_code, "") { diff --git a/src/components/application_manager/test/include/application_manager/mock_application.h b/src/components/application_manager/test/include/application_manager/mock_application.h index 952b485e48..626931dcf9 100644 --- a/src/components/application_manager/test/include/application_manager/mock_application.h +++ b/src/components/application_manager/test/include/application_manager/mock_application.h @@ -110,6 +110,7 @@ class MockApplication : public ::application_manager::Application { const mobile_apis::VideoStreamingState::eType()); MOCK_CONST_METHOD0(app_icon_path, const std::string&()); MOCK_CONST_METHOD0(device, connection_handler::DeviceHandle()); + MOCK_CONST_METHOD0(secondary_device, connection_handler::DeviceHandle()); MOCK_CONST_METHOD0(CurrentHmiState, const application_manager::HmiStatePtr()); MOCK_CONST_METHOD0(RegularHmiState, const application_manager::HmiStatePtr()); MOCK_CONST_METHOD0(PostponedHmiState, @@ -128,6 +129,8 @@ class MockApplication : public ::application_manager::Application { MOCK_METHOD1(set_app_icon_path, bool(const std::string& file_name)); MOCK_METHOD1(set_app_allowed, void(const bool allowed)); MOCK_METHOD1(set_device, void(connection_handler::DeviceHandle device)); + MOCK_METHOD1(set_secondary_device, + void(connection_handler::DeviceHandle secondary_device)); MOCK_CONST_METHOD0(get_grammar_id, uint32_t()); MOCK_METHOD1(set_grammar_id, void(uint32_t value)); MOCK_METHOD1( @@ -137,6 +140,10 @@ class MockApplication : public ::application_manager::Application { ::protocol_handler::MajorProtocolVersion()); MOCK_METHOD1(set_is_resuming, void(bool)); MOCK_CONST_METHOD0(is_resuming, bool()); + MOCK_METHOD1(set_deferred_resumption_hmi_level, + void(application_manager::mobile_api::HMILevel::eType level)); + MOCK_CONST_METHOD0(deferred_resumption_hmi_level, + application_manager::mobile_api::HMILevel::eType()); MOCK_METHOD1(AddFile, bool(const ::application_manager::AppFile& file)); MOCK_CONST_METHOD0(getAppFiles, const ::application_manager::AppFilesMap&()); MOCK_METHOD1(UpdateFile, bool(const ::application_manager::AppFile& file)); diff --git a/src/components/application_manager/test/include/application_manager/mock_message_helper.h b/src/components/application_manager/test/include/application_manager/mock_message_helper.h index fe17eb6788..07c3f78b51 100644 --- a/src/components/application_manager/test/include/application_manager/mock_message_helper.h +++ b/src/components/application_manager/test/include/application_manager/mock_message_helper.h @@ -230,6 +230,12 @@ class MockMessageHelper { MOCK_METHOD2(SendUIChangeRegistrationRequestToHMI, void(ApplicationConstSharedPtr app, ApplicationManager& app_mngr)); + MOCK_METHOD5(CreateDeviceInfo, + bool(connection_handler::DeviceHandle device_handle, + const protocol_handler::SessionObserver& session_observer, + const policy::PolicyHandlerInterface& policy_handler, + ApplicationManager& app_mngr, + smart_objects::SmartObject* output)); MOCK_METHOD5(CreateHMIApplicationStruct, bool(ApplicationConstSharedPtr app, const protocol_handler::SessionObserver& session_observer, diff --git a/src/components/application_manager/test/include/application_manager/mock_resume_ctrl.h b/src/components/application_manager/test/include/application_manager/mock_resume_ctrl.h index fb264b7d82..907d0c26f2 100644 --- a/src/components/application_manager/test/include/application_manager/mock_resume_ctrl.h +++ b/src/components/application_manager/test/include/application_manager/mock_resume_ctrl.h @@ -57,6 +57,7 @@ class MockResumeCtrl : public resumption::ResumeCtrl { const std::string& hash)); MOCK_METHOD1(StartResumptionOnlyHMILevel, bool(app_mngr::ApplicationSharedPtr application)); + MOCK_METHOD1(RetryResumption, void(const uint32_t app_id)); MOCK_METHOD1(CheckPersistenceFilesForResumption, bool(app_mngr::ApplicationSharedPtr application)); MOCK_METHOD2(CheckApplicationHash, @@ -96,6 +97,7 @@ class MockResumeCtrl : public resumption::ResumeCtrl { #ifdef BUILD_TESTS MOCK_METHOD1(set_resumption_storage, void(utils::SharedPtr<resumption::ResumptionData> mock_storage)); + MOCK_CONST_METHOD0(get_resumption_active, bool()); #endif // BUILD_TESTS }; diff --git a/src/components/application_manager/test/mock_message_helper.cc b/src/components/application_manager/test/mock_message_helper.cc index fc2136cdd8..4c673ae523 100644 --- a/src/components/application_manager/test/mock_message_helper.cc +++ b/src/components/application_manager/test/mock_message_helper.cc @@ -423,6 +423,16 @@ void MessageHelper::SendUIChangeRegistrationRequestToHMI( ->SendUIChangeRegistrationRequestToHMI(app, app_mngr); } +bool MessageHelper::CreateDeviceInfo( + connection_handler::DeviceHandle device_handle, + const protocol_handler::SessionObserver& session_observer, + const policy::PolicyHandlerInterface& policy_handler, + ApplicationManager& app_mngr, + smart_objects::SmartObject* output) { + return MockMessageHelper::message_helper_mock()->CreateDeviceInfo( + device_handle, session_observer, policy_handler, app_mngr, output); +} + bool MessageHelper::CreateHMIApplicationStruct( ApplicationConstSharedPtr app, const protocol_handler::SessionObserver& session_observer, diff --git a/src/components/application_manager/test/resumption/resume_ctrl_test.cc b/src/components/application_manager/test/resumption/resume_ctrl_test.cc index 1a5d070941..4a99c75c5b 100644 --- a/src/components/application_manager/test/resumption/resume_ctrl_test.cc +++ b/src/components/application_manager/test/resumption/resume_ctrl_test.cc @@ -64,6 +64,8 @@ using ::testing::NiceMock; using ::testing::Return; using ::testing::ReturnRef; using ::testing::SetArgReferee; +using ::testing::ReturnPointee; +using ::testing::SaveArg; using namespace application_manager_test; using namespace resumption; @@ -82,7 +84,11 @@ class ResumeCtrlTest : public ::testing::Test { , kHash_("saved_hash") , kAppResumingTimeout_(30000u) // miliseconds , kTestTimeStamp_(1452074434u) - , app_set_lock_ptr_(std::make_shared<sync_primitives::Lock>()) {} + , app_set_lock_ptr_(std::make_shared<sync_primitives::Lock>()) + , kDefaultDeferredTestLevel_(eType::INVALID_ENUM) + , kNaviLowbandwidthLevel_("LIMITED") + , kProjectionLowbandwidthLevel_("NONE") + , kMediaLowbandwidthLevel_("NONE") {} virtual void SetUp() OVERRIDE { Mock::VerifyAndClearExpectations(&mock_app_mngr_); @@ -103,11 +109,32 @@ class ResumeCtrlTest : public ::testing::Test { .WillByDefault(ReturnRef(mock_state_controller_)); ON_CALL(mock_app_mngr_, get_settings()) .WillByDefault(ReturnRef(mock_application_manager_settings_)); + EXPECT_CALL(mock_app_mngr_, CheckResumptionRequiredTransportAvailable(_)) + .Times(AtLeast(0)) + .WillRepeatedly(Return(true)); ON_CALL(mock_application_manager_settings_, use_db_for_resumption()) .WillByDefault(Return(false)); ON_CALL(mock_application_manager_settings_, app_resuming_timeout()) .WillByDefault(ReturnRef(kAppResumingTimeout_)); + // use EXPECTED_CALL().Times(AtLeast(0)) instead of ON_CALL to remove + // warning messages + EXPECT_CALL(mock_application_manager_settings_, + navigation_lowbandwidth_resumption_level()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(kNaviLowbandwidthLevel_)); + EXPECT_CALL(mock_application_manager_settings_, + projection_lowbandwidth_resumption_level()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(kProjectionLowbandwidthLevel_)); + EXPECT_CALL(mock_application_manager_settings_, + media_lowbandwidth_resumption_level()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(kMediaLowbandwidthLevel_)); + + EXPECT_CALL(*mock_app_, deferred_resumption_hmi_level()) + .Times(AtLeast(0)) + .WillRepeatedly(Return(kDefaultDeferredTestLevel_)); } void TearDown() OVERRIDE { Mock::VerifyAndClearExpectations(&mock_app_mngr_); @@ -143,6 +170,11 @@ class ResumeCtrlTest : public ::testing::Test { const uint32_t kAppResumingTimeout_; const uint32_t kTestTimeStamp_; std::shared_ptr<sync_primitives::Lock> app_set_lock_ptr_; + sync_primitives::Lock app_set_lock_; + const mobile_apis::HMILevel::eType kDefaultDeferredTestLevel_; + const std::string kNaviLowbandwidthLevel_; + const std::string kProjectionLowbandwidthLevel_; + const std::string kMediaLowbandwidthLevel_; }; /** @@ -544,6 +576,32 @@ TEST_F(ResumeCtrlTest, StartResumptionOnlyHMILevel) { EXPECT_TRUE(res); } +TEST_F(ResumeCtrlTest, RetryResumption) { + const uint32_t app_id = 1; + + EXPECT_CALL(mock_app_mngr_, application(app_id)).WillOnce(Return(mock_app_)); + EXPECT_CALL(*mock_app_, deferred_resumption_hmi_level()) + .WillOnce(Return(eType::HMI_FULL)); + + res_ctrl_->RetryResumption(app_id); + + bool is_resumption_active = res_ctrl_->get_resumption_active(); + EXPECT_TRUE(is_resumption_active); +} + +TEST_F(ResumeCtrlTest, RetryResumption_NotDeferred) { + const uint32_t app_id = 1; + + EXPECT_CALL(mock_app_mngr_, application(app_id)).WillOnce(Return(mock_app_)); + EXPECT_CALL(*mock_app_, deferred_resumption_hmi_level()) + .WillOnce(Return(eType::INVALID_ENUM)); + + res_ctrl_->RetryResumption(app_id); + + bool is_resumption_active = res_ctrl_->get_resumption_active(); + EXPECT_FALSE(is_resumption_active); +} + TEST_F(ResumeCtrlTest, StartAppHmiStateResumption_AppInFull) { mobile_apis::HMILevel::eType restored_test_type = eType::HMI_FULL; uint32_t ign_off_count = 0; @@ -584,6 +642,126 @@ TEST_F(ResumeCtrlTest, StartAppHmiStateResumption_AppInBackground) { res_ctrl_->StartAppHmiStateResumption(mock_app_); } +TEST_F(ResumeCtrlTest, StartAppHmiStateResumption_AppHasDeferredResumption) { + mobile_apis::HMILevel::eType restored_test_type = eType::HMI_NONE; + mobile_apis::HMILevel::eType deferred_level = eType::HMI_FULL; + uint32_t ign_off_count = 0; + smart_objects::SmartObject saved_app; + saved_app[application_manager::strings::ign_off_count] = ign_off_count; + saved_app[application_manager::strings::hmi_level] = restored_test_type; + + // resume into deferred level instead of restored level + EXPECT_CALL(mock_state_controller_, SetRegularState(_, deferred_level)) + .Times(AtLeast(1)); + GetInfoFromApp(); + ON_CALL(*mock_storage_, + GetSavedApplication(kTestPolicyAppId_, kMacAddress_, _)) + .WillByDefault(DoAll(SetArgReferee<2>(saved_app), Return(true))); + + mobile_apis::HMILevel::eType app_deferred_level = deferred_level; + EXPECT_CALL(*mock_app_, deferred_resumption_hmi_level()) + .WillRepeatedly(ReturnPointee(&app_deferred_level)); + EXPECT_CALL(*mock_app_, + set_deferred_resumption_hmi_level(eType::INVALID_ENUM)) + .WillOnce(SaveArg<0>(&app_deferred_level)); + + EXPECT_CALL(*mock_storage_, + RemoveApplicationFromSaved(kTestPolicyAppId_, kMacAddress_)) + .WillOnce(Return(true)); + + ON_CALL(mock_app_mngr_, GetUserConsentForDevice("12345")) + .WillByDefault(Return(policy::kDeviceAllowed)); + res_ctrl_->StartAppHmiStateResumption(mock_app_); +} + +TEST_F(ResumeCtrlTest, + StartAppHmiStateResumption_HighBandwidthTransportNotAvailable) { + mobile_apis::HMILevel::eType restored_test_type = eType::HMI_FULL; + uint32_t ign_off_count = 0; + smart_objects::SmartObject saved_app; + saved_app[application_manager::strings::ign_off_count] = ign_off_count; + saved_app[application_manager::strings::hmi_level] = restored_test_type; + + EXPECT_CALL(mock_state_controller_, SetRegularState(_, eType::HMI_LIMITED)) + .Times(AtLeast(1)); + GetInfoFromApp(); + ON_CALL(*mock_storage_, + GetSavedApplication(kTestPolicyAppId_, kMacAddress_, _)) + .WillByDefault(DoAll(SetArgReferee<2>(saved_app), Return(true))); + + EXPECT_CALL(mock_app_mngr_, CheckResumptionRequiredTransportAvailable(_)) + .WillOnce(Return(false)); + + EXPECT_CALL(*mock_app_, is_navi()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_app_, mobile_projection_enabled()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_app_, is_media_application()).WillRepeatedly(Return(false)); + + // if resumption is deferred ... + EXPECT_CALL(*mock_app_, deferred_resumption_hmi_level()) + .WillRepeatedly(Return(restored_test_type)); + + // ... then RemoveApplicationFromSaved() will not be called + EXPECT_CALL(*mock_storage_, + RemoveApplicationFromSaved(kTestPolicyAppId_, kMacAddress_)) + .Times(0); + + ON_CALL(mock_app_mngr_, GetUserConsentForDevice("12345")) + .WillByDefault(Return(policy::kDeviceAllowed)); + res_ctrl_->StartAppHmiStateResumption(mock_app_); +} + +TEST_F( + ResumeCtrlTest, + StartAppHmiStateResumption_HighBandwidthTransportNotAvailable_NaviAndMedia) { + mobile_apis::HMILevel::eType restored_test_type = eType::HMI_LIMITED; + uint32_t ign_off_count = 0; + smart_objects::SmartObject saved_app; + saved_app[application_manager::strings::ign_off_count] = ign_off_count; + saved_app[application_manager::strings::hmi_level] = restored_test_type; + + // in this test, it is expected that the app will resume into LIMITED, which + // is the higher level among NONE and LIMITED + EXPECT_CALL(mock_state_controller_, SetRegularState(_, eType::HMI_LIMITED)) + .Times(AtLeast(1)); + GetInfoFromApp(); + ON_CALL(*mock_storage_, + GetSavedApplication(kTestPolicyAppId_, kMacAddress_, _)) + .WillByDefault(DoAll(SetArgReferee<2>(saved_app), Return(true))); + + EXPECT_CALL(mock_app_mngr_, CheckResumptionRequiredTransportAvailable(_)) + .WillOnce(Return(false)); + + EXPECT_CALL(*mock_app_, is_navi()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_app_, mobile_projection_enabled()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_app_, is_media_application()).WillRepeatedly(Return(true)); + + std::string navi_lowbandwidth_level("NONE"); + std::string projection_lowbandwidth_level("BACKGROUND"); + std::string media_lowbandwidth_level("LIMITED"); + EXPECT_CALL(mock_application_manager_settings_, + navigation_lowbandwidth_resumption_level()) + .WillRepeatedly(ReturnRef(navi_lowbandwidth_level)); + EXPECT_CALL(mock_application_manager_settings_, + projection_lowbandwidth_resumption_level()) + .WillRepeatedly(ReturnRef(projection_lowbandwidth_level)); + EXPECT_CALL(mock_application_manager_settings_, + media_lowbandwidth_resumption_level()) + .WillRepeatedly(ReturnRef(media_lowbandwidth_level)); + + EXPECT_CALL(*mock_app_, deferred_resumption_hmi_level()) + .WillRepeatedly(Return(restored_test_type)); + + EXPECT_CALL(*mock_storage_, + RemoveApplicationFromSaved(kTestPolicyAppId_, kMacAddress_)) + .Times(0); + + ON_CALL(mock_app_mngr_, GetUserConsentForDevice("12345")) + .WillByDefault(Return(policy::kDeviceAllowed)); + res_ctrl_->StartAppHmiStateResumption(mock_app_); +} + /** * @brief Group of tests which check restoring resumption with different data */ @@ -630,6 +808,35 @@ TEST_F(ResumeCtrlTest, SetupDefaultHMILevel) { res_ctrl_->SetupDefaultHMILevel(mock_app_); } +TEST_F(ResumeCtrlTest, + SetupDefaultHMILevel_HighBandwidthTransportNotAvailable) { + smart_objects::SmartObject saved_app; + + saved_app[application_manager::strings::hmi_level] = kDefaultTestLevel_; + + ON_CALL(mock_app_mngr_, GetDefaultHmiLevel(const_app_)) + .WillByDefault(Return(kDefaultTestLevel_)); + GetInfoFromApp(); + EXPECT_CALL(mock_app_mngr_, GetUserConsentForDevice("12345")).Times(0); + + ON_CALL(mock_app_mngr_, GetDefaultHmiLevel(const_app_)) + .WillByDefault(Return(kDefaultTestLevel_)); + + EXPECT_CALL(mock_app_mngr_, CheckResumptionRequiredTransportAvailable(_)) + .WillRepeatedly(Return(false)); + + EXPECT_CALL(*mock_app_, is_navi()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_app_, mobile_projection_enabled()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_app_, is_media_application()).WillRepeatedly(Return(false)); + + // SetRegularState() should be called with kProjectionLowbandwidthLevel_ + EXPECT_CALL(mock_state_controller_, SetRegularState(_, eType::HMI_NONE)) + .Times(AtLeast(1)); + + res_ctrl_->SetupDefaultHMILevel(mock_app_); +} + TEST_F(ResumeCtrlTest, ApplicationResumptiOnTimer_AppInFull) { ON_CALL(mock_app_mngr_, application(kTestAppId_)) .WillByDefault(Return(mock_app_)); diff --git a/src/components/config_profile/include/config_profile/profile.h b/src/components/config_profile/include/config_profile/profile.h index 61dddf55b0..4c2be53228 100644 --- a/src/components/config_profile/include/config_profile/profile.h +++ b/src/components/config_profile/include/config_profile/profile.h @@ -37,6 +37,7 @@ #include <string> #include <vector> #include <list> +#include <map> #include "utils/macro.h" #include "protocol_handler/protocol_handler_settings.h" #include "connection_handler/connection_handler_settings.h" @@ -385,6 +386,12 @@ class Profile : public protocol_handler::ProtocolHandlerSettings, */ uint16_t transport_manager_tcp_adapter_port() const OVERRIDE; + /** + * @brief Returns the network interface name for TCP transport adapter + */ + const std::string& transport_manager_tcp_adapter_network_interface() + const OVERRIDE; + // TransportManageMMESettings interface const std::string& event_mq_name() const OVERRIDE; @@ -466,17 +473,39 @@ class Profile : public protocol_handler::ProtocolHandlerSettings, size_t update_before_hours() const; #endif // ENABLE_SECURITY - /** - * @brief Reads a string value from the profile - * - * @param value Result value - * @param default_value Value to use key wasn't found - * @param pSection The section to read the value in - * @param pKey The key whose value needs to be read out - * - * @return FALSE if could not read the value out of the profile - * (then the value is equal \c default_value) - */ + + /** + * @brief Returns true multiple transports is enabled + */ + const bool multiple_transports_enabled() const OVERRIDE; + + /** + * @brief Returns list of secondary transports available + * for the named primary transport + */ + const std::vector<std::string>& secondary_transports_for_bluetooth() + const OVERRIDE; + const std::vector<std::string>& secondary_transports_for_usb() const OVERRIDE; + const std::vector<std::string>& secondary_transports_for_wifi() + const OVERRIDE; + + /** + * @brief Returns list of allowed transports for the named service + */ + const std::vector<std::string>& audio_service_transports() const OVERRIDE; + const std::vector<std::string>& video_service_transports() const OVERRIDE; + + /** + * @brief Reads a string value from the profile + * + * @param value Result value + * @param default_value Value to use key wasn't found + * @param pSection The section to read the value in + * @param pKey The key whose value needs to be read out + * + * @return FALSE if could not read the value out of the profile + * (then the value is equal \c default_value) + */ bool ReadStringValue(std::string* value, const char* default_value, const char* const pSection, @@ -520,13 +549,17 @@ class Profile : public protocol_handler::ProtocolHandlerSettings, * @param pKey The key whose value needs to be read out * @param out_result Pointer to bool value for result reading Section * (could be NULL) + * @param allow_empty If true, then out_result will be true when the value + * contains an empty string. + * If false, then out_result will be false in such case. * * @return container of values or empty continer * if could not read the value out of the profile */ std::vector<std::string> ReadStringContainer(const char* const pSection, const char* const pKey, - bool* out_result) const; + bool* out_result, + bool allow_empty = false) const; /** * @brief Reads an container of hex int values from the profile, * which handle as "0x01, 0xA0, 0XFF" @@ -618,6 +651,33 @@ class Profile : public protocol_handler::ProtocolHandlerSettings, uint16_t open_attempt_timeout_ms_resumption_db() const; /** + * @brief Returns "transport required for resumption" map + * + * Keys of the map are AppHMIType strings, i.e. "DEFAULT", "COMMUNICATION", + * "MEDIA", and so on. The map may contain a special key "EMPTY_APP" for apps + * that does not specify any AppHMIType. + */ + const std::map<std::string, std::vector<std::string> >& + transport_required_for_resumption_map() const OVERRIDE; + + /** + * @brief Returns HMI level for resumption of a NAVIGATION app + */ + const std::string& navigation_lowbandwidth_resumption_level() const OVERRIDE; + + /** + * @brief Returns HMI level for resumption of a PROJECTION app + */ + const std::string& projection_lowbandwidth_resumption_level() const OVERRIDE; + + /** + * @brief Returns HMI level for resumption of a media app + * + * Note: this is *not* for AppHMIType = MEDIA. + */ + const std::string& media_lowbandwidth_resumption_level() const OVERRIDE; + + /** * @brief Returns wait time after device connection * before app launch request */ @@ -714,13 +774,29 @@ class Profile : public protocol_handler::ProtocolHandlerSettings, * @param pKey The key whose value needs to be read out * * @return FALSE if could not read the value out of the profile - * (then the value is not changed) + * (then the value is not changed) or the value was empty */ bool ReadValue(std::string* value, const char* const pSection, const char* const pKey) const; /** + * @brief Reads a string value from the profile + * + * This is same as ReadValue(), except that this method will accept an empty + * string. + * + * @param value The value to return + * @param pSection The section to read the value in + * @param pKey The key whose value needs to be read out + * + * @return TRUE if the value is read, FALSE if the value is not found + */ + bool ReadValueEmpty(std::string* value, + const char* const pSection, + const char* const pKey) const; + + /** * @brief Reads a boolean value from the profile * * @param value The value to return @@ -859,6 +935,7 @@ class Profile : public protocol_handler::ProtocolHandlerSettings, std::vector<uint32_t> supported_diag_modes_; std::string system_files_path_; uint16_t transport_manager_tcp_adapter_port_; + std::string transport_manager_tcp_adapter_network_interface_; std::string tts_delimiter_; uint32_t audio_data_stopped_timeout_; uint32_t video_data_stopped_timeout_; @@ -919,6 +996,11 @@ class Profile : public protocol_handler::ProtocolHandlerSettings, bool use_db_for_resumption_; uint16_t attempts_to_open_resumption_db_; uint16_t open_attempt_timeout_ms_resumption_db_; + std::map<std::string, std::vector<std::string> > + transport_required_for_resumption_map_; + std::string navigation_lowbandwidth_resumption_level_; + std::string projection_lowbandwidth_resumption_level_; + std::string media_lowbandwidth_resumption_level_; uint16_t app_launch_wait_time_; uint16_t app_launch_max_retry_attempt_; uint16_t app_launch_retry_wait_time_; @@ -928,6 +1010,12 @@ class Profile : public protocol_handler::ProtocolHandlerSettings, bool enable_app_launch_ios_; uint32_t app_tranport_change_timer_; uint32_t app_tranport_change_timer_addition_; + bool multiple_transports_enabled_; + std::vector<std::string> secondary_transports_for_bluetooth_; + std::vector<std::string> secondary_transports_for_usb_; + std::vector<std::string> secondary_transports_for_wifi_; + std::vector<std::string> audio_service_transports_; + std::vector<std::string> video_service_transports_; bool error_occured_; std::string error_description_; diff --git a/src/components/config_profile/src/profile.cc b/src/components/config_profile/src/profile.cc index 4137476d63..3f3ec7eb63 100644 --- a/src/components/config_profile/src/profile.cc +++ b/src/components/config_profile/src/profile.cc @@ -33,6 +33,7 @@ #include "config_profile/profile.h" #include <errno.h> +#include <numeric> #include <string.h> #include <stdlib.h> #include <sstream> @@ -88,6 +89,12 @@ const char* kSDL4Section = "SDL4"; const char* kSDL5Section = "SDL5"; const char* kResumptionSection = "Resumption"; const char* kAppLaunchSection = "AppLaunch"; +const char* kMultipleTransportsSection = "MultipleTransports"; +const char* kServicesMapSection = "ServicesMap"; +const char* kTransportRequiredForResumptionSection = + "TransportRequiredForResumption"; +const char* kLowBandwidthTransportResumptionLevelSection = + "LowBandwidthTransportResumptionLevel"; const char* kSDLVersionKey = "SDLVersion"; const char* kHmiCapabilitiesKey = "HMICapabilities"; @@ -146,6 +153,7 @@ const char* kHeartBeatTimeoutKey = "HeartBeatTimeout"; const char* kMaxSupportedProtocolVersionKey = "MaxSupportedProtocolVersion"; const char* kUseLastStateKey = "UseLastState"; const char* kTCPAdapterPortKey = "TCPAdapterPort"; +const char* kTCPAdapterNetworkInterfaceKey = "TCPAdapterNetworkInterface"; const char* kServerPortKey = "ServerPort"; const char* kVideoStreamingPortKey = "VideoStreamingPort"; const char* kAudioStreamingPortKey = "AudioStreamingPort"; @@ -214,6 +222,60 @@ const char* kEnableAppLaunchIOSKey = "EnableAppLaunchIOS"; const char* kAppTransportChangeTimerKey = "AppTransportChangeTimer"; const char* kAppTransportChangeTimerAdditionKey = "AppTransportChangeTimerAddition"; +const char* kMultipleTransportsEnabledKey = "MultipleTransportsEnabled"; +const char* kSecondaryTransportForBluetoothKey = + "SecondaryTransportForBluetooth"; +const char* kSecondaryTransportForUSBKey = "SecondaryTransportForUSB"; +const char* kSecondaryTransportForWiFiKey = "SecondaryTransportForWiFi"; +const char* kAudioServiceTransportsKey = "AudioServiceTransports"; +const char* kVideoServiceTransportsKey = "VideoServiceTransports"; + +const char* kDefaultTransportRequiredForResumptionKey = + "DefaultTransportRequiredForResumption"; +const char* kAppHMITypeDefault = "DEFAULT"; +const char* kCommunicationTransportRequiredForResumptionKey = + "CommunicationTransportRequiredForResumption"; +const char* kAppHMITypeCommunication = "COMMUNICATION"; +const char* kMediaTransportRequiredForResumptionKey = + "MediaTransportRequiredForResumption"; +const char* kAppHMITypeMedia = "MEDIA"; +const char* kMessagingTransportRequiredForResumptionKey = + "MessagingTransportRequiredForResumption"; +const char* kAppHMITypeMessaging = "MESSAGING"; +const char* kNavigationTransportRequiredForResumptionKey = + "NavigationTransportRequiredForResumption"; +const char* kAppHMITypeNavigation = "NAVIGATION"; +const char* kInformationTransportRequiredForResumptionKey = + "InformationTransportRequiredForResumption"; +const char* kAppHMITypeInformation = "INFORMATION"; +const char* kSocialTransportRequiredForResumptionKey = + "SocialTransportRequiredForResumption"; +const char* kAppHMITypeSocial = "SOCIAL"; +const char* kBackgroundProcessTransportRequiredForResumptionKey = + "BackgroundProcessTransportRequiredForResumption"; +const char* kAppHMITypeBackgroundProcess = "BACKGROUND_PROCESS"; +const char* kTestingTransportRequiredForResumptionKey = + "TestingTransportRequiredForResumption"; +const char* kAppHMITypeTesting = "TESTING"; +const char* kSystemTransportRequiredForResumptionKey = + "SystemTransportRequiredForResumption"; +const char* kAppHMITypeSystem = "SYSTEM"; +const char* kProjectionTransportRequiredForResumptionKey = + "ProjectionTransportRequiredForResumption"; +const char* kAppHMITypeProjection = "PROJECTION"; +const char* kRemoteControlTransportRequiredForResumptionKey = + "RemoteControlTransportRequiredForResumption"; +const char* kAppHMITypeRemoteControl = "REMOTE_CONTROL"; +const char* kEmptyAppTransportRequiredForResumptionKey = + "EmptyAppTransportRequiredForResumption"; +const char* kAppHMITypeEmptyApp = "EMPTY_APP"; +const char* kNavigationLowBandwidthResumptionLevelKey = + "NavigationLowBandwidthResumptionLevel"; +const char* kProjectionLowBandwidthResumptionLevelKey = + "ProjectionLowBandwidthResumptionLevel"; +const char* kMediaLowBandwidthResumptionLevelKey = + "MediaLowBandwidthResumptionLevel"; + #ifdef WEB_HMI const char* kDefaultLinkToWebHMI = "HMI/index.html"; #endif // WEB_HMI @@ -237,6 +299,7 @@ const char* kDefaultHubProtocolMask = "com.smartdevicelink.prot"; const char* kDefaultPoolProtocolMask = "com.smartdevicelink.prot"; const char* kDefaultIAPSystemConfig = "/fs/mp/etc/mm/ipod.cfg"; const char* kDefaultIAP2SystemConfig = "/fs/mp/etc/mm/iap2.cfg"; +const char* kDefaultTransportManagerTCPAdapterNetworkInterface = ""; #ifdef ENABLE_SECURITY const char* kDefaultSecurityProtocol = "TLSv1.2"; @@ -311,6 +374,8 @@ const uint32_t kDefaultAppTransportChangeTimer = 500u; const uint32_t kDefaultAppTransportChangeTimerAddition = 0u; const std::string kAllowedSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_.-"; +const bool kDefaultMultipleTransportsEnabled = false; +const char* kDefaultLowBandwidthResumptionLevel = "NONE"; } // namespace namespace profile { @@ -403,6 +468,11 @@ Profile::Profile() , attempts_to_open_resumption_db_(kDefaultAttemptsToOpenResumptionDB) , open_attempt_timeout_ms_resumption_db_( kDefaultOpenAttemptTimeoutMsResumptionDB) + , navigation_lowbandwidth_resumption_level_( + kDefaultLowBandwidthResumptionLevel) + , projection_lowbandwidth_resumption_level_( + kDefaultLowBandwidthResumptionLevel) + , media_lowbandwidth_resumption_level_(kDefaultLowBandwidthResumptionLevel) , app_launch_wait_time_(kDefaultAppLaunchWaitTime) , app_launch_max_retry_attempt_(kDefaultAppLaunchMaxRetryAttempt) , app_launch_retry_wait_time_(kDefaultAppLaunchRetryWaitTime) @@ -413,6 +483,7 @@ Profile::Profile() , app_tranport_change_timer_(kDefaultAppTransportChangeTimer) , app_tranport_change_timer_addition_( kDefaultAppTransportChangeTimerAddition) + , multiple_transports_enabled_(kDefaultMultipleTransportsEnabled) , error_occured_(false) , error_description_() { // SDL version @@ -682,6 +753,11 @@ uint16_t Profile::transport_manager_tcp_adapter_port() const { return transport_manager_tcp_adapter_port_; } +const std::string& Profile::transport_manager_tcp_adapter_network_interface() + const { + return transport_manager_tcp_adapter_network_interface_; +} + const std::string& Profile::tts_delimiter() const { return tts_delimiter_; } @@ -899,6 +975,23 @@ uint16_t Profile::open_attempt_timeout_ms_resumption_db() const { return open_attempt_timeout_ms_resumption_db_; } +const std::map<std::string, std::vector<std::string> >& +Profile::transport_required_for_resumption_map() const { + return transport_required_for_resumption_map_; +} + +const std::string& Profile::navigation_lowbandwidth_resumption_level() const { + return navigation_lowbandwidth_resumption_level_; +} + +const std::string& Profile::projection_lowbandwidth_resumption_level() const { + return projection_lowbandwidth_resumption_level_; +} + +const std::string& Profile::media_lowbandwidth_resumption_level() const { + return media_lowbandwidth_resumption_level_; +} + const uint16_t Profile::app_launch_max_retry_attempt() const { return app_launch_max_retry_attempt_; } @@ -935,6 +1028,31 @@ const uint16_t Profile::wait_time_between_apps() const { return wait_time_between_apps_; } +const bool Profile::multiple_transports_enabled() const { + return multiple_transports_enabled_; +} + +const std::vector<std::string>& Profile::secondary_transports_for_bluetooth() + const { + return secondary_transports_for_bluetooth_; +} + +const std::vector<std::string>& Profile::secondary_transports_for_usb() const { + return secondary_transports_for_usb_; +} + +const std::vector<std::string>& Profile::secondary_transports_for_wifi() const { + return secondary_transports_for_wifi_; +} + +const std::vector<std::string>& Profile::audio_service_transports() const { + return audio_service_transports_; +} + +const std::vector<std::string>& Profile::video_service_transports() const { + return video_service_transports_; +} + const bool Profile::ErrorOccured() const { return error_occured_; } @@ -1610,6 +1728,16 @@ void Profile::UpdateValues() { kTCPAdapterPortKey, kTransportManagerSection); + // Transport manager TCP network interface + ReadStringValue(&transport_manager_tcp_adapter_network_interface_, + kDefaultTransportManagerTCPAdapterNetworkInterface, + kTransportManagerSection, + kTCPAdapterNetworkInterfaceKey); + + LOG_UPDATED_VALUE(transport_manager_tcp_adapter_network_interface_, + kTCPAdapterNetworkInterfaceKey, + kTransportManagerSection); + // Event MQ ReadStringValue( &event_mq_name_, kDefaultEventMQ, kTransportManagerSection, kEventMQKey); @@ -1841,6 +1969,84 @@ void Profile::UpdateValues() { kOpenAttemptTimeoutMsResumptionDBKey, kResumptionSection); + { // read parameters from TransportRequiredForResumption section + struct KeyPair { + const char* ini_key_name; + const char* map_key_name; + } keys[] = { + {kDefaultTransportRequiredForResumptionKey, kAppHMITypeDefault}, + {kCommunicationTransportRequiredForResumptionKey, + kAppHMITypeCommunication}, + {kMediaTransportRequiredForResumptionKey, kAppHMITypeMedia}, + {kMessagingTransportRequiredForResumptionKey, kAppHMITypeMessaging}, + {kNavigationTransportRequiredForResumptionKey, kAppHMITypeNavigation}, + {kInformationTransportRequiredForResumptionKey, kAppHMITypeInformation}, + {kSocialTransportRequiredForResumptionKey, kAppHMITypeSocial}, + {kBackgroundProcessTransportRequiredForResumptionKey, + kAppHMITypeBackgroundProcess}, + {kTestingTransportRequiredForResumptionKey, kAppHMITypeTesting}, + {kSystemTransportRequiredForResumptionKey, kAppHMITypeSystem}, + {kProjectionTransportRequiredForResumptionKey, kAppHMITypeProjection}, + {kRemoteControlTransportRequiredForResumptionKey, + kAppHMITypeRemoteControl}, + {kEmptyAppTransportRequiredForResumptionKey, kAppHMITypeEmptyApp}, + {NULL, NULL}}; + struct KeyPair* entry = keys; + + while (entry->ini_key_name != NULL) { + bool exist = false; + std::vector<std::string> transport_list = + ReadStringContainer(kTransportRequiredForResumptionSection, + entry->ini_key_name, + &exist, + true); + if (exist) { + transport_required_for_resumption_map_[entry->map_key_name] = + transport_list; + + const std::string list_with_comma = std::accumulate( + transport_list.begin(), + transport_list.end(), + std::string(""), + [](std::string& first, std::string& second) { + return first.empty() ? second : first + ", " + second; + }); + LOG_UPDATED_VALUE(list_with_comma, + entry->ini_key_name, + kTransportRequiredForResumptionSection); + } + entry++; + } + } + + // Read parameters from LowBandwidthTransportResumptionLevel section + ReadStringValue(&navigation_lowbandwidth_resumption_level_, + kDefaultLowBandwidthResumptionLevel, + kLowBandwidthTransportResumptionLevelSection, + kNavigationLowBandwidthResumptionLevelKey); + + LOG_UPDATED_VALUE(navigation_lowbandwidth_resumption_level_, + kNavigationLowBandwidthResumptionLevelKey, + kLowBandwidthTransportResumptionLevelSection); + + ReadStringValue(&projection_lowbandwidth_resumption_level_, + kDefaultLowBandwidthResumptionLevel, + kLowBandwidthTransportResumptionLevelSection, + kProjectionLowBandwidthResumptionLevelKey); + + LOG_UPDATED_VALUE(projection_lowbandwidth_resumption_level_, + kProjectionLowBandwidthResumptionLevelKey, + kLowBandwidthTransportResumptionLevelSection); + + ReadStringValue(&media_lowbandwidth_resumption_level_, + kDefaultLowBandwidthResumptionLevel, + kLowBandwidthTransportResumptionLevelSection, + kMediaLowBandwidthResumptionLevelKey); + + LOG_UPDATED_VALUE(media_lowbandwidth_resumption_level_, + kMediaLowBandwidthResumptionLevelKey, + kLowBandwidthTransportResumptionLevelSection); + // Read parameters from App Launch section ReadUIntValue(&app_launch_wait_time_, kDefaultAppLaunchWaitTime, @@ -1917,6 +2123,59 @@ void Profile::UpdateValues() { LOG_UPDATED_VALUE(app_tranport_change_timer_addition_, kAppTransportChangeTimerAdditionKey, kMainSection); + + ReadBoolValue(&multiple_transports_enabled_, + kDefaultMultipleTransportsEnabled, + kMultipleTransportsSection, + kMultipleTransportsEnabledKey); + + LOG_UPDATED_BOOL_VALUE(multiple_transports_enabled_, + kMultipleTransportsEnabledKey, + kMultipleTransportsSection); + + { // Secondary Transports and ServicesMap + struct KeyPair { + std::vector<std::string>* ini_vector; + const char* ini_section_name; + const char* ini_key_name; + } keys[] = {{&secondary_transports_for_bluetooth_, + kMultipleTransportsSection, + kSecondaryTransportForBluetoothKey}, + {&secondary_transports_for_usb_, + kMultipleTransportsSection, + kSecondaryTransportForUSBKey}, + {&secondary_transports_for_wifi_, + kMultipleTransportsSection, + kSecondaryTransportForWiFiKey}, + {&audio_service_transports_, + kServicesMapSection, + kAudioServiceTransportsKey}, + {&video_service_transports_, + kServicesMapSection, + kVideoServiceTransportsKey}, + {NULL, NULL, NULL}}; + struct KeyPair* entry = keys; + + while (entry->ini_vector != NULL) { + bool exist = false; + std::vector<std::string> profile_entry = ReadStringContainer( + entry->ini_section_name, entry->ini_key_name, &exist, true); + if (exist) { + *entry->ini_vector = profile_entry; + + const std::string list_with_comma = std::accumulate( + profile_entry.begin(), + profile_entry.end(), + std::string(""), + [](std::string& first, std::string& second) { + return first.empty() ? second : first + ", " + second; + }); + LOG_UPDATED_VALUE( + list_with_comma, entry->ini_key_name, entry->ini_section_name); + } + entry++; + } + } } bool Profile::ReadValue(bool* value, @@ -1944,15 +2203,22 @@ bool Profile::ReadValue(std::string* value, const char* const pSection, const char* const pKey) const { DCHECK(value); + return ReadValueEmpty(value, pSection, pKey) && "\0" != *value; +} + +bool Profile::ReadValueEmpty(std::string* value, + const char* const pSection, + const char* const pKey) const { + DCHECK(value); bool ret = false; char buf[INI_LINE_LEN + 1]; *buf = '\0'; - if ((0 != ini_read_value(config_file_name_.c_str(), pSection, pKey, buf)) && - ('\0' != *buf)) { + if (0 != ini_read_value(config_file_name_.c_str(), pSection, pKey, buf)) { *value = buf; ret = true; } + return ret; } @@ -2014,6 +2280,18 @@ namespace { int32_t hex_to_int(const std::string& value) { return static_cast<int32_t>(strtol(value.c_str(), NULL, 16)); } + +std::string trim_string(const std::string& str) { + const char* delims = " \t"; + + size_t start = str.find_first_not_of(delims); + if (std::string::npos == start) { + return std::string(); + } + size_t end = str.find_last_not_of(delims); + + return str.substr(start, end - start + 1); +} } std::vector<int> Profile::ReadIntContainer(const char* const pSection, @@ -2031,9 +2309,15 @@ std::vector<int> Profile::ReadIntContainer(const char* const pSection, std::vector<std::string> Profile::ReadStringContainer( const char* const pSection, const char* const pKey, - bool* out_result) const { + bool* out_result, + bool allow_empty) const { std::string string; - const bool result = ReadValue(&string, pSection, pKey); + bool result; + if (allow_empty) { + result = ReadValueEmpty(&string, pSection, pKey); + } else { + result = ReadValue(&string, pSection, pKey); + } if (out_result) *out_result = result; std::vector<std::string> value_container; @@ -2043,7 +2327,7 @@ std::vector<std::string> Profile::ReadStringContainer( while (iss) { if (!getline(iss, temp_str, ',')) break; - value_container.push_back(temp_str); + value_container.push_back(trim_string(temp_str)); } } return value_container; diff --git a/src/components/config_profile/test/profile_test.cc b/src/components/config_profile/test/profile_test.cc index e7d62f4740..64bafdd6bf 100644 --- a/src/components/config_profile/test/profile_test.cc +++ b/src/components/config_profile/test/profile_test.cc @@ -690,10 +690,34 @@ TEST_F(ProfileTest, CheckStringContainer) { std::vector<std::string>::iterator element_mode = diagmodes_list.begin(); element_mode++; element_mode++; - diag_mode = std::find(diagmodes_list.begin(), diagmodes_list.end(), " 0x03"); + diag_mode = std::find(diagmodes_list.begin(), diagmodes_list.end(), "0x03"); EXPECT_EQ(diag_mode, element_mode); } +TEST_F(ProfileTest, CheckStringContainerEmpty) { + // Set new config file + profile_.set_config_file_name("smartDeviceLink_test.ini"); + EXPECT_EQ("smartDeviceLink_test.ini", profile_.config_file_name()); + + bool isread = false; + std::vector<std::string> output_list = + profile_.ReadStringContainer("MAIN", "AppConfigFolder", &isread); + EXPECT_FALSE(isread); + EXPECT_TRUE(output_list.empty()); + + isread = false; + std::vector<std::string> output_list2 = + profile_.ReadStringContainer("MAIN", "AppConfigFolder", &isread, true); + EXPECT_TRUE(isread); + EXPECT_TRUE(output_list2.empty()); + + isread = false; + std::vector<std::string> output_list3 = + profile_.ReadStringContainer("MAIN", "DoesNotExistKey", &isread, true); + EXPECT_FALSE(isread); + EXPECT_TRUE(output_list2.empty()); +} + #ifdef ENABLE_SECURITY TEST_F(ProfileTest, CheckIntContainerInSecurityData) { // Set new config file diff --git a/src/components/connection_handler/include/connection_handler/connection.h b/src/components/connection_handler/include/connection_handler/connection.h index 9b72d60776..a2309c1521 100644 --- a/src/components/connection_handler/include/connection_handler/connection.h +++ b/src/components/connection_handler/include/connection_handler/connection.h @@ -72,13 +72,18 @@ typedef std::map<int32_t, Connection*> ConnectionList; */ struct Service { protocol_handler::ServiceType service_type; + transport_manager::ConnectionUID connection_id; bool is_protected_; Service() : service_type(protocol_handler::kInvalidServiceType) + , connection_id(0) , is_protected_(false) {} - explicit Service(protocol_handler::ServiceType service_type) - : service_type(service_type), is_protected_(false) {} + explicit Service(protocol_handler::ServiceType service_type, + transport_manager::ConnectionUID connection_id) + : service_type(service_type) + , connection_id(connection_id) + , is_protected_(false) {} bool operator==(const protocol_handler::ServiceType service_type) const { return this->service_type == service_type; @@ -154,9 +159,11 @@ class Connection { /** * @brief Adds session to connection + * @param connection_handle Connection Handle for the session * @return new session id or 0 in case of issues */ - uint32_t AddNewSession(); + uint32_t AddNewSession( + const transport_manager::ConnectionUID connection_handle); /** * @brief Removes session from connection @@ -171,11 +178,13 @@ class Connection { * @param session_id session ID * @param service_type Type of service * @param is_protected protection state + * @param connection_id Connection ID associated with the service * @return TRUE on success, otherwise FALSE */ bool AddNewService(uint8_t session_id, protocol_handler::ServiceType service_type, - const bool is_protected); + const bool is_protected, + transport_manager::ConnectionUID connection_id); /** * @brief Removes service from session * @param session_id session ID @@ -184,6 +193,18 @@ class Connection { */ bool RemoveService(uint8_t session_id, protocol_handler::ServiceType service_type); + + /** + * @brief Removes secondary service from session + * @param secondary_connection_handle connection identifying services to be + * removed + * @param removed_services_list Returned: List of service types removed + * @return the session ID associated with the services removed + */ + uint8_t RemoveSecondaryServices( + transport_manager::ConnectionUID secondary_connection_handle, + std::list<protocol_handler::ServiceType>& removed_services_list); + #ifdef ENABLE_SECURITY /** * @brief Sets crypto context of service @@ -213,10 +234,11 @@ class Connection { const protocol_handler::ServiceType& service_type); #endif // ENABLE_SECURITY - /** - * @brief Returns map of sessions which have been opened in - * current connection. - */ + + /** + * @brief Returns map of sessions which have been opened in + * current connection. + */ const SessionMap session_map() const; /** @@ -283,6 +305,20 @@ class Connection { */ bool ProtocolVersion(uint8_t session_id, uint8_t& protocol_version); + /** + * @brief Returns the primary connection handle associated with this + * connection + * @return ConnectionHandle + */ + ConnectionHandle primary_connection_handle() const; + + /** + * @brief Sets the primary connection handle + * @param primary_connection_handle the primary connection handle to + * associate with this connection + */ + void SetPrimaryConnectionHandle(ConnectionHandle primary_connection_handle); + private: /** * @brief Current connection handler. @@ -307,6 +343,11 @@ class Connection { mutable sync_primitives::Lock session_map_lock_; /** + * @brief primary connection handle for secondary connections + */ + ConnectionHandle primary_connection_handle_; + + /** * @brief monitor that closes connection if there is no traffic over it */ HeartBeatMonitor* heartbeat_monitor_; diff --git a/src/components/connection_handler/include/connection_handler/connection_handler_impl.h b/src/components/connection_handler/include/connection_handler/connection_handler_impl.h index e270d9faeb..121d5ed08c 100644 --- a/src/components/connection_handler/include/connection_handler/connection_handler_impl.h +++ b/src/components/connection_handler/include/connection_handler/connection_handler_impl.h @@ -54,11 +54,14 @@ #include "utils/stl_utils.h" #include "utils/rwlock.h" +const transport_manager::ConnectionUID kDisabledSecondary = 0xFFFFFFFF; + /** * \namespace connection_handler * \brief SmartDeviceLink connection_handler namespace. */ namespace connection_handler { + /** * \class ConnectionHandlerImpl * \brief SmartDeviceLink connection_handler main class @@ -274,6 +277,24 @@ class ConnectionHandlerImpl void OnMalformedMessageCallback(const uint32_t& connection_key) OVERRIDE; /** + * @brief Converts connection handle to transport type string used in + * smartDeviceLink.ini file, e.g. "TCP_WIFI" + * @param connection_handle A connection identifier + * @return string representation of the transport of the device + */ + const std::string TransportTypeProfileStringFromConnHandle( + transport_manager::ConnectionUID connection_handle) const; + + /** + * @brief Converts device handle to transport type string used in + * smartDeviceLink.ini file, e.g. "TCP_WIFI" + * @param device_handle A device handle + * @return string representation of the transport of the device + */ + const std::string TransportTypeProfileStringFromDeviceHandle( + DeviceHandle device_handle) const; + + /** * \brief Creates unique identifier of session (can be used as hash) * from given connection identifier * within which session exists and session number. @@ -509,6 +530,43 @@ class ConnectionHandlerImpl DevicesDiscoveryStarter& get_device_discovery_starter(); /** + * \brief Add a session. This is meant to be called from Connection class. + * \param primary_transport_id the primary connection ID to associate with the + * newly created session + * \return new session id, or 0 if failed + **/ + uint32_t AddSession( + const transport_manager::ConnectionUID primary_transport_id) OVERRIDE; + + /** + * \brief Remove a session. This is meant to be called from Connection class. + * \param session_id ID of the session to remove + * \return true if successful, false otherwise + **/ + bool RemoveSession(uint8_t session_id) OVERRIDE; + + DataAccessor<SessionConnectionMap> session_connection_map() OVERRIDE; + + /** + * \brief Associate a secondary transport ID with a session + * \param session_id the session ID + * \param connection_id the new secondary connection ID to associate with the + * session + * \return the SessionTransports (newly) associated with the session + **/ + SessionTransports SetSecondaryTransportID( + uint8_t session_id, + transport_manager::ConnectionUID secondary_transport_id) OVERRIDE; + + /** + * \brief Retrieve the session transports associated with a session + * \param session_id the session ID + * \return the SessionTransports associated with the session + **/ + const SessionTransports GetSessionTransports( + uint8_t session_id) const OVERRIDE; + + /** * \brief Invoked when observer's OnServiceStartedCallback is completed * \param session_key the key of started session passed to * OnServiceStartedCallback(). @@ -524,6 +582,28 @@ class ConnectionHandlerImpl bool result, std::vector<std::string>& rejected_params); + /** + * \brief Called when secondary transport with given session ID is established + * \param primary_connection_handle Set to identifier of primary connection + * \param secondary_connection_handle Identifier of secondary connection + * \param sessionid session ID taken from Register Secondary Transport frame + **/ + bool OnSecondaryTransportStarted( + transport_manager::ConnectionUID& primary_connection_handle, + const transport_manager::ConnectionUID secondary_connection_handle, + const uint8_t session_id) OVERRIDE; + + /** + * \brief Called when secondary transport shuts down + * \param primary_connection_handle Identifier of primary connection + * \param secondary_connection_handle Identifier of secondary connection + * transport + **/ + void OnSecondaryTransportEnded( + const transport_manager::ConnectionUID primary_connection_handle, + const transport_manager::ConnectionUID secondary_connection_handle) + OVERRIDE; + private: /** * \brief Disconnect application. @@ -535,6 +615,9 @@ class ConnectionHandlerImpl void OnConnectionEnded(const transport_manager::ConnectionUID connection_id); + const uint8_t GetSessionIdFromSecondaryTransport( + transport_manager::ConnectionUID secondary_transport_id) const; + const ConnectionHandlerSettings& settings_; /** * \brief Pointer to observer @@ -554,6 +637,13 @@ class ConnectionHandlerImpl DeviceMap device_list_; /** + * @brief session/connection map + */ + SessionConnectionMap session_connection_map_; + mutable std::shared_ptr<sync_primitives::Lock> + session_connection_map_lock_ptr_; + + /** * \brief List of connections */ ConnectionList connection_list_; @@ -573,6 +663,11 @@ class ConnectionHandlerImpl std::map<uint32_t, protocol_handler::SessionContext> start_service_context_map_; + /** + * @brief connection object as it's being closed + */ + Connection* ending_connection_; + #ifdef BUILD_TESTS // Methods for test usage public: @@ -581,6 +676,9 @@ class ConnectionHandlerImpl void addDeviceConnection( const transport_manager::DeviceInfo& device_info, const transport_manager::ConnectionUID connection_id); + SessionConnectionMap& getSessionConnectionMap() { + return session_connection_map_; + } #endif private: DISALLOW_COPY_AND_ASSIGN(ConnectionHandlerImpl); diff --git a/src/components/connection_handler/src/connection.cc b/src/components/connection_handler/src/connection.cc index 614120312a..b8399b2c9b 100644 --- a/src/components/connection_handler/src/connection.cc +++ b/src/components/connection_handler/src/connection.cc @@ -82,6 +82,7 @@ Connection::Connection(ConnectionHandle connection_handle, , connection_handle_(connection_handle) , connection_device_handle_(connection_device_handle) , session_map_lock_(true) + , primary_connection_handle_(0) , heartbeat_timeout_(heartbeat_timeout) { LOG4CXX_AUTO_TRACE(logger_); DCHECK(connection_handler_); @@ -97,39 +98,62 @@ Connection::~Connection() { heart_beat_monitor_thread_->join(); delete heartbeat_monitor_; threads::DeleteThread(heart_beat_monitor_thread_); - sync_primitives::AutoLock lock(session_map_lock_); - session_map_.clear(); -} -// Finds a key not presented in std::map<unsigned char, T> -// Returns 0 if that key not found -namespace { -template <class T> -uint32_t findGap(const std::map<unsigned char, T>& map) { - for (uint32_t i = 1; i <= UCHAR_MAX; ++i) { - if (map.find(i) == map.end()) { - return i; - } + // Before clearing out the session_map_, we must remove all sessions + // associated with this Connection from the SessionConnectionMap. + + // NESTED LOCK: make sure to lock session_map_lock_ then ConnectionHandler's + // session_connection_map_lock_ptr_ (which will be taken in RemoveSession). + sync_primitives::AutoLock lock(session_map_lock_); + SessionMap::iterator session_it = session_map_.begin(); + while (session_it != session_map_.end()) { + LOG4CXX_INFO( + logger_, + "Removed Session ID " + << static_cast<int>(session_it->first) + << " from Session/Connection Map in Connection Destructor"); + connection_handler_->RemoveSession(session_it->first); + session_it++; } - return 0; + + session_map_.clear(); } -} // namespace -uint32_t Connection::AddNewSession() { +uint32_t Connection::AddNewSession( + const transport_manager::ConnectionUID connection_handle) { LOG4CXX_AUTO_TRACE(logger_); + + // NESTED LOCK: make sure to lock session_map_lock_ then ConnectionHandler's + // session_connection_map_lock_ptr_ (which will be taken in AddSession) sync_primitives::AutoLock lock(session_map_lock_); - const uint32_t session_id = findGap(session_map_); + + // Even though we have our own SessionMap, we use the Connection Handler's + // SessionConnectionMap to generate a session ID. We want to make sure that + // session IDs are globally unique, and not only unique within a Connection. + const uint32_t session_id = + connection_handler_->AddSession(connection_handle); if (session_id > 0) { Session& new_session = session_map_[session_id]; new_session.protocol_version = ::protocol_handler::PROTOCOL_VERSION_2; - new_session.service_list.push_back(Service(protocol_handler::kRpc)); - new_session.service_list.push_back(Service(protocol_handler::kBulk)); + new_session.service_list.push_back( + Service(protocol_handler::kRpc, connection_handle)); + new_session.service_list.push_back( + Service(protocol_handler::kBulk, connection_handle)); } + return session_id; } uint32_t Connection::RemoveSession(uint8_t session_id) { + LOG4CXX_AUTO_TRACE(logger_); + + // Again, a NESTED lock, but it follows the rules. sync_primitives::AutoLock lock(session_map_lock_); + + if (!connection_handler_->RemoveSession(session_id)) { + return 0; + } + SessionMap::iterator it = session_map_.find(session_id); if (session_map_.end() == it) { LOG4CXX_WARN(logger_, "Session not found in this connection!"); @@ -137,12 +161,14 @@ uint32_t Connection::RemoveSession(uint8_t session_id) { } heartbeat_monitor_->RemoveSession(session_id); session_map_.erase(session_id); + return session_id; } bool Connection::AddNewService(uint8_t session_id, protocol_handler::ServiceType service_type, - const bool request_protection) { + const bool request_protection, + transport_manager::ConnectionUID connection_id) { // Ignore wrong services if (protocol_handler::kControl == service_type || protocol_handler::kInvalidServiceType == service_type) { @@ -152,7 +178,9 @@ bool Connection::AddNewService(uint8_t session_id, LOG4CXX_DEBUG(logger_, "Add service " << service_type << " for session " - << static_cast<uint32_t>(session_id)); + << static_cast<uint32_t>(session_id) + << " using connection ID " + << static_cast<uint32_t>(connection_id)); sync_primitives::AutoLock lock(session_map_lock_); SessionMap::iterator session_it = session_map_.find(session_id); @@ -201,7 +229,7 @@ bool Connection::AddNewService(uint8_t session_id, #endif // ENABLE_SECURITY } // id service is not exists - session.service_list.push_back(Service(service_type)); + session.service_list.push_back(Service(service_type, connection_id)); return true; } @@ -246,6 +274,63 @@ bool Connection::RemoveService(uint8_t session_id, return true; } +uint8_t Connection::RemoveSecondaryServices( + transport_manager::ConnectionUID secondary_connection_handle, + std::list<protocol_handler::ServiceType>& removed_services_list) { + LOG4CXX_AUTO_TRACE(logger_); + + uint8_t found_session_id = 0; + sync_primitives::AutoLock lock(session_map_lock_); + + LOG4CXX_INFO(logger_, + "RemoveSecondaryServices looking for services on Connection ID " + << static_cast<int>(secondary_connection_handle)); + + // Walk the SessionMap in the primary connection, and for each + // Session, we walk its ServiceList, looking for all the services + // that were running on the now-closed Secondary Connection. + for (SessionMap::iterator session_it = session_map_.begin(); + session_map_.end() != session_it; + ++session_it) { + LOG4CXX_INFO(logger_, + "RemoveSecondaryServices found session ID " + << static_cast<int>(session_it->first)); + + // Now, for each session, walk the its ServiceList, looking for services + // that were using secondary)_connection_handle. If we find such a service, + // set session_found and break out of the outer loop. + ServiceList& service_list = session_it->second.service_list; + ServiceList::iterator service_it = service_list.begin(); + for (; service_it != service_list.end();) { + LOG4CXX_INFO(logger_, + "RemoveSecondaryServices found service ID " + << static_cast<int>(service_it->service_type)); + if (service_it->connection_id == secondary_connection_handle) { + found_session_id = session_it->first; + + LOG4CXX_INFO(logger_, + "RemoveSecondaryServices removing Service " + << static_cast<int>(service_it->service_type) + << " in session " + << static_cast<int>(found_session_id)); + + removed_services_list.push_back(service_it->service_type); + service_it = service_list.erase(service_it); + } else { + service_it++; + } + } + + // If we found a session that had services running on the secondary + // connection, we're done. + if (found_session_id != 0) { + break; + } + } + + return found_session_id; +} + #ifdef ENABLE_SECURITY int Connection::SetSSLContext(uint8_t session_id, security_manager::SSLContext* context) { @@ -405,6 +490,15 @@ bool Connection::ProtocolVersion(uint8_t session_id, return true; } +ConnectionHandle Connection::primary_connection_handle() const { + return primary_connection_handle_; +} + +void Connection::SetPrimaryConnectionHandle( + ConnectionHandle primary_connection_handle) { + primary_connection_handle_ = primary_connection_handle; +} + void Connection::StartHeartBeat(uint8_t session_id) { heartbeat_monitor_->AddSession(session_id); } diff --git a/src/components/connection_handler/src/connection_handler_impl.cc b/src/components/connection_handler/src/connection_handler_impl.cc index 83d80d9696..729f216fc3 100644 --- a/src/components/connection_handler/src/connection_handler_impl.cc +++ b/src/components/connection_handler/src/connection_handler_impl.cc @@ -68,11 +68,14 @@ ConnectionHandlerImpl::ConnectionHandlerImpl( , connection_handler_observer_(NULL) , transport_manager_(tm) , protocol_handler_(NULL) + , session_connection_map_lock_ptr_( + std::make_shared<sync_primitives::Lock>(true)) , connection_list_lock_() , connection_handler_observer_lock_() , connection_list_deleter_(&connection_list_) , start_service_context_map_lock_() - , start_service_context_map_() {} + , start_service_context_map_() + , ending_connection_(NULL) {} ConnectionHandlerImpl::~ConnectionHandlerImpl() { LOG4CXX_AUTO_TRACE(logger_); @@ -362,7 +365,7 @@ uint32_t ConnectionHandlerImpl::OnSessionStartedCallback( Connection* connection = it->second; if ((0 == session_id) && (protocol_handler::kRpc == service_type)) { - new_session_id = connection->AddNewSession(); + new_session_id = connection->AddNewSession(connection_handle); if (0 == new_session_id) { LOG4CXX_ERROR(logger_, "Couldn't start new session!"); return 0; @@ -371,7 +374,8 @@ uint32_t ConnectionHandlerImpl::OnSessionStartedCallback( *hash_id = KeyFromPair(connection_handle, new_session_id); } } else { // Could be create new service or protected exists one - if (!connection->AddNewService(session_id, service_type, is_protected)) { + if (!connection->AddNewService( + session_id, service_type, is_protected, connection_handle)) { LOG4CXX_ERROR(logger_, "Couldn't establish " #ifdef ENABLE_SECURITY @@ -414,7 +418,33 @@ void ConnectionHandlerImpl::OnSessionStartedCallback( LOG4CXX_AUTO_TRACE(logger_); std::vector<std::string> rejected_params; - protocol_handler::SessionContext context(connection_handle, + + // In case this is a Session running on a Secondary Transport, we need to + // find the Sessions's primary transport. In this case, "connection_handle" + // reflects the secondary transport, which we need for the various callbacks, + // so they can send appropriate Ack or NAK messages on the correct transport. + transport_manager::ConnectionUID primary_connection_handle = + connection_handle; + if (session_id != 0) { + SessionTransports st = GetSessionTransports(session_id); + if (st.primary_transport == 0) { + LOG4CXX_WARN(logger_, + "OnSessionStartedCallback could not find Session in the " + "Session/Connection Map!"); + } else { + LOG4CXX_INFO(logger_, + "OnSessionStartedCallback found session " + << static_cast<int>(session_id) + << " with primary connection " + << static_cast<int>(st.primary_transport) + << " and secondary connection " + << static_cast<int>(st.secondary_transport)); + primary_connection_handle = st.primary_transport; + } + } + + protocol_handler::SessionContext context(primary_connection_handle, + connection_handle, session_id, 0, service_type, @@ -428,7 +458,8 @@ void ConnectionHandlerImpl::OnSessionStartedCallback( } #endif // ENABLE_SECURITY sync_primitives::AutoReadLock lock(connection_list_lock_); - ConnectionList::iterator it = connection_list_.find(connection_handle); + ConnectionList::iterator it = + connection_list_.find(primary_connection_handle); if (connection_list_.end() == it) { LOG4CXX_ERROR(logger_, "Unknown connection!"); protocol_handler_->NotifySessionStarted(context, rejected_params); @@ -440,15 +471,18 @@ void ConnectionHandlerImpl::OnSessionStartedCallback( !connection->SessionServiceExists(session_id, service_type); if ((0 == session_id) && (protocol_handler::kRpc == service_type)) { - context.new_session_id_ = connection->AddNewSession(); + context.new_session_id_ = + connection->AddNewSession(primary_connection_handle); if (0 == context.new_session_id_) { LOG4CXX_ERROR(logger_, "Couldn't start new session!"); protocol_handler_->NotifySessionStarted(context, rejected_params); return; } - context.hash_id_ = KeyFromPair(connection_handle, context.new_session_id_); + context.hash_id_ = + KeyFromPair(primary_connection_handle, context.new_session_id_); } else { // Could be create new service or protected exists one - if (!connection->AddNewService(session_id, service_type, is_protected)) { + if (!connection->AddNewService( + session_id, service_type, is_protected, connection_handle)) { LOG4CXX_ERROR(logger_, "Couldn't establish " #ifdef ENABLE_SECURITY @@ -465,7 +499,7 @@ void ConnectionHandlerImpl::OnSessionStartedCallback( sync_primitives::AutoReadLock read_lock(connection_handler_observer_lock_); if (connection_handler_observer_) { const uint32_t session_key = - KeyFromPair(connection_handle, context.new_session_id_); + KeyFromPair(primary_connection_handle, context.new_session_id_); { sync_primitives::AutoLock auto_lock(start_service_context_map_lock_); @@ -502,10 +536,12 @@ void ConnectionHandlerImpl::NotifyServiceStartedResult( start_service_context_map_.erase(it); } + // We need the context's primary connection so we can manage its services list Connection* connection = NULL; { sync_primitives::AutoReadLock lock(connection_list_lock_); - ConnectionList::iterator it = connection_list_.find(context.connection_id_); + ConnectionList::iterator it = + connection_list_.find(context.primary_connection_id_); if (connection_list_.end() == it) { LOG4CXX_ERROR(logger_, "connection not found"); return; @@ -538,6 +574,28 @@ void ConnectionHandlerImpl::OnApplicationFloodCallBack( uint8_t session_id = 0; PairFromKey(connection_key, &connection_handle, &session_id); + // In case this is a Session running on a Secondary Transport, + // "connection_handle" will reflect the active (secondary) transport. + // To close the conneciton and its sessions properly, we need to find + // the Sessions's primary transport/connection. + if (session_id != 0) { + SessionTransports st = GetSessionTransports(session_id); + if (st.primary_transport == 0) { + LOG4CXX_WARN(logger_, + "OnApplicationFloodCallBack could not find Session in the " + "Session/Connection Map!"); + } else { + LOG4CXX_INFO(logger_, + "OnApplicationFloodCallBack found session " + << static_cast<int>(session_id) + << " with primary connection " + << static_cast<int>(st.primary_transport) + << " and secondary connection " + << static_cast<int>(st.secondary_transport)); + connection_handle = st.primary_transport; + } + } + LOG4CXX_INFO(logger_, "Disconnect flooding application"); if (session_id != 0) { CloseSession(connection_handle, session_id, kFlood); @@ -555,6 +613,28 @@ void ConnectionHandlerImpl::OnMalformedMessageCallback( uint8_t session_id = 0; PairFromKey(connection_key, &connection_handle, &session_id); + // In case this is a Session running on a Secondary Transport, + // "connection_handle" will reflect the active (secondary) transport. + // To close the conneciton and its sessions properly, we need to find + // the Sessions's primary transport/connection. + if (session_id != 0) { + SessionTransports st = GetSessionTransports(session_id); + if (st.primary_transport == 0) { + LOG4CXX_WARN(logger_, + "OnMalformedMessageCallback could not find Session in the " + "Session/Connection Map!"); + } else { + LOG4CXX_INFO(logger_, + "OnMalformedMessageCallback found session " + << static_cast<int>(session_id) + << " with primary connection " + << static_cast<int>(st.primary_transport) + << " and secondary connection " + << static_cast<int>(st.secondary_transport)); + connection_handle = st.primary_transport; + } + } + LOG4CXX_INFO(logger_, "Disconnect malformed messaging application"); CloseConnectionSessions(connection_handle, kMalformed); CloseConnection(connection_handle); @@ -577,8 +657,33 @@ uint32_t ConnectionHandlerImpl::OnSessionEndedCallback( const protocol_handler::ServiceType& service_type) { LOG4CXX_AUTO_TRACE(logger_); + // In case this is a Session running on a Secondary Transport, we need to + // find the Sessions's primary transport. In this case, "connection_handle" + // reflects the secondary transport, which we need for the various callbacks, + // so they can send appropriate Ack or NAK messages on the correct transport. + transport_manager::ConnectionUID primary_connection_handle = + connection_handle; + if (session_id != 0) { + SessionTransports st = GetSessionTransports(session_id); + if (st.primary_transport == 0) { + LOG4CXX_WARN(logger_, + "OnSessionEndedCallback could not find Session in the " + "Session/Connection Map!"); + } else { + LOG4CXX_INFO(logger_, + "OnSessionEndedCallback found session " + << static_cast<int>(session_id) + << " with primary connection " + << static_cast<int>(st.primary_transport) + << " and secondary connection " + << static_cast<int>(st.secondary_transport)); + primary_connection_handle = st.primary_transport; + } + } + connection_list_lock_.AcquireForReading(); - ConnectionList::iterator it = connection_list_.find(connection_handle); + ConnectionList::iterator it = + connection_list_.find(primary_connection_handle); if (connection_list_.end() == it) { LOG4CXX_WARN(logger_, "Unknown connection!"); connection_list_lock_.Release(); @@ -588,7 +693,8 @@ uint32_t ConnectionHandlerImpl::OnSessionEndedCallback( connection_list_lock_.Release(); Connection* connection = connection_item.second; - const uint32_t session_key = KeyFromPair(connection_handle, session_id); + const uint32_t session_key = + KeyFromPair(primary_connection_handle, session_id); if (protocol_handler::kRpc == service_type) { LOG4CXX_INFO(logger_, @@ -631,6 +737,178 @@ uint32_t ConnectionHandlerImpl::OnSessionEndedCallback( return session_key; } +bool ConnectionHandlerImpl::OnSecondaryTransportStarted( + transport_manager::ConnectionUID& primary_connection_handle, + const transport_manager::ConnectionUID secondary_connection_handle, + const uint8_t session_id) { + LOG4CXX_AUTO_TRACE(logger_); + + if (session_id == 0) { + LOG4CXX_WARN(logger_, "Session id for secondary transport is invalid"); + return false; + } + + DeviceHandle device_handle; + Connection* connection; + { + sync_primitives::AutoReadLock lock(connection_list_lock_); + ConnectionList::iterator it = + connection_list_.find(secondary_connection_handle); + if (connection_list_.end() == it) { + LOG4CXX_WARN(logger_, + "Unknown connection " << secondary_connection_handle); + return false; + } + + connection = it->second; + device_handle = connection->connection_device_handle(); + } + + // Add the secondary transport connection ID to the SessionConnectionMap + SessionTransports st = + SetSecondaryTransportID(session_id, secondary_connection_handle); + primary_connection_handle = st.primary_transport; + if (st.secondary_transport != secondary_connection_handle) { + LOG4CXX_WARN(logger_, + "Failed setting the session's secondary transport ID"); + return false; + } + + connection->SetPrimaryConnectionHandle(primary_connection_handle); + + const uint32_t session_key = + KeyFromPair(primary_connection_handle, session_id); + + sync_primitives::AutoReadLock read_lock(connection_handler_observer_lock_); + if (connection_handler_observer_) { + LOG4CXX_TRACE(logger_, + "Calling Connection Handler Observer's " + "OnSecondaryTransportStartedCallback"); + connection_handler_observer_->OnSecondaryTransportStartedCallback( + device_handle, session_key); + } + + return true; +} + +void ConnectionHandlerImpl::OnSecondaryTransportEnded( + const transport_manager::ConnectionUID primary_connection_handle, + const transport_manager::ConnectionUID secondary_connection_handle) { + LOG4CXX_AUTO_TRACE(logger_); + + LOG4CXX_INFO(logger_, + "Secondary Transport: " + << static_cast<int32_t>(secondary_connection_handle) + << " ended. Cleaning up services from primary connection ID " + << static_cast<int32_t>(primary_connection_handle)); + connection_list_lock_.AcquireForReading(); + ConnectionList::iterator itr = + connection_list_.find(primary_connection_handle); + if (connection_list_.end() == itr) { + LOG4CXX_ERROR(logger_, "Primary Connection not found!"); + connection_list_lock_.Release(); + return; + } + Connection* connection = itr->second; + connection_list_lock_.Release(); + + if (connection != NULL) { + std::list<protocol_handler::ServiceType> removed_services_list; + uint8_t session_id = connection->RemoveSecondaryServices( + secondary_connection_handle, removed_services_list); + + if (session_id == 0) { + // The secondary services have already been removed from the primary + // connection, so we find the session associated with this secondary + // transport in the SessionConnectionMap + session_id = + GetSessionIdFromSecondaryTransport(secondary_connection_handle); + } + + if (session_id != 0) { + { + sync_primitives::AutoReadLock read_lock( + connection_handler_observer_lock_); + if (connection_handler_observer_) { + const uint32_t session_key = + KeyFromPair(primary_connection_handle, session_id); + + // Walk the returned list of services and call the ServiceEnded + // callback for each + std::list<protocol_handler::ServiceType>::const_iterator it = + removed_services_list.begin(); + for (; removed_services_list.end() != it; ++it) { + connection_handler_observer_->OnServiceEndedCallback( + session_key, *it, CloseSessionReason::kCommon); + } + + connection_handler_observer_->OnSecondaryTransportEndedCallback( + session_key); + } + } + + // Clear the secondary connection from the Session/Connection map entry + // associated with this session + SetSecondaryTransportID(session_id, 0); + } + } +} + +const std::string +ConnectionHandlerImpl::TransportTypeProfileStringFromConnHandle( + transport_manager::ConnectionUID connection_handle) const { + LOG4CXX_AUTO_TRACE(logger_); + + sync_primitives::AutoReadLock lock(connection_list_lock_); + ConnectionList::const_iterator it = connection_list_.find(connection_handle); + if (connection_list_.end() == it) { + LOG4CXX_WARN(logger_, "Unknown connection " << connection_handle); + return std::string(); + } else { + DeviceHandle device_handle = it->second->connection_device_handle(); + return TransportTypeProfileStringFromDeviceHandle(device_handle); + } +} + +const std::string +ConnectionHandlerImpl::TransportTypeProfileStringFromDeviceHandle( + DeviceHandle device_handle) const { + std::string connection_type; + DeviceMap::const_iterator it = device_list_.find(device_handle); + if (device_list_.end() == it) { + LOG4CXX_ERROR(logger_, "Device not found!"); + } else { + connection_type = it->second.connection_type(); + } + + // Caution: this should be in sync with devicesType map in + // transport_adapter_impl.cc + if (connection_type == "USB_AOA") { + return std::string("AOA_USB"); + } else if (connection_type == "BLUETOOTH") { + return std::string("SPP_BLUETOOTH"); + } else if (connection_type == "USB_IOS") { + return std::string("IAP_USB"); + } else if (connection_type == "BLUETOOTH_IOS") { + return std::string("IAP_BLUETOOTH"); + } else if (connection_type == "WIFI") { + return std::string("TCP_WIFI"); + } else if (connection_type == "USB_IOS_HOST_MODE") { + return std::string("IAP_USB_HOST_MODE"); + } else if (connection_type == "USB_IOS_DEVICE_MODE") { + return std::string("IAP_USB_DEVICE_MODE"); + } else if (connection_type == "CARPLAY_WIRELESS_IOS") { + return std::string("IAP_CARPLAY"); +#ifdef BUILD_TESTS + } else if (connection_type == "BTMAC") { + return std::string("BTMAC"); +#endif + } else { + LOG4CXX_WARN(logger_, "Unknown transport type string: " << connection_type); + return std::string(); + } +} + uint32_t ConnectionHandlerImpl::KeyFromPair( transport_manager::ConnectionUID connection_handle, uint8_t session_id) const { @@ -639,7 +917,7 @@ uint32_t ConnectionHandlerImpl::KeyFromPair( "Key for ConnectionHandle:" << static_cast<uint32_t>(connection_handle) << " Session:" << static_cast<uint32_t>(session_id) - << " is: " << static_cast<uint32_t>(key)); + << " is: 0x" << std::hex << static_cast<uint32_t>(key)); if (protocol_handler::HASH_ID_WRONG == key) { LOG4CXX_ERROR(logger_, "Connection key is WRONG_HASH_ID " @@ -722,6 +1000,139 @@ DevicesDiscoveryStarter& ConnectionHandlerImpl::get_device_discovery_starter() { return *this; } +// Finds a key not presented in std::map<unsigned char, T> +// Returns 0 if that key not found +namespace { +template <class T> +uint32_t findGap(const std::map<unsigned char, T>& map) { + for (uint32_t i = 1; i <= UCHAR_MAX; ++i) { + if (map.find(i) == map.end()) { + return i; + } + } + return 0; +} +} // namespace + +uint32_t ConnectionHandlerImpl::AddSession( + const transport_manager::ConnectionUID primary_transport_id) { + LOG4CXX_AUTO_TRACE(logger_); + + sync_primitives::AutoLock auto_lock(session_connection_map_lock_ptr_); + const uint32_t session_id = findGap(session_connection_map_); + if (session_id > 0) { + LOG4CXX_INFO(logger_, + "New session ID " << session_id << " and Connection Id " + << static_cast<int>(primary_transport_id) + << " added to Session/Connection Map"); + SessionTransports st; + st.primary_transport = primary_transport_id; + st.secondary_transport = 0; + session_connection_map_[session_id] = st; + } else { + LOG4CXX_WARN(logger_, + "Session/Connection Map could not create a new session ID!!!"); + } + + return session_id; +} + +bool ConnectionHandlerImpl::RemoveSession(uint8_t session_id) { + LOG4CXX_AUTO_TRACE(logger_); + + sync_primitives::AutoLock auto_lock(session_connection_map_lock_ptr_); + SessionConnectionMap::iterator itr = session_connection_map_.find(session_id); + if (session_connection_map_.end() == itr) { + LOG4CXX_WARN(logger_, "Session not found in Session/Connection Map!"); + return false; + } + + LOG4CXX_INFO(logger_, + "Removed Session ID " << static_cast<int>(session_id) + << " from Session/Connection Map"); + session_connection_map_.erase(session_id); + return true; +} + +DataAccessor<SessionConnectionMap> +ConnectionHandlerImpl::session_connection_map() { + return DataAccessor<SessionConnectionMap>(session_connection_map_, + session_connection_map_lock_ptr_); +} + +SessionTransports ConnectionHandlerImpl::SetSecondaryTransportID( + uint8_t session_id, + transport_manager::ConnectionUID secondary_transport_id) { + SessionTransports st; + + sync_primitives::AutoLock auto_lock(session_connection_map_lock_ptr_); + SessionConnectionMap::iterator it = session_connection_map_.find(session_id); + if (session_connection_map_.end() == it) { + LOG4CXX_WARN(logger_, + "SetSecondaryTransportID: session ID " + << static_cast<int>(session_id) + << " not found in Session/Connection map"); + st.primary_transport = 0; + st.secondary_transport = 0; + } else { + st = it->second; + + // The only time we overwrite an existing entry in the map is if the new + // secondary transport ID is kDisabledSecondary, which effectively DISABLES + // the secondary transport feature for the session, or if the new secondary + // transport ID is 0, which means a secondary transport has shut down + if (st.secondary_transport != 0 && + secondary_transport_id != kDisabledSecondary && + secondary_transport_id != 0) { + LOG4CXX_WARN(logger_, + "SetSecondaryTransportID: session ID " + << static_cast<int>(session_id) + << " already has a secondary connection " + << static_cast<int>(st.secondary_transport) + << " in the Session/Connection map"); + } else { + st.secondary_transport = secondary_transport_id; + session_connection_map_[session_id] = st; + } + } + + return st; +} + +const SessionTransports ConnectionHandlerImpl::GetSessionTransports( + uint8_t session_id) const { + SessionTransports st; + sync_primitives::AutoLock auto_lock(session_connection_map_lock_ptr_); + SessionConnectionMap::const_iterator it = + session_connection_map_.find(session_id); + if (session_connection_map_.end() == it) { + st.primary_transport = 0; + st.secondary_transport = 0; + } else { + st = it->second; + } + + return st; +} + +const uint8_t ConnectionHandlerImpl::GetSessionIdFromSecondaryTransport( + transport_manager::ConnectionUID secondary_transport_id) const { + sync_primitives::AutoLock auto_lock(session_connection_map_lock_ptr_); + SessionConnectionMap::const_iterator it = session_connection_map_.begin(); + for (; session_connection_map_.end() != it; it++) { + SessionTransports st = it->second; + if (st.secondary_transport == secondary_transport_id) { + return it->first; + } + } + + LOG4CXX_ERROR(logger_, + "Could not find secondary transport ID " + << static_cast<int>(secondary_transport_id) + << " in the Session/Connection map"); + return 0; +} + struct CompareMAC { explicit CompareMAC(const std::string& mac) : mac_(mac) {} bool operator()(const DeviceMap::value_type& device) { @@ -1098,8 +1509,28 @@ void ConnectionHandlerImpl::SendEndService(uint32_t key, uint8_t service_type) { uint32_t connection_handle = 0; uint8_t session_id = 0; PairFromKey(key, &connection_handle, &session_id); - protocol_handler_->SendEndService( - connection_handle, session_id, service_type); + + // If the service is running on a secondary transport, we need to retrieve + // that transport from the SessionConnection Map + SessionTransports st = GetSessionTransports(session_id); + if (st.primary_transport == 0) { + LOG4CXX_WARN(logger_, + "SendEndService could not find Session in the " + "Session/Connection Map!"); + } else { + LOG4CXX_INFO(logger_, + "SendEndService found session " + << static_cast<int>(session_id) + << " with primary connection " + << static_cast<int>(st.primary_transport) + << " and secondary connection " + << static_cast<int>(st.secondary_transport)); + + protocol_handler_->SendEndService(st.primary_transport, + st.secondary_transport, + session_id, + service_type); + } } } @@ -1166,6 +1597,10 @@ void ConnectionHandlerImpl::OnConnectionEnded( sync_primitives::AutoReadLock read_lock(connection_handler_observer_lock_); if (connection_handler_observer_ && connection.get() != NULL) { + // We have to remember the Connection object we just removed from + // connection_list_, because we will need to retrieve the protocol + // version from it inside of OnServiceEndedCallback + ending_connection_ = connection.get(); const SessionMap session_map = connection->session_map(); for (SessionMap::const_iterator session_it = session_map.begin(); @@ -1182,6 +1617,13 @@ void ConnectionHandlerImpl::OnConnectionEnded( session_key, service_it->service_type, CloseSessionReason::kCommon); } } + ending_connection_ = NULL; + } + + ConnectionHandle primary_connection_handle = + connection->primary_connection_handle(); + if (primary_connection_handle != 0) { + OnSecondaryTransportEnded(primary_connection_handle, connection_id); } } @@ -1222,6 +1664,10 @@ bool ConnectionHandlerImpl::ProtocolVersionUsed( ConnectionList::const_iterator it = connection_list_.find(connection_id); if (connection_list_.end() != it) { return it->second->ProtocolVersion(session_id, protocol_version); + } else if (ending_connection_ && + static_cast<uint32_t>(ending_connection_->connection_handle()) == + connection_id) { + return ending_connection_->ProtocolVersion(session_id, protocol_version); } LOG4CXX_WARN(logger_, "Connection not found !"); return false; diff --git a/src/components/connection_handler/test/connection_handler_impl_test.cc b/src/components/connection_handler/test/connection_handler_impl_test.cc index 56dbf6b9de..f66d6b350c 100644 --- a/src/components/connection_handler/test/connection_handler_impl_test.cc +++ b/src/components/connection_handler/test/connection_handler_impl_test.cc @@ -198,10 +198,12 @@ class ConnectionHandlerTest : public ::testing::Test { } // Check Service Wrapper - void CheckServiceExists(const int connectionId, - const int session_id, - const ::protocol_handler::ServiceType serviceId, - const bool exists) { + void CheckServiceExists( + const int connectionId, + const int session_id, + const ::protocol_handler::ServiceType serviceId, + const ::transport_manager::ConnectionUID serviceConnectionId, + const bool exists) { // Check all trees to find Service and check own protected value const ConnectionList& connection_list = connection_handler_->getConnectionList(); @@ -221,6 +223,7 @@ class ConnectionHandlerTest : public ::testing::Test { std::find(service_list.begin(), service_list.end(), serviceId); if (exists) { ASSERT_NE(serv_it, service_list.end()); + ASSERT_EQ(serv_it->connection_id, serviceConnectionId); } else { ASSERT_EQ(serv_it, service_list.end()); } @@ -306,6 +309,31 @@ class ConnectionHandlerTest : public ::testing::Test { connection->ProtocolVersion(session_id, check_protocol_version)); EXPECT_EQ(check_protocol_version, protocol_version); } + void AddSecondaryTestService(ServiceType service_type) { + EXPECT_NE(0u, out_context_.new_session_id_); + EXPECT_EQ(SessionHash(uid_, out_context_.new_session_id_), + out_context_.hash_id_); + CheckSessionExists(uid_, out_context_.new_session_id_); + + // Set protocol version to 5 + ChangeProtocol(uid_, + out_context_.new_session_id_, + protocol_handler::PROTOCOL_VERSION_5); + + connection_handler_test::MockConnectionHandlerObserver + temp_connection_handler_observer; + connection_handler_->set_connection_handler_observer( + &temp_connection_handler_observer); + EXPECT_CALL(temp_connection_handler_observer, + OnServiceStartedCallback(_, _, service_type, _)).Times(1); + + connection_handler_->OnSessionStartedCallback(2u, + out_context_.new_session_id_, + service_type, + PROTECTION_OFF, + static_cast<BsonObject*>(0)); + connection_handler_->set_connection_handler_observer(NULL); + } ConnectionHandlerImpl* connection_handler_; testing::NiceMock<transport_manager_test::MockTransportManager> @@ -518,7 +546,7 @@ MATCHER_P(SameDevice, device, "") { TEST_F(ConnectionHandlerTest, SendEndServiceWithoutSetProtocolHandler) { AddTestDeviceConnection(); AddTestSession(); - EXPECT_CALL(mock_protocol_handler_, SendEndService(_, _, kRpc)).Times(0); + EXPECT_CALL(mock_protocol_handler_, SendEndService(_, _, _, kRpc)).Times(0); connection_handler_->SendEndService(connection_key_, kRpc); } @@ -526,7 +554,7 @@ TEST_F(ConnectionHandlerTest, SendEndService) { AddTestDeviceConnection(); AddTestSession(); connection_handler_->set_protocol_handler(&mock_protocol_handler_); - EXPECT_CALL(mock_protocol_handler_, SendEndService(_, _, kRpc)); + EXPECT_CALL(mock_protocol_handler_, SendEndService(_, _, _, kRpc)); connection_handler_->SendEndService(connection_key_, kRpc); } @@ -1112,7 +1140,7 @@ TEST_F(ConnectionHandlerTest, StartService_withServices) { PROTECTION_OFF, static_cast<BsonObject*>(NULL)); EXPECT_NE(0u, audio_context.new_session_id_); - CheckServiceExists(uid_, audio_context.new_session_id_, kAudio, true); + CheckServiceExists(uid_, audio_context.new_session_id_, kAudio, uid_, true); EXPECT_EQ(protocol_handler::HASH_ID_NOT_SUPPORTED, audio_context.hash_id_); // Start Audio service @@ -1122,7 +1150,8 @@ TEST_F(ConnectionHandlerTest, StartService_withServices) { PROTECTION_OFF, static_cast<BsonObject*>(NULL)); EXPECT_NE(0u, video_context.new_session_id_); - CheckServiceExists(uid_, video_context.new_session_id_, kMobileNav, true); + CheckServiceExists( + uid_, video_context.new_session_id_, kMobileNav, uid_, true); EXPECT_EQ(protocol_handler::HASH_ID_NOT_SUPPORTED, video_context.hash_id_); connection_handler_->set_protocol_handler(NULL); @@ -1150,7 +1179,8 @@ TEST_F(ConnectionHandlerTest, StartService_withServices_withParams) { PROTECTION_OFF, dummy_param); EXPECT_EQ(out_context_.new_session_id_, video_context.new_session_id_); - CheckServiceExists(uid_, out_context_.new_session_id_, kMobileNav, true); + CheckServiceExists( + uid_, out_context_.new_session_id_, kMobileNav, uid_, true); EXPECT_EQ(protocol_handler::HASH_ID_NOT_SUPPORTED, video_context.hash_id_); connection_handler_->set_protocol_handler(NULL); @@ -1174,7 +1204,7 @@ TEST_F(ConnectionHandlerTest, ServiceStop_UnExistService) { connection_handler_->OnSessionEndedCallback( uid_, out_context_.new_session_id_, &dummy_hash, kAudio); EXPECT_EQ(0u, end_session_result); - CheckServiceExists(uid_, out_context_.new_session_id_, kAudio, false); + CheckServiceExists(uid_, out_context_.new_session_id_, kAudio, uid_, false); } TEST_F(ConnectionHandlerTest, ServiceStop) { @@ -1204,7 +1234,7 @@ TEST_F(ConnectionHandlerTest, ServiceStop) { connection_handler_->OnSessionEndedCallback( uid_, out_context_.new_session_id_, &some_hash_id, kAudio); EXPECT_EQ(connection_key_, end_session_result); - CheckServiceExists(uid_, out_context_.new_session_id_, kAudio, false); + CheckServiceExists(uid_, out_context_.new_session_id_, kAudio, uid_, false); } } @@ -2003,6 +2033,193 @@ TEST_F(ConnectionHandlerTest, OnDeviceConnectionSwitching) { connection_handler_->OnDeviceSwitchingStart(mac_address_, second_mac_address); } +TEST_F(ConnectionHandlerTest, StartStopSecondarySession) { + // Add virtual device and connection + AddTestDeviceConnection(); + // Start new session with RPC service + AddTestSession(); + + connection_handler_test::MockConnectionHandlerObserver + mock_connection_handler_observer; + connection_handler_->set_connection_handler_observer( + &mock_connection_handler_observer); + + // Start a session on a secondary transport + const transport_manager::DeviceInfo secondary_device_info( + device_handle_, mac_address_, device_name_, std::string("WIFI")); + const transport_manager::ConnectionUID secondary_uid = 2u; + // Add Device and connection + ON_CALL(mock_connection_handler_settings, heart_beat_timeout()) + .WillByDefault(Return(1000u)); + connection_handler_->addDeviceConnection(secondary_device_info, + secondary_uid); + + EXPECT_CALL(mock_connection_handler_observer, + OnSecondaryTransportStartedCallback(device_handle_, _)).Times(1); + + connection_handler_->OnSecondaryTransportStarted( + uid_, secondary_uid, out_context_.new_session_id_); + + SessionTransports st = + connection_handler_->GetSessionTransports(out_context_.new_session_id_); + EXPECT_EQ(st.primary_transport, uid_); + EXPECT_EQ(st.secondary_transport, secondary_uid); + + AddSecondaryTestService(kAudio); + AddSecondaryTestService(kMobileNav); + + CheckServiceExists( + uid_, out_context_.new_session_id_, kAudio, secondary_uid, true); + CheckServiceExists( + uid_, out_context_.new_session_id_, kMobileNav, secondary_uid, true); + + connection_handler_->set_connection_handler_observer( + &mock_connection_handler_observer); + EXPECT_CALL(mock_connection_handler_observer, + OnSecondaryTransportEndedCallback(_)).Times(1); + EXPECT_CALL(mock_connection_handler_observer, + OnServiceEndedCallback(_, kAudio, _)).Times(1); + EXPECT_CALL(mock_connection_handler_observer, + OnServiceEndedCallback(_, kMobileNav, _)).Times(1); + + connection_handler_->OnSecondaryTransportEnded(uid_, secondary_uid); + + st = connection_handler_->GetSessionTransports(out_context_.new_session_id_); + EXPECT_EQ(st.primary_transport, uid_); + EXPECT_EQ(st.secondary_transport, 0u); + + CheckServiceExists( + uid_, out_context_.new_session_id_, kAudio, secondary_uid, false); + CheckServiceExists( + uid_, out_context_.new_session_id_, kMobileNav, secondary_uid, false); +} + +TEST_F(ConnectionHandlerTest, StopSecondarySession_NoService) { + AddTestDeviceConnection(); + AddTestSession(); + + connection_handler_test::MockConnectionHandlerObserver + mock_connection_handler_observer; + connection_handler_->set_connection_handler_observer( + &mock_connection_handler_observer); + + const transport_manager::DeviceInfo secondary_device_info( + device_handle_, mac_address_, device_name_, std::string("WIFI")); + const transport_manager::ConnectionUID secondary_uid = 123u; + ON_CALL(mock_connection_handler_settings, heart_beat_timeout()) + .WillByDefault(Return(1000u)); + connection_handler_->addDeviceConnection(secondary_device_info, + secondary_uid); + + EXPECT_CALL(mock_connection_handler_observer, + OnSecondaryTransportStartedCallback(device_handle_, _)).Times(1); + connection_handler_->OnSecondaryTransportStarted( + uid_, secondary_uid, out_context_.new_session_id_); + + // check if OnSecondaryTransportEndedCallback is triggered with correct + // session ID even if we don't have any services + EXPECT_CALL(mock_connection_handler_observer, + OnSecondaryTransportEndedCallback(_)); + EXPECT_CALL(mock_connection_handler_observer, OnServiceEndedCallback(_, _, _)) + .Times(0); + + connection_handler_->OnSecondaryTransportEnded(uid_, secondary_uid); + + SessionTransports st = + connection_handler_->GetSessionTransports(out_context_.new_session_id_); + EXPECT_EQ(st.primary_transport, uid_); + EXPECT_EQ(st.secondary_transport, 0u); +} + +TEST_F(ConnectionHandlerTest, ConnectionType_valid) { + AddTestDeviceConnection(); + AddTestSession(); + + std::string ret = + connection_handler_->TransportTypeProfileStringFromConnHandle(uid_); + EXPECT_EQ(connection_type_, ret); +} + +TEST_F(ConnectionHandlerTest, ConnectionType_invalid) { + AddTestDeviceConnection(); + + transport_manager::ConnectionUID invalid_uid = 12345; + ASSERT_TRUE(invalid_uid != uid_); + std::string ret = + connection_handler_->TransportTypeProfileStringFromConnHandle( + invalid_uid); + EXPECT_EQ(std::string(), ret); +} + +TEST_F(ConnectionHandlerTest, SetSecondaryTransportID_UpdateSuccess) { + uint8_t session_id = 123; + transport_manager::ConnectionUID primary_uid = 100; + transport_manager::ConnectionUID secondary_uid = 0; + + SessionConnectionMap& session_connection_map = + connection_handler_->getSessionConnectionMap(); + // secondary transport's ID is 0 + SessionTransports st = {primary_uid, secondary_uid}; + session_connection_map[session_id] = st; + + secondary_uid = 200; + st = connection_handler_->SetSecondaryTransportID(session_id, secondary_uid); + EXPECT_EQ(primary_uid, st.primary_transport); + EXPECT_EQ(secondary_uid, st.secondary_transport); +} + +TEST_F(ConnectionHandlerTest, SetSecondaryTransportID_UpdateFailure) { + uint8_t session_id = 123; + transport_manager::ConnectionUID primary_uid = 100; + transport_manager::ConnectionUID secondary_uid = 300; + + SessionConnectionMap& session_connection_map = + connection_handler_->getSessionConnectionMap(); + // secondary transport's ID is already assigned + SessionTransports st = {primary_uid, secondary_uid}; + session_connection_map[session_id] = st; + + st = connection_handler_->SetSecondaryTransportID(session_id, 500); + EXPECT_EQ(primary_uid, st.primary_transport); + // secondary transport's ID is NOT updated + EXPECT_EQ(secondary_uid, st.secondary_transport); +} + +TEST_F(ConnectionHandlerTest, SetSecondaryTransportID_OverwirteSecondaryUID) { + uint8_t session_id = 123; + transport_manager::ConnectionUID primary_uid = 200; + transport_manager::ConnectionUID secondary_uid = 500; + + SessionConnectionMap& session_connection_map = + connection_handler_->getSessionConnectionMap(); + SessionTransports st = {primary_uid, secondary_uid}; + session_connection_map[session_id] = st; + + secondary_uid = kDisabledSecondary; + st = connection_handler_->SetSecondaryTransportID(session_id, secondary_uid); + EXPECT_EQ(primary_uid, st.primary_transport); + // secondary transport's ID is updated + EXPECT_EQ(secondary_uid, st.secondary_transport); +} + +TEST_F(ConnectionHandlerTest, SetSecondaryTransportID_Failure) { + uint8_t session_id = 123; + transport_manager::ConnectionUID primary_uid = 100; + transport_manager::ConnectionUID secondary_uid = 0; + + SessionConnectionMap& session_connection_map = + connection_handler_->getSessionConnectionMap(); + SessionTransports st = {primary_uid, secondary_uid}; + session_connection_map[session_id] = st; + + uint8_t invalid_session_id = 10; + secondary_uid = 300; + st = connection_handler_->SetSecondaryTransportID(invalid_session_id, + secondary_uid); + EXPECT_EQ(0u, st.primary_transport); + EXPECT_EQ(0u, st.secondary_transport); +} + } // namespace connection_handler_test } // namespace components } // namespace test diff --git a/src/components/connection_handler/test/connection_test.cc b/src/components/connection_handler/test/connection_test.cc index de21dd1e97..98b83f5fc4 100644 --- a/src/components/connection_handler/test/connection_test.cc +++ b/src/components/connection_handler/test/connection_test.cc @@ -36,6 +36,7 @@ #include "protocol/common.h" #include "connection_handler/connection.h" +#include "connection_handler/mock_connection_handler.h" #include "connection_handler/connection_handler_impl.h" #include "protocol/service_type.h" #include "connection_handler/mock_connection_handler_settings.h" @@ -56,6 +57,8 @@ namespace connection_handler_test { using namespace ::connection_handler; using namespace ::protocol_handler; +using ::testing::Return; + class ConnectionTest : public ::testing::Test { protected: void SetUp() OVERRIDE { @@ -78,7 +81,7 @@ class ConnectionTest : public ::testing::Test { session_id, protocol_handler::PROTOCOL_VERSION_3); } void StartDefaultSession() { - session_id = connection_->AddNewSession(); + session_id = connection_->AddNewSession(kDefaultConnectionHandle); EXPECT_NE(session_id, 0u); const SessionMap sessionMap = connection_->session_map(); EXPECT_FALSE(sessionMap.empty()); @@ -88,13 +91,14 @@ class ConnectionTest : public ::testing::Test { std::find(serviceList.begin(), serviceList.end(), kRpc); const bool found_result = (it != serviceList.end()); EXPECT_TRUE(found_result); + EXPECT_EQ(connection_->primary_connection_handle(), 0); } void AddNewService(const ServiceType service_type, const bool protection, const bool expect_add_new_service_call_result, const bool expect_exist_service) { - const bool result = - connection_->AddNewService(session_id, service_type, protection); + const bool result = connection_->AddNewService( + session_id, service_type, protection, kDefaultConnectionHandle); EXPECT_EQ(result, expect_add_new_service_call_result); #ifdef ENABLE_SECURITY @@ -110,12 +114,35 @@ class ConnectionTest : public ::testing::Test { std::find(newServiceList.begin(), newServiceList.end(), service_type); const bool found_result = it != newServiceList.end(); EXPECT_EQ(expect_exist_service, found_result); -#ifdef ENABLE_SECURITY if (found_result) { const Service& service = *it; + transport_manager::ConnectionUID expected_connection_handle = + kDefaultConnectionHandle; + EXPECT_EQ(service.connection_id, expected_connection_handle); +#ifdef ENABLE_SECURITY EXPECT_EQ(service.is_protected_, protection); - } #endif // ENABLE_SECURITY + } + } + void AddNewSecondaryService(const ServiceType service_type) { + const bool result = connection_->AddNewService( + session_id, service_type, false, kSecondaryConnectionHandle); + EXPECT_EQ(result, true); + + const SessionMap session_map = connection_->session_map(); + EXPECT_FALSE(session_map.empty()); + const ServiceList newServiceList = session_map.begin()->second.service_list; + EXPECT_FALSE(newServiceList.empty()); + const ServiceList::const_iterator it = + std::find(newServiceList.begin(), newServiceList.end(), service_type); + const bool found_result = it != newServiceList.end(); + EXPECT_TRUE(found_result); + if (found_result) { + const Service& service = *it; + transport_manager::ConnectionUID expected_secondary_connection_handle = + kSecondaryConnectionHandle; + EXPECT_EQ(service.connection_id, expected_secondary_connection_handle); + } } void RemoveService(const ServiceType service_type, @@ -141,6 +168,8 @@ class ConnectionTest : public ::testing::Test { transport_manager_mock; ConnectionHandlerImpl* connection_handler_; uint32_t session_id; + static const transport_manager::ConnectionUID kDefaultConnectionHandle = 1; + static const transport_manager::ConnectionUID kSecondaryConnectionHandle = 2; }; TEST_F(ConnectionTest, Session_TryGetProtocolVersionWithoutSession) { @@ -236,13 +265,17 @@ TEST_F(ConnectionTest, HeartBeat_Protocol5_ZeroHeartBeat_NotSupported) { // Try to add service without session TEST_F(ConnectionTest, Session_AddNewServiceWithoutSession) { - EXPECT_EQ(connection_->AddNewService(session_id, kAudio, true), + EXPECT_EQ(connection_->AddNewService( + session_id, kAudio, true, kDefaultConnectionHandle), EXPECT_RETURN_FALSE); - EXPECT_EQ(connection_->AddNewService(session_id, kAudio, false), + EXPECT_EQ(connection_->AddNewService( + session_id, kAudio, false, kDefaultConnectionHandle), EXPECT_RETURN_FALSE); - EXPECT_EQ(connection_->AddNewService(session_id, kMobileNav, true), + EXPECT_EQ(connection_->AddNewService( + session_id, kMobileNav, true, kDefaultConnectionHandle), EXPECT_RETURN_FALSE); - EXPECT_EQ(connection_->AddNewService(session_id, kMobileNav, false), + EXPECT_EQ(connection_->AddNewService( + session_id, kMobileNav, false, kDefaultConnectionHandle), EXPECT_RETURN_FALSE); } @@ -409,6 +442,133 @@ TEST_F(ConnectionTest, RemoveSession) { EXPECT_EQ(0u, connection_->RemoveSession(session_id)); } +TEST_F(ConnectionTest, AddNewSession_VerifyAddSessionCalled) { + MockConnectionHandler mock_connection_handler; + + ConnectionHandle connection_handle = 123; + DeviceHandle device_handle = 0u; + uint32_t heart_beat = 10000u; + Connection* connection = new Connection( + connection_handle, device_handle, &mock_connection_handler, heart_beat); + + transport_manager::ConnectionUID connection_handle_uid = 1; + uint32_t mock_session_id = 2; + EXPECT_CALL(mock_connection_handler, AddSession(connection_handle_uid)) + .WillOnce(Return(mock_session_id)); + + uint32_t sid = connection->AddNewSession(connection_handle_uid); + EXPECT_EQ(mock_session_id, sid); + + EXPECT_CALL(mock_connection_handler, RemoveSession(mock_session_id)) + .WillOnce(Return(true)); // invoked by destructor of connection + delete connection; +} + +TEST_F(ConnectionTest, RemoveSession_VerifyRemoveSessionCalled) { + MockConnectionHandler mock_connection_handler; + + ConnectionHandle connection_handle = 123; + DeviceHandle device_handle = 0u; + uint32_t heart_beat = 10000u; + Connection* connection = new Connection( + connection_handle, device_handle, &mock_connection_handler, heart_beat); + + transport_manager::ConnectionUID connection_handle_uid = 1; + uint32_t mock_session_id = 10; + EXPECT_CALL(mock_connection_handler, AddSession(connection_handle_uid)) + .WillOnce(Return(mock_session_id)); + EXPECT_CALL(mock_connection_handler, + RemoveSession(static_cast<uint8_t>(mock_session_id))) + .WillOnce(Return(true)); + + uint32_t sid = connection->AddNewSession(connection_handle_uid); + + uint32_t ret = connection->RemoveSession(sid); + EXPECT_EQ(sid, ret); + + delete connection; +} + +TEST_F(ConnectionTest, SecondarySessionTest) { + StartSession(); + AddNewService( + kRpc, PROTECTION_OFF, EXPECT_RETURN_FALSE, EXPECT_SERVICE_EXISTS); + + const ConnectionHandle connectionHandle = 0; + const DeviceHandle device_handle = 0u; + const uint32_t heart_beat = 0u; + Connection* secondary_connection = new Connection( + connectionHandle, device_handle, connection_handler_, heart_beat); + + secondary_connection->SetPrimaryConnectionHandle(kDefaultConnectionHandle); + connection_handler::ConnectionHandle expected_primary_connection_handle = + kDefaultConnectionHandle; + EXPECT_EQ(secondary_connection->primary_connection_handle(), + expected_primary_connection_handle); + + AddNewSecondaryService(kAudio); + AddNewSecondaryService(kMobileNav); + + delete secondary_connection; +} + +TEST_F(ConnectionTest, RemoveSecondaryServices_SUCCESS) { + StartSession(); + + ServiceType services[2] = {kMobileNav, kAudio}; + AddNewSecondaryService(services[0]); + AddNewSecondaryService(services[1]); + size_t services_count = sizeof(services) / sizeof(services[0]); + + std::list<ServiceType> removed_services; + uint8_t ret_session_id = connection_->RemoveSecondaryServices( + kSecondaryConnectionHandle, removed_services); + + // check return value + EXPECT_EQ(session_id, ret_session_id); + // check returned list + EXPECT_EQ(services_count, removed_services.size()); + std::list<protocol_handler::ServiceType>::iterator it; + it = std::find(removed_services.begin(), removed_services.end(), services[0]); + EXPECT_TRUE(it != removed_services.end()); + it = std::find(removed_services.begin(), removed_services.end(), services[1]); + EXPECT_TRUE(it != removed_services.end()); +} + +TEST_F(ConnectionTest, RemoveSecondaryServices_NoService) { + StartSession(); + /* do not call AddNewSecondaryService() */ + + std::list<ServiceType> removed_services; + uint8_t ret_session_id = connection_->RemoveSecondaryServices( + kSecondaryConnectionHandle, removed_services); + + // check return value + EXPECT_EQ(0, ret_session_id); + // check returned list + EXPECT_EQ(0u, removed_services.size()); +} + +TEST_F(ConnectionTest, RemoveSecondaryServices_InvalidConnectionHandle) { + StartSession(); + + ServiceType services[2] = {kMobileNav, kAudio}; + AddNewSecondaryService(services[0]); + AddNewSecondaryService(services[1]); + + transport_manager::ConnectionUID invalid_connection_handle = 123; + ASSERT_TRUE(kSecondaryConnectionHandle != invalid_connection_handle); + + std::list<ServiceType> removed_services; + uint8_t ret_session_id = connection_->RemoveSecondaryServices( + invalid_connection_handle, removed_services); + + // check return value + EXPECT_EQ(0, ret_session_id); + // check returned list + EXPECT_EQ(0u, removed_services.size()); +} + #ifdef ENABLE_SECURITY TEST_F(ConnectionTest, SetSSLContextWithoutSession) { diff --git a/src/components/connection_handler/test/heart_beat_monitor_test.cc b/src/components/connection_handler/test/heart_beat_monitor_test.cc index 4c67c97191..e089a07ec4 100644 --- a/src/components/connection_handler/test/heart_beat_monitor_test.cc +++ b/src/components/connection_handler/test/heart_beat_monitor_test.cc @@ -51,6 +51,7 @@ namespace connection_handler_test { using ::testing::DoAll; using ::testing::_; +using ::testing::Return; class HeartBeatMonitorTest : public testing::Test { public: @@ -64,6 +65,8 @@ class HeartBeatMonitorTest : public testing::Test { uint32_t kTimeout; static const connection_handler::ConnectionHandle kConnectionHandle = 0xABCDEF; + static const transport_manager::ConnectionUID kDefaultConnectionHandle = 1; + static const uint32_t kDefaultSessionId = 1; virtual void SetUp() { conn = new connection_handler::Connection( @@ -80,25 +83,40 @@ ACTION_P2(RemoveSession, conn, session_id) { } TEST_F(HeartBeatMonitorTest, TimerNotStarted) { + EXPECT_CALL(connection_handler_mock, AddSession(_)) + .WillOnce(Return(kDefaultSessionId)); + EXPECT_CALL(connection_handler_mock, RemoveSession(kDefaultSessionId)) + .WillOnce(Return(true)); // called by destructor of Connection + // Whithout StartHeartBeat nothing to be call EXPECT_CALL(connection_handler_mock, CloseSession(_, _)).Times(0); EXPECT_CALL(connection_handler_mock, CloseConnection(_)).Times(0); EXPECT_CALL(connection_handler_mock, SendHeartBeat(_, _)).Times(0); - conn->AddNewSession(); + conn->AddNewSession(kDefaultConnectionHandle); } TEST_F(HeartBeatMonitorTest, TimerNotElapsed) { + EXPECT_CALL(connection_handler_mock, AddSession(_)) + .WillOnce(Return(kDefaultSessionId)); + EXPECT_CALL(connection_handler_mock, RemoveSession(kDefaultSessionId)) + .WillOnce(Return(true)); + EXPECT_CALL(connection_handler_mock, SendHeartBeat(_, _)).Times(0); EXPECT_CALL(connection_handler_mock, CloseSession(_, _)).Times(0); EXPECT_CALL(connection_handler_mock, CloseConnection(_)).Times(0); - const uint32_t session = conn->AddNewSession(); + const uint32_t session = conn->AddNewSession(kDefaultConnectionHandle); conn->StartHeartBeat(session); } TEST_F(HeartBeatMonitorTest, TimerElapsed) { - const uint32_t session = conn->AddNewSession(); + EXPECT_CALL(connection_handler_mock, AddSession(_)) + .WillOnce(Return(kDefaultSessionId)); + EXPECT_CALL(connection_handler_mock, RemoveSession(kDefaultSessionId)) + .WillOnce(Return(true)); // invoked by RemoveSession action + + const uint32_t session = conn->AddNewSession(kDefaultConnectionHandle); TestAsyncWaiter waiter; uint32_t times = 0; @@ -121,11 +139,16 @@ TEST_F(HeartBeatMonitorTest, TimerElapsed) { } TEST_F(HeartBeatMonitorTest, KeptAlive) { + EXPECT_CALL(connection_handler_mock, AddSession(_)) + .WillOnce(Return(kDefaultSessionId)); + EXPECT_CALL(connection_handler_mock, RemoveSession(kDefaultSessionId)) + .WillOnce(Return(true)); + EXPECT_CALL(connection_handler_mock, CloseSession(_, _)).Times(0); EXPECT_CALL(connection_handler_mock, CloseConnection(_)).Times(0); EXPECT_CALL(connection_handler_mock, SendHeartBeat(_, _)).Times(0); - const uint32_t session = conn->AddNewSession(); + const uint32_t session = conn->AddNewSession(kDefaultConnectionHandle); conn->StartHeartBeat(session); usleep(kTimeout * MICROSECONDS_IN_MILLISECONDS - MICROSECONDS_IN_SECOND); conn->KeepAlive(session); @@ -137,7 +160,12 @@ TEST_F(HeartBeatMonitorTest, KeptAlive) { } TEST_F(HeartBeatMonitorTest, NotKeptAlive) { - const uint32_t session = conn->AddNewSession(); + EXPECT_CALL(connection_handler_mock, AddSession(_)) + .WillOnce(Return(kDefaultSessionId)); + EXPECT_CALL(connection_handler_mock, RemoveSession(kDefaultSessionId)) + .WillOnce(Return(true)); + + const uint32_t session = conn->AddNewSession(kDefaultConnectionHandle); TestAsyncWaiter waiter; uint32_t times = 0; @@ -167,8 +195,20 @@ TEST_F(HeartBeatMonitorTest, NotKeptAlive) { } TEST_F(HeartBeatMonitorTest, TwoSessionsElapsed) { - const uint32_t kSession1 = conn->AddNewSession(); - const uint32_t kSession2 = conn->AddNewSession(); + const uint32_t kMockSessionId1 = 1; + const uint32_t kMockSessionId2 = 2; + EXPECT_CALL(connection_handler_mock, AddSession(_)) + .WillOnce(Return(kMockSessionId1)) + .WillOnce(Return(kMockSessionId2)); + EXPECT_CALL(connection_handler_mock, RemoveSession(kMockSessionId1)) + .WillOnce(Return(true)); + EXPECT_CALL(connection_handler_mock, RemoveSession(kMockSessionId2)) + .WillOnce(Return(true)); + + const uint32_t kSession1 = conn->AddNewSession(kDefaultConnectionHandle); + + const transport_manager::ConnectionUID kAnotherConnectionHandle = 2; + const uint32_t kSession2 = conn->AddNewSession(kAnotherConnectionHandle); TestAsyncWaiter waiter; uint32_t times = 0; @@ -199,7 +239,12 @@ TEST_F(HeartBeatMonitorTest, TwoSessionsElapsed) { } TEST_F(HeartBeatMonitorTest, IncreaseHeartBeatTimeout) { - const uint32_t kSession = conn->AddNewSession(); + EXPECT_CALL(connection_handler_mock, AddSession(_)) + .WillOnce(Return(kDefaultSessionId)); + EXPECT_CALL(connection_handler_mock, RemoveSession(kDefaultSessionId)) + .WillOnce(Return(true)); + + const uint32_t kSession = conn->AddNewSession(kDefaultConnectionHandle); EXPECT_CALL(connection_handler_mock, CloseSession(_, _)).Times(0); EXPECT_CALL(connection_handler_mock, CloseConnection(_)).Times(0); @@ -211,7 +256,12 @@ TEST_F(HeartBeatMonitorTest, IncreaseHeartBeatTimeout) { } TEST_F(HeartBeatMonitorTest, DecreaseHeartBeatTimeout) { - const uint32_t kSession = conn->AddNewSession(); + EXPECT_CALL(connection_handler_mock, AddSession(_)) + .WillOnce(Return(kDefaultSessionId)); + EXPECT_CALL(connection_handler_mock, RemoveSession(kDefaultSessionId)) + .WillOnce(Return(true)); + + const uint32_t kSession = conn->AddNewSession(kDefaultConnectionHandle); TestAsyncWaiter waiter; uint32_t times = 0; diff --git a/src/components/include/application_manager/application_manager.h b/src/components/include/application_manager/application_manager.h index 0888e1fc90..39fc3c7ae6 100644 --- a/src/components/include/application_manager/application_manager.h +++ b/src/components/include/application_manager/application_manager.h @@ -322,6 +322,19 @@ class ApplicationManager { virtual mobile_api::HMILevel::eType GetDefaultHmiLevel( ApplicationConstSharedPtr application) const = 0; + + /** + * @brief Checks if required transport for resumption is available + * + * The required transport can be configured through smartDeviceLink.ini file. + * + * @param application an instance of the app to check + * @return true if the app is connected through one of the required + * transports, false otherwise + */ + virtual bool CheckResumptionRequiredTransportAvailable( + ApplicationConstSharedPtr application) const = 0; + /** * @brief hmi_capabilities return capabilities of hmi * @return capabilities of hmi diff --git a/src/components/include/application_manager/application_manager_settings.h b/src/components/include/application_manager/application_manager_settings.h index e745a831c6..8f9d7496c8 100644 --- a/src/components/include/application_manager/application_manager_settings.h +++ b/src/components/include/application_manager/application_manager_settings.h @@ -38,6 +38,8 @@ #include <stdint.h> #include <string> +#include <map> +#include <vector> namespace application_manager { class ApplicationManagerSettings : public RequestControlerSettings, @@ -87,6 +89,13 @@ class ApplicationManagerSettings : public RequestControlerSettings, virtual const uint32_t& app_resuming_timeout() const = 0; virtual uint16_t attempts_to_open_resumption_db() const = 0; virtual uint16_t open_attempt_timeout_ms_resumption_db() const = 0; + virtual const std::map<std::string, std::vector<std::string> >& + transport_required_for_resumption_map() const = 0; + virtual const std::string& navigation_lowbandwidth_resumption_level() + const = 0; + virtual const std::string& projection_lowbandwidth_resumption_level() + const = 0; + virtual const std::string& media_lowbandwidth_resumption_level() const = 0; virtual void set_config_file_name(const std::string& fileName) = 0; virtual const std::pair<uint32_t, int32_t>& start_stream_retry_amount() const = 0; diff --git a/src/components/include/connection_handler/connection_handler.h b/src/components/include/connection_handler/connection_handler.h index 352f886aed..bfaacf5fc1 100644 --- a/src/components/include/connection_handler/connection_handler.h +++ b/src/components/include/connection_handler/connection_handler.h @@ -40,6 +40,7 @@ #include "connection_handler/connection.h" #include "connection_handler/devices_discovery_starter.h" #include "utils/macro.h" +#include "utils/data_accessor.h" /** * \namespace connection_handler @@ -51,6 +52,14 @@ enum CloseSessionReason { kCommon = 0, kFlood, kMalformed, kUnauthorizedApp }; class ConnectionHandlerObserver; +// The SessionConnectionMap keeps track of the primary and secondary transports +// associated with a session ID +typedef struct { + transport_manager::ConnectionUID primary_transport; + transport_manager::ConnectionUID secondary_transport; +} SessionTransports; +typedef std::map<uint8_t, SessionTransports> SessionConnectionMap; + /** * \class ConnectionHandler * \brief SmartDeviceLink ConnectionHandler interface class @@ -202,6 +211,43 @@ class ConnectionHandler { virtual DevicesDiscoveryStarter& get_device_discovery_starter() = 0; /** + * \brief Add a session. This is meant to be called from Connection class. + * \param primary_transport_id the primary connection ID to associate with the + * newly created session + * \return new session id, or 0 if failed + **/ + virtual uint32_t AddSession( + const transport_manager::ConnectionUID primary_transport_id) = 0; + + /** + * \brief Remove a session. This is meant to be called from Connection class. + * \param session_id ID of the session to remove + * \return true if successful, false otherwise + **/ + virtual bool RemoveSession(uint8_t session_id) = 0; + + virtual DataAccessor<SessionConnectionMap> session_connection_map() = 0; + + /** + * \brief Associate a secondary transport ID with a session + * \param session_id the session ID + * \param connection_id the new secondary connection ID to associate with the + * session + * \return the SessionTransports (newly) associated with the session + **/ + virtual SessionTransports SetSecondaryTransportID( + uint8_t session_id, + transport_manager::ConnectionUID secondary_transport_id) = 0; + + /** + * \brief Retrieve the session transports associated with a session + * \param session_id the session ID + * \return the SessionTransports associated with the session + **/ + virtual const SessionTransports GetSessionTransports( + uint8_t session_id) const = 0; + + /** * \brief Invoked when observer's OnServiceStartedCallback is completed * \param session_key the key of started session passed to * OnServiceStartedCallback(). @@ -217,6 +263,28 @@ class ConnectionHandler { bool result, std::vector<std::string>& rejected_params) = 0; + /** + * \brief Called when secondary transport with given session ID is established + * \param primary_connection_handle Set to identifier of primary connection + * \param secondary_connection_handle Identifier of secondary connection + * \param session_id session ID taken from Register Secondary Transport frame + * \return true if successful + **/ + virtual bool OnSecondaryTransportStarted( + transport_manager::ConnectionUID& primary_connection_handle, + const transport_manager::ConnectionUID secondary_connection_handle, + const uint8_t session_id) = 0; + + /** + * \brief Called when secondary transport shuts down + * \param primary_connection_handle Identifier of primary connection + * \param secondary_connection_handle Identifier of secondary connection + * transport + **/ + virtual void OnSecondaryTransportEnded( + const transport_manager::ConnectionUID primary_connection_handle, + const transport_manager::ConnectionUID secondary_connection_handle) = 0; + protected: /** * \brief Destructor diff --git a/src/components/include/connection_handler/connection_handler_observer.h b/src/components/include/connection_handler/connection_handler_observer.h index b4c04c17c9..2f4ee94449 100644 --- a/src/components/include/connection_handler/connection_handler_observer.h +++ b/src/components/include/connection_handler/connection_handler_observer.h @@ -157,6 +157,23 @@ class ConnectionHandlerObserver { virtual security_manager::SSLContext::HandshakeContext GetHandshakeContext( uint32_t key) const = 0; #endif // ENABLE_SECURITY + + /** + * \brief Called when secondary transport for a particular app is started. + * \param device_handle Device identifier on which the secondary transport is + * started. + * \param session_key session ID representing the app + */ + virtual void OnSecondaryTransportStartedCallback( + const connection_handler::DeviceHandle device_handle, + const int32_t session_key) = 0; + + /** + * \brief Called when secondary transport for a particular app is terminated. + * \param session_key session ID representing the app + */ + virtual void OnSecondaryTransportEndedCallback(const int32_t session_key) = 0; + protected: /** * \brief Destructor diff --git a/src/components/include/protocol/bson_object_keys.h b/src/components/include/protocol/bson_object_keys.h index 6f80aeec2b..56bd5cebd6 100644 --- a/src/components/include/protocol/bson_object_keys.h +++ b/src/components/include/protocol/bson_object_keys.h @@ -42,6 +42,12 @@ extern const char* height; extern const char* width; extern const char* video_protocol; extern const char* video_codec; +extern const char* secondary_transports; +extern const char* audio_service_transports; +extern const char* video_service_transports; +extern const char* tcp_ip_address; +extern const char* tcp_port; +extern const char* reason; } // namespace strings diff --git a/src/components/include/protocol/common.h b/src/components/include/protocol/common.h index 00d57a9bf5..1c95020a96 100644 --- a/src/components/include/protocol/common.h +++ b/src/components/include/protocol/common.h @@ -167,6 +167,22 @@ enum { */ FRAME_DATA_END_SERVICE_NACK = 0x06, /** + *\brief Register Secondary Transport frame + */ + FRAME_DATA_REGISTER_SECONDARY_TRANSPORT = 0x07, + /** + *\brief Register Secondary Transport acknowledgement frame + */ + FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_ACK = 0x08, + /** + *\brief Register Secondary Transport not acknowledgement frame + */ + FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_NACK = 0x09, + /** + *\brief Transport Event Update frame + */ + FRAME_DATA_TRANSPORT_EVENT_UPDATE = 0xFD, + /** *\brief Service data ACK frame */ FRAME_DATA_SERVICE_DATA_ACK = 0xFE, diff --git a/src/components/include/protocol_handler/protocol_handler.h b/src/components/include/protocol_handler/protocol_handler.h index 1da8d61e52..bb79964b12 100644 --- a/src/components/include/protocol_handler/protocol_handler.h +++ b/src/components/include/protocol_handler/protocol_handler.h @@ -100,7 +100,15 @@ class ProtocolHandler { */ virtual void SendEndSession(int32_t connection_id, uint8_t session_id) = 0; - virtual void SendEndService(int32_t connection_id, + /** + * \brief Sends ending session to mobile application + * \param primary_connection_id Identifier of connection within which + * service exists + * \param connection_id Identifier of the actual transport for the service + * \param session_id ID of session to be ended + */ + virtual void SendEndService(int32_t primary_connection_id, + int32_t connection_id, uint8_t session_id, uint8_t service_type) = 0; diff --git a/src/components/include/protocol_handler/protocol_handler_settings.h b/src/components/include/protocol_handler/protocol_handler_settings.h index e1107cb2a9..c4f9bb6420 100644 --- a/src/components/include/protocol_handler/protocol_handler_settings.h +++ b/src/components/include/protocol_handler/protocol_handler_settings.h @@ -51,6 +51,28 @@ class ProtocolHandlerSettings { */ virtual const std::vector<int>& force_unprotected_service() const = 0; #endif // ENABLE_SECURITY + + /** + * @brief Returns true multiple transports is enabled + */ + virtual const bool multiple_transports_enabled() const = 0; + + /** + * @brief Returns list of secondary transports available + * for the named primary transport + */ + virtual const std::vector<std::string>& secondary_transports_for_bluetooth() + const = 0; + virtual const std::vector<std::string>& secondary_transports_for_usb() + const = 0; + virtual const std::vector<std::string>& secondary_transports_for_wifi() + const = 0; + + /** + * @brief Returns list of allowed transports for the named service + */ + virtual const std::vector<std::string>& audio_service_transports() const = 0; + virtual const std::vector<std::string>& video_service_transports() const = 0; }; } // namespace protocol_handler #endif // SRC_COMPONENTS_INCLUDE_PROTOCOL_HANDLER_PROTOCOL_HANDLER_SETTINGS_H_ diff --git a/src/components/include/protocol_handler/session_observer.h b/src/components/include/protocol_handler/session_observer.h index 7a5dcf287c..d50b1e694e 100644 --- a/src/components/include/protocol_handler/session_observer.h +++ b/src/components/include/protocol_handler/session_observer.h @@ -59,6 +59,7 @@ enum { HASH_ID_NOT_SUPPORTED = 0, HASH_ID_WRONG = 0xFFFF0000 }; * @brief Struct with data containing attributes of starting session **/ struct SessionContext { + transport_manager::ConnectionUID primary_connection_id_; transport_manager::ConnectionUID connection_id_; uint8_t initial_session_id_; uint8_t new_session_id_; @@ -71,7 +72,8 @@ struct SessionContext { * @brief Constructor */ SessionContext() - : connection_id_(0) + : primary_connection_id_(0) + , connection_id_(0) , initial_session_id_(0) , new_session_id_(0) , service_type_(protocol_handler::kInvalidServiceType) @@ -81,6 +83,8 @@ struct SessionContext { /** * @brief Constructor + * @param primary_connection_id Connection identifier of the primary + * connection in which the session is started * @param connection_id_ Connection identifier within which session is * started. * @param session_id Session ID specified to OnSessionStartedCallback() @@ -91,13 +95,15 @@ struct SessionContext { * @param is_protected Whether service will be protected * @param is_new_service Whether service was already established **/ - SessionContext(transport_manager::ConnectionUID connection_id, + SessionContext(transport_manager::ConnectionUID primary_connection_id, + transport_manager::ConnectionUID connection_id, uint8_t session_id, uint8_t new_session_id, protocol_handler::ServiceType service_type, uint32_t hash_id, const bool is_protected) - : connection_id_(connection_id) + : primary_connection_id_(primary_connection_id) + , connection_id_(connection_id) , initial_session_id_(session_id) , new_session_id_(new_session_id) , service_type_(service_type) @@ -205,6 +211,24 @@ class SessionObserver { virtual void OnMalformedMessageCallback(const uint32_t& connection_key) = 0; /** + * @brief Converts connection handle to transport type string used in + * smartDeviceLink.ini file, e.g. "TCP_WIFI" + * @param connection_handle A connection identifier + * @return string representation of the transport of the device + */ + virtual const std::string TransportTypeProfileStringFromConnHandle( + transport_manager::ConnectionUID connection_handle) const = 0; + + /** + * @brief Converts device handle to transport type string used in + * smartDeviceLink.ini file, e.g. "TCP_WIFI" + * @param device_handle A device handle + * @return string representation of the transport of the device + */ + virtual const std::string TransportTypeProfileStringFromDeviceHandle( + transport_manager::DeviceHandle device_handle) const = 0; + + /** * \brief Creates unique identifier of session (can be used as hash) * from given connection identifier * within which session exists and session number. diff --git a/src/components/include/test/application_manager/mock_application_manager.h b/src/components/include/test/application_manager/mock_application_manager.h index 09957dbde0..0bb759fc1f 100644 --- a/src/components/include/test/application_manager/mock_application_manager.h +++ b/src/components/include/test/application_manager/mock_application_manager.h @@ -131,6 +131,9 @@ class MockApplicationManager : public application_manager::ApplicationManager { MOCK_METHOD0(hmi_capabilities, application_manager::HMICapabilities&()); MOCK_CONST_METHOD0(hmi_capabilities, const application_manager::HMICapabilities&()); + MOCK_CONST_METHOD1( + CheckResumptionRequiredTransportAvailable, + bool(application_manager::ApplicationConstSharedPtr application)); MOCK_METHOD2(ProcessQueryApp, void(const smart_objects::SmartObject& sm_object, const uint32_t connection_key)); diff --git a/src/components/include/test/application_manager/mock_application_manager_settings.h b/src/components/include/test/application_manager/mock_application_manager_settings.h index 25cf994566..735539156a 100644 --- a/src/components/include/test/application_manager/mock_application_manager_settings.h +++ b/src/components/include/test/application_manager/mock_application_manager_settings.h @@ -95,6 +95,13 @@ class MockApplicationManagerSettings MOCK_CONST_METHOD0(app_resuming_timeout, const uint32_t&()); MOCK_CONST_METHOD0(attempts_to_open_resumption_db, uint16_t()); MOCK_CONST_METHOD0(open_attempt_timeout_ms_resumption_db, uint16_t()); + MOCK_CONST_METHOD0(transport_required_for_resumption_map, + std::map<std::string, std::vector<std::string> >&()); + MOCK_CONST_METHOD0(navigation_lowbandwidth_resumption_level, + const std::string&()); + MOCK_CONST_METHOD0(projection_lowbandwidth_resumption_level, + const std::string&()); + MOCK_CONST_METHOD0(media_lowbandwidth_resumption_level, const std::string&()); MOCK_METHOD1(set_config_file_name, void(const std::string& fileName)); // The following line won't really compile, as the return // type has multiple template arguments. To fix it, use a diff --git a/src/components/include/test/connection_handler/mock_connection_handler.h b/src/components/include/test/connection_handler/mock_connection_handler.h index f1416c999e..2de3a0f9a7 100644 --- a/src/components/include/test/connection_handler/mock_connection_handler.h +++ b/src/components/include/test/connection_handler/mock_connection_handler.h @@ -51,6 +51,7 @@ using connection_handler::ConnectionHandle; using connection_handler::DeviceHandle; using connection_handler::CloseSessionReason; using connection_handler::DevicesDiscoveryStarter; +using connection_handler::SessionTransports; class MockConnectionHandler : public connection_handler::ConnectionHandler { public: @@ -84,13 +85,6 @@ class MockConnectionHandler : public connection_handler::ConnectionHandler { void(uint32_t connection_key, uint8_t session_id)); MOCK_METHOD2(BindProtocolVersionWithSession, void(uint32_t connection_key, uint8_t protocol_version)); - - // DEPRECATED - MOCK_CONST_METHOD4(GetDataOnSessionKey, - int32_t(uint32_t key, - uint32_t* app_id, - std::list<int32_t>* sessions_list, - uint32_t* device_id)); MOCK_CONST_METHOD4(GetDataOnSessionKey, int32_t(uint32_t key, uint32_t* app_id, @@ -103,10 +97,31 @@ class MockConnectionHandler : public connection_handler::ConnectionHandler { MOCK_METHOD0(get_device_discovery_starter, DevicesDiscoveryStarter&()); MOCK_CONST_METHOD1(GetConnectedDevicesMAC, void(std::vector<std::string>& macs)); + MOCK_METHOD1( + AddSession, + uint32_t(const transport_manager::ConnectionUID primary_transport_id)); + MOCK_METHOD1(RemoveSession, bool(uint8_t session_id)); + MOCK_METHOD0(session_connection_map, + DataAccessor<connection_handler::SessionConnectionMap>()); + MOCK_METHOD2(SetSecondaryTransportID, + SessionTransports( + uint8_t session_id, + transport_manager::ConnectionUID secondary_transport_id)); + MOCK_CONST_METHOD1(GetSessionTransports, + const SessionTransports(uint8_t session_id)); MOCK_METHOD3(NotifyServiceStartedResult, void(uint32_t session_key, bool result, std::vector<std::string>& rejected_params)); + MOCK_METHOD3( + OnSecondaryTransportStarted, + bool(transport_manager::ConnectionUID& primary_connection_handle, + const transport_manager::ConnectionUID secondary_connection_handle, + const uint8_t session_id)); + MOCK_METHOD2( + OnSecondaryTransportEnded, + void(const transport_manager::ConnectionUID primary_connection_handle, + const transport_manager::ConnectionUID secondary_connection_handle)); }; } // namespace connection_handler_test diff --git a/src/components/include/test/connection_handler/mock_connection_handler_observer.h b/src/components/include/test/connection_handler/mock_connection_handler_observer.h index a96498028d..ede08a9a4a 100644 --- a/src/components/include/test/connection_handler/mock_connection_handler_observer.h +++ b/src/components/include/test/connection_handler/mock_connection_handler_observer.h @@ -74,6 +74,11 @@ class MockConnectionHandlerObserver const connection_handler::Device& device_to)); MOCK_METHOD1(OnDeviceSwitchingFinish, void(const std::string& device_uid)); MOCK_CONST_METHOD1(CheckAppIsNavi, bool(const uint32_t app_id)); + MOCK_METHOD2(OnSecondaryTransportStartedCallback, + void(const connection_handler::DeviceHandle device_handle, + const int32_t session_key)); + MOCK_METHOD1(OnSecondaryTransportEndedCallback, + void(const int32_t session_key)); }; } // namespace connection_handler_test diff --git a/src/components/include/test/protocol_handler/mock_protocol_handler.h b/src/components/include/test/protocol_handler/mock_protocol_handler.h index e667911944..13c0264cb8 100644 --- a/src/components/include/test/protocol_handler/mock_protocol_handler.h +++ b/src/components/include/test/protocol_handler/mock_protocol_handler.h @@ -55,8 +55,9 @@ class MockProtocolHandler : public ::protocol_handler::ProtocolHandler { void(uint32_t connection_key, int32_t number_of_frames)); MOCK_METHOD2(SendHeartBeat, void(int32_t connection_id, uint8_t session_id)); MOCK_METHOD2(SendEndSession, void(int32_t connection_id, uint8_t session_id)); - MOCK_METHOD3(SendEndService, - void(int32_t connection_id, + MOCK_METHOD4(SendEndService, + void(int32_t primary_connection_id, + int32_t connection_id, uint8_t session_id, uint8_t service_type)); MOCK_CONST_METHOD0(get_settings, diff --git a/src/components/include/test/protocol_handler/mock_protocol_handler_settings.h b/src/components/include/test/protocol_handler/mock_protocol_handler_settings.h index 8ddeded889..2ed9c47e9d 100644 --- a/src/components/include/test/protocol_handler/mock_protocol_handler_settings.h +++ b/src/components/include/test/protocol_handler/mock_protocol_handler_settings.h @@ -60,6 +60,17 @@ class MockProtocolHandlerSettings MOCK_CONST_METHOD0(force_protected_service, const std::vector<int>&()); MOCK_CONST_METHOD0(force_unprotected_service, const std::vector<int>&()); #endif + MOCK_CONST_METHOD0(multiple_transports_enabled, const bool()); + MOCK_CONST_METHOD0(secondary_transports_for_bluetooth, + const std::vector<std::string>&()); + MOCK_CONST_METHOD0(secondary_transports_for_usb, + const std::vector<std::string>&()); + MOCK_CONST_METHOD0(secondary_transports_for_wifi, + const std::vector<std::string>&()); + MOCK_CONST_METHOD0(audio_service_transports, + const std::vector<std::string>&()); + MOCK_CONST_METHOD0(video_service_transports, + const std::vector<std::string>&()); }; } // namespace protocol_handler_test diff --git a/src/components/include/test/protocol_handler/mock_session_observer.h b/src/components/include/test/protocol_handler/mock_session_observer.h index ae32f35948..c0612ce137 100644 --- a/src/components/include/test/protocol_handler/mock_session_observer.h +++ b/src/components/include/test/protocol_handler/mock_session_observer.h @@ -76,6 +76,12 @@ class MockSessionObserver : public ::protocol_handler::SessionObserver { void(const uint32_t& connection_key)); MOCK_METHOD1(OnMalformedMessageCallback, void(const uint32_t& connection_key)); + MOCK_CONST_METHOD1( + TransportTypeProfileStringFromConnHandle, + const std::string(transport_manager::ConnectionUID connection_handle)); + MOCK_CONST_METHOD1( + TransportTypeProfileStringFromDeviceHandle, + const std::string(transport_manager::DeviceHandle device_handle)); MOCK_CONST_METHOD2( KeyFromPair, uint32_t(transport_manager::ConnectionUID connection_handle, @@ -89,11 +95,6 @@ class MockSessionObserver : public ::protocol_handler::SessionObserver { uint32_t* app_id, std::list<int32_t>* sessions_list, transport_manager::DeviceHandle* device_id)); - DEPRECATED MOCK_CONST_METHOD4(GetDataOnSessionKey, - int32_t(uint32_t key, - uint32_t* app_id, - std::list<int32_t>* sessions_list, - uint32_t* device_id)); MOCK_CONST_METHOD5(GetDataOnDeviceID, int32_t(transport_manager::DeviceHandle device_handle, diff --git a/src/components/include/test/transport_manager/mock_transport_manager_listener.h b/src/components/include/test/transport_manager/mock_transport_manager_listener.h index f4c4fdcf68..133dabe732 100644 --- a/src/components/include/test/transport_manager/mock_transport_manager_listener.h +++ b/src/components/include/test/transport_manager/mock_transport_manager_listener.h @@ -85,6 +85,8 @@ class MockTransportManagerListener : public TransportManagerListener { void(const DeviceUID& device_uid_from, const DeviceUID& device_uid_to)); MOCK_METHOD1(OnDeviceSwitchingFinish, void(const DeviceUID& device_uid)); + MOCK_METHOD1(OnTransportConfigUpdated, + void(const std::map<std::string, std::string>& configs)); }; } // namespace transport_manager_test diff --git a/src/components/include/test/transport_manager/mock_transport_manager_settings.h b/src/components/include/test/transport_manager/mock_transport_manager_settings.h index 88112df003..3e7c8f36f7 100644 --- a/src/components/include/test/transport_manager/mock_transport_manager_settings.h +++ b/src/components/include/test/transport_manager/mock_transport_manager_settings.h @@ -61,6 +61,8 @@ class MockTransportManagerSettings MOCK_CONST_METHOD0(iap_hub_connection_wait_timeout, uint32_t()); MOCK_CONST_METHOD0(app_transport_change_timer, uint32_t()); MOCK_CONST_METHOD0(app_transport_change_timer_addition, uint32_t()); + MOCK_CONST_METHOD0(transport_manager_tcp_adapter_network_interface, + std::string&()); }; } // namespace transport_manager_test diff --git a/src/components/include/test/transport_manager/transport_adapter/mock_transport_adapter.h b/src/components/include/test/transport_manager/transport_adapter/mock_transport_adapter.h index c37c41d4f8..eff0abdcd3 100644 --- a/src/components/include/test/transport_manager/transport_adapter/mock_transport_adapter.h +++ b/src/components/include/test/transport_manager/transport_adapter/mock_transport_adapter.h @@ -103,6 +103,8 @@ class MockTransportAdapter void(const ::transport_manager::DeviceUID& device_handle)); MOCK_CONST_METHOD0(GetSwitchableDevices, transport_manager::SwitchableDevices()); + MOCK_CONST_METHOD0(GetTransportConfiguration, + transport_manager::transport_adapter::TransportConfig()); #ifdef TELEMETRY_MONITOR MOCK_METHOD0(GetTelemetryObserver, ::transport_manager::TMTelemetryObserver*()); diff --git a/src/components/include/transport_manager/transport_adapter/transport_adapter.h b/src/components/include/transport_manager/transport_adapter/transport_adapter.h index 7f6d347535..c00adb33b8 100644 --- a/src/components/include/transport_manager/transport_adapter/transport_adapter.h +++ b/src/components/include/transport_manager/transport_adapter/transport_adapter.h @@ -5,6 +5,9 @@ * Copyright (c) 2016, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -16,7 +19,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -70,6 +73,9 @@ enum DeviceType { IOS_BT, IOS_USB, TCP, + IOS_USB_HOST_MODE, + IOS_USB_DEVICE_MODE, + IOS_CARPLAY_WIRELESS, // running on iAP over Carplay wireless transport UNKNOWN }; @@ -86,6 +92,18 @@ typedef std::map<DeviceUID, DeviceSptr> DeviceMap; */ typedef std::list<TransportAdapterListener*> TransportAdapterListenerList; +/** + * @brief Type definition for transport's configuration information + */ +typedef std::map<std::string, std::string> TransportConfig; + +/** + * @brief TransportConfig keys + */ +extern const char* tc_enabled; +extern const char* tc_tcp_port; +extern const char* tc_tcp_ip_address; + class TransportAdapter { public: /** @@ -312,6 +330,12 @@ class TransportAdapter { virtual void DeviceSwitched(const DeviceUID& device_handle) = 0; virtual SwitchableDevices GetSwitchableDevices() const = 0; + + /** + * @brief Returns the transport's configuration information + */ + virtual TransportConfig GetTransportConfiguration() const = 0; + #ifdef TELEMETRY_MONITOR /** * @brief Return Time metric observer diff --git a/src/components/include/transport_manager/transport_adapter/transport_adapter_event.h b/src/components/include/transport_manager/transport_adapter/transport_adapter_event.h index 18f4ccb2d1..5d55960943 100644 --- a/src/components/include/transport_manager/transport_adapter/transport_adapter_event.h +++ b/src/components/include/transport_manager/transport_adapter/transport_adapter_event.h @@ -2,6 +2,9 @@ * Copyright (c) 2013, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -57,7 +60,8 @@ enum class EventTypeEnum { ON_RECEIVED_FAIL, ON_COMMUNICATION_ERROR, ON_UNEXPECTED_DISCONNECT, - ON_TRANSPORT_SWITCH_REQUESTED + ON_TRANSPORT_SWITCH_REQUESTED, + ON_TRANSPORT_CONFIG_UPDATED }; class TransportAdapterEvent { diff --git a/src/components/include/transport_manager/transport_manager_listener.h b/src/components/include/transport_manager/transport_manager_listener.h index d336eade45..0d02bfd8c7 100644 --- a/src/components/include/transport_manager/transport_manager_listener.h +++ b/src/components/include/transport_manager/transport_manager_listener.h @@ -2,6 +2,9 @@ * Copyright (c) 2014, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -33,6 +36,7 @@ #ifndef SRC_COMPONENTS_INCLUDE_TRANSPORT_MANAGER_TRANSPORT_MANAGER_LISTENER_H_ #define SRC_COMPONENTS_INCLUDE_TRANSPORT_MANAGER_TRANSPORT_MANAGER_LISTENER_H_ +#include <map> #include <vector> #include "transport_manager/common.h" #include "transport_manager/info.h" @@ -193,6 +197,14 @@ class TransportManagerListener { virtual void OnTMMessageSendFailed( const DataSendError& error, const ::protocol_handler::RawMessagePtr message) = 0; + + /** + * @brief Notifies that configuration of a transport has been updated. + * + * @param configs pairs of key and value that represent configuration. + */ + virtual void OnTransportConfigUpdated( + const std::map<std::string, std::string>& configs) = 0; }; } // namespace transport_manager #endif // SRC_COMPONENTS_INCLUDE_TRANSPORT_MANAGER_TRANSPORT_MANAGER_LISTENER_H_ diff --git a/src/components/include/transport_manager/transport_manager_listener_empty.h b/src/components/include/transport_manager/transport_manager_listener_empty.h index ca6c573a06..a255256290 100644 --- a/src/components/include/transport_manager/transport_manager_listener_empty.h +++ b/src/components/include/transport_manager/transport_manager_listener_empty.h @@ -2,6 +2,9 @@ * Copyright (c) 2014, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -187,6 +190,14 @@ class TransportManagerListenerEmpty : public TransportManagerListener { void OnTMMessageSendFailed( const DataSendError& error, const ::protocol_handler::RawMessagePtr message) OVERRIDE {} + + /** + * @brief Notifies that configuration of a transport has been updated. + * + * @param configs pairs of key and value that represent configuration. + */ + void OnTransportConfigUpdated( + const std::map<std::string, std::string>& configs) OVERRIDE {} }; } // namespace transport_manager #endif // SRC_COMPONENTS_INCLUDE_TRANSPORT_MANAGER_TRANSPORT_MANAGER_LISTENER_EMPTY_H_ diff --git a/src/components/include/transport_manager/transport_manager_settings.h b/src/components/include/transport_manager/transport_manager_settings.h index feb3fa2c02..3912bbe747 100644 --- a/src/components/include/transport_manager/transport_manager_settings.h +++ b/src/components/include/transport_manager/transport_manager_settings.h @@ -63,6 +63,12 @@ class TransportManagerSettings : public TransportManagerMMESettings { * the transport change timeout value. */ virtual uint32_t app_transport_change_timer_addition() const = 0; + + /** + * @brief Returns the network interface name for TCP transport adapter + */ + virtual const std::string& transport_manager_tcp_adapter_network_interface() + const = 0; }; } // namespace transport_manager #endif // SRC_COMPONENTS_INCLUDE_TRANSPORT_MANAGER_TRANSPORT_MANAGER_SETTINGS_H_ diff --git a/src/components/include/utils/data_accessor.h b/src/components/include/utils/data_accessor.h index 645886592c..6d0fb0ed68 100644 --- a/src/components/include/utils/data_accessor.h +++ b/src/components/include/utils/data_accessor.h @@ -36,7 +36,7 @@ #include "utils/lock.h" #include "utils/shared_ptr.h" -// This class is for thread-safe access to data +// This class is for thread-safe const access to data template <class T> class DataAccessor { public: diff --git a/src/components/interfaces/HMI_API.xml b/src/components/interfaces/HMI_API.xml index f08709bb2b..c4e53f1db3 100644 --- a/src/components/interfaces/HMI_API.xml +++ b/src/components/interfaces/HMI_API.xml @@ -1974,6 +1974,9 @@ <param name="deviceInfo" type="Common.DeviceInfo" mandatory="true"> <description>The ID, serial number, transport type the named-app's-device is connected over to HU.</description> </param> + <param name="secondaryDeviceInfo" type="Common.DeviceInfo" mandatory="false"> + <description>The ID, serial number, transport type that are acquired through Secondary Transport.</description> + </param> <param name="policyAppID" type="String" maxlength="50" minlength="1" mandatory="true"> <description>Policy ID(=the appID the application registers with) of registered application.</description> </param> diff --git a/src/components/protocol/src/bson_object_keys.cc b/src/components/protocol/src/bson_object_keys.cc index fb225b1461..11160d7082 100644 --- a/src/components/protocol/src/bson_object_keys.cc +++ b/src/components/protocol/src/bson_object_keys.cc @@ -12,6 +12,12 @@ const char* height = "height"; const char* width = "width"; const char* video_protocol = "videoProtocol"; const char* video_codec = "videoCodec"; +const char* secondary_transports = "secondaryTransports"; +const char* audio_service_transports = "audioServiceTransports"; +const char* video_service_transports = "videoServiceTransports"; +const char* tcp_ip_address = "tcpIpAddress"; +const char* tcp_port = "tcpPort"; +const char* reason = "reason"; } // namespace strings diff --git a/src/components/protocol_handler/include/protocol_handler/protocol_handler_impl.h b/src/components/protocol_handler/include/protocol_handler/protocol_handler_impl.h index 99f03b1c04..fb685f33d3 100644 --- a/src/components/protocol_handler/include/protocol_handler/protocol_handler_impl.h +++ b/src/components/protocol_handler/include/protocol_handler/protocol_handler_impl.h @@ -44,6 +44,7 @@ #include "utils/threads/message_loop_thread.h" #include "utils/shared_ptr.h" #include "utils/messagemeter.h" +#include "utils/custom_string.h" #include "protocol_handler/protocol_handler.h" #include "protocol_handler/protocol_packet.h" @@ -55,6 +56,7 @@ #include "transport_manager/common.h" #include "transport_manager/transport_manager.h" #include "transport_manager/transport_manager_listener_empty.h" +#include "transport_manager/transport_adapter/transport_adapter.h" #include "connection_handler/connection_handler.h" #include "application_manager/policies/policy_handler_observer.h" @@ -131,6 +133,30 @@ typedef threads::MessageLoopThread< utils::PrioritizedQueue<RawFordMessageFromMobile> > FromMobileQueue; typedef threads::MessageLoopThread< utils::PrioritizedQueue<RawFordMessageToMobile> > ToMobileQueue; + +// Type to allow easy mapping between a device type and transport +// characteristics +typedef enum { + TT_NONE = -1, + TT_USB = 0, + TT_BLUETOOTH = 1, + TT_WIFI = 2 +} TransportType; + +struct TransportDescription { + TransportDescription(const TransportType transport_type, + const bool ios_transport, + const bool android_transport) + : transport_type_(transport_type) + , ios_transport_(ios_transport) + , android_transport_(android_transport) {} + + TransportType transport_type_; + bool ios_transport_; + bool android_transport_; +}; + +typedef std::map<std::string, TransportDescription> TransportTypes; } // namespace impl /** @@ -231,7 +257,15 @@ class ProtocolHandlerImpl */ void SendEndSession(int32_t connection_id, uint8_t session_id); - void SendEndService(int32_t connection_id, + /** + * \brief Sends ending session to mobile application + * \param primary_connection_id Identifier of connection within which + * service exists + * \param connection_id Identifier of the actual transport for the service + * \param session_id ID of session to be ended + */ + void SendEndService(int32_t primary_connection_id, + int32_t connection_id, uint8_t session_id, uint8_t service_type); @@ -422,10 +456,19 @@ class ProtocolHandlerImpl const impl::ToMobileQueue& get_to_mobile_queue() const { return raw_ford_messages_to_mobile_; } + + void set_tcp_config(bool tcp_enabled, + std::string tcp_address, + std::string tcp_port) { + tcp_enabled_ = tcp_enabled; + tcp_ip_address_ = tcp_address; + tcp_port_ = tcp_port; + } #endif private: - void SendEndServicePrivate(int32_t connection_id, + void SendEndServicePrivate(int32_t primary_connection_id, + int32_t connection_id, uint8_t session_id, uint8_t service_type); @@ -436,6 +479,28 @@ class ProtocolHandlerImpl uint8_t session_id, uint32_t message_id); + /* + * Prepare and send TransportUpdateEvent message + */ + void SendTransportUpdateEvent(ConnectionID connection_id, uint8_t session_id); + + /* + * Prepare and send RegisterSecondaryTransportAck message + */ + RESULT_CODE SendRegisterSecondaryTransportAck( + ConnectionID connection_id, + ConnectionID primary_transport_connection_id, + uint8_t session_id); + + /* + * Prepare and send RegisterSecondaryTransportNAck message + */ + RESULT_CODE SendRegisterSecondaryTransportNAck( + ConnectionID connection_id, + ConnectionID primary_transport_connection_id, + uint8_t session_id, + BsonObject* reason = NULL); + /** * @brief Notifies about receiving message from TM. * @@ -474,6 +539,19 @@ class ProtocolHandlerImpl void OnConnectionClosed( const transport_manager::ConnectionUID connection_id) OVERRIDE; + void OnUnexpectedDisconnect( + const transport_manager::ConnectionUID connection_id, + const transport_manager::CommunicationError& error) OVERRIDE; + + /** + * @brief Notifies that configuration of a transport has been updated. + * + * @param configs pairs of key and value that represent configuration. + */ + void OnTransportConfigUpdated( + const transport_manager::transport_adapter::TransportConfig& configs) + OVERRIDE; + /** * @brief Notifies subscribers about message * received from mobile device. @@ -575,6 +653,9 @@ class ProtocolHandlerImpl RESULT_CODE HandleControlMessageStartSession(const ProtocolFramePtr packet); + RESULT_CODE HandleControlMessageRegisterSecondaryTransport( + const ProtocolFramePtr packet); + RESULT_CODE HandleControlMessageHeartBeat(const ProtocolPacket& packet); void PopValideAndExpirateMultiframes(); @@ -603,6 +684,32 @@ class ProtocolHandlerImpl */ uint8_t SupportedSDLProtocolVersion() const; + const impl::TransportDescription GetTransportTypeFromConnectionType( + const std::string& device_type) const; + + const bool ParseSecondaryTransportConfiguration( + const ConnectionID connection_id, + std::vector<std::string>& secondaryTransports, + std::vector<int32_t>& audioServiceTransports, + std::vector<int32_t>& videoServiceTransports) const; + + void GenerateSecondaryTransportsForStartSessionAck( + const std::vector<std::string>& secondary_transport_types, + bool device_is_ios, + bool device_is_android, + std::vector<std::string>& secondaryTransports) const; + + void GenerateServiceTransportsForStartSessionAck( + bool secondary_enabled, + const std::vector<std::string>& service_transports, + const std::string& primary_connection_type, + const impl::TransportType primary_transport_type, + const std::vector<std::string>& secondary_transport_types, + std::vector<int32_t>& serviceTransports) const; + + const std::string TransportTypeFromTransport( + const utils::custom_string::CustomString& transport) const; + const ProtocolHandlerSettings& settings_; /** @@ -689,6 +796,10 @@ class ProtocolHandlerImpl sync_primitives::Lock start_session_frame_map_lock_; StartSessionFrameMap start_session_frame_map_; + bool tcp_enabled_; + std::string tcp_port_; + std::string tcp_ip_address_; + #ifdef TELEMETRY_MONITOR PHTelemetryObserver* metric_observer_; #endif // TELEMETRY_MONITOR diff --git a/src/components/protocol_handler/include/protocol_handler/protocol_packet.h b/src/components/protocol_handler/include/protocol_handler/protocol_packet.h index b6c05d4c46..d3e3ec5809 100644 --- a/src/components/protocol_handler/include/protocol_handler/protocol_packet.h +++ b/src/components/protocol_handler/include/protocol_handler/protocol_packet.h @@ -332,6 +332,11 @@ class ProtocolPacket { ConnectionID connection_id() const; /** + * \brief Setter of Connection Identifier + */ + void set_connection_id(ConnectionID connection_id); + + /** * \brief Getter for data payload size */ uint32_t payload_size() const; diff --git a/src/components/protocol_handler/src/protocol_handler_impl.cc b/src/components/protocol_handler/src/protocol_handler_impl.cc index 636932f449..6548f86c6a 100644 --- a/src/components/protocol_handler/src/protocol_handler_impl.cc +++ b/src/components/protocol_handler/src/protocol_handler_impl.cc @@ -31,6 +31,7 @@ */ #include "protocol_handler/protocol_handler_impl.h" +#include <arpa/inet.h> // for INET6_ADDRSTRLEN #include <memory.h> #include <algorithm> // std::find #include <bson_object.h> @@ -60,7 +61,8 @@ std::string ConvertPacketDataToString(const uint8_t* data, const size_t kStackSize = 65536; -ProtocolPacket::ProtocolVersion defaultProtocolVersion(5, 0, 0); +ProtocolPacket::ProtocolVersion defaultProtocolVersion(5, 1, 0); +ProtocolPacket::ProtocolVersion minMultipleTransportsVersion(5, 1, 0); ProtocolHandlerImpl::ProtocolHandlerImpl( const ProtocolHandlerSettings& settings, @@ -84,6 +86,7 @@ ProtocolHandlerImpl::ProtocolHandlerImpl( "PH ToMobile", this, threads::ThreadOptions(kStackSize)) , start_session_frame_map_lock_() , start_session_frame_map_() + , tcp_enabled_(false) #ifdef TELEMETRY_MONITOR , metric_observer_(NULL) #endif // TELEMETRY_MONITOR @@ -242,6 +245,8 @@ void ProtocolHandlerImpl::SendStartSessionAck( BsonObject& params) { LOG4CXX_AUTO_TRACE(logger_); + bool send_transport_update_event = false; + uint8_t ack_protocol_version = SupportedSDLProtocolVersion(); const bool proxy_supports_v5_protocol = @@ -317,6 +322,91 @@ void ProtocolHandlerImpl::SendStartSessionAck( "Protocol version parameter was written to bson params: " << protocol_ver_written << "; Value: " << bson_object_get_string(¶ms, strings::protocol_version)); + + LOG4CXX_INFO(logger_, + "Protocol Version String " << protocolVersionString); + + std::vector<std::string> secondaryTransports; + std::vector<int32_t> audioServiceTransports; + std::vector<int32_t> videoServiceTransports; + if (*minVersion >= minMultipleTransportsVersion) { + if (ParseSecondaryTransportConfiguration(connection_id, + secondaryTransports, + audioServiceTransports, + videoServiceTransports)) { + LOG4CXX_DEBUG(logger_, "Multiple transports are enabled."); + BsonArray secondaryTransportsArr; + bson_array_initialize(&secondaryTransportsArr, + secondaryTransports.size()); + for (unsigned int i = 0; i < secondaryTransports.size(); i++) { + char secondaryTransport[255]; + strncpy(secondaryTransport, + secondaryTransports[i].c_str(), + sizeof(secondaryTransport)); + secondaryTransport[sizeof(secondaryTransport) - 1] = '\0'; + LOG4CXX_DEBUG( + logger_, + "Adding " + << secondaryTransport + << " to secondaryTransports parameter of StartSessionAck"); + bson_array_add_string(&secondaryTransportsArr, secondaryTransport); + } + bson_object_put_array( + ¶ms, strings::secondary_transports, &secondaryTransportsArr); + + BsonArray audioServiceTransportsArr; + bson_array_initialize(&audioServiceTransportsArr, + audioServiceTransports.size()); + for (unsigned int i = 0; i < audioServiceTransports.size(); i++) { + LOG4CXX_DEBUG(logger_, + "Adding " << audioServiceTransports[i] + << " to audioServiceTransports parameter " + "of StartSessionAck"); + bson_array_add_int32(&audioServiceTransportsArr, + audioServiceTransports[i]); + } + bson_object_put_array(¶ms, + strings::audio_service_transports, + &audioServiceTransportsArr); + + BsonArray videoServiceTransportsArr; + bson_array_initialize(&videoServiceTransportsArr, + videoServiceTransports.size()); + for (unsigned int i = 0; i < videoServiceTransports.size(); i++) { + LOG4CXX_DEBUG(logger_, + "Adding " << videoServiceTransports[i] + << " to videoServiceTransports parameter " + "of StartSessionAck"); + bson_array_add_int32(&videoServiceTransportsArr, + videoServiceTransports[i]); + } + bson_object_put_array(¶ms, + strings::video_service_transports, + &videoServiceTransportsArr); + + if (settings_.multiple_transports_enabled()) { + send_transport_update_event = true; + } else { + LOG4CXX_DEBUG( + logger_, + "Multiple transports feature is disabled by configuration"); + // In this case, we must remember that this session will never have + // a secondary transport. + connection_handler_.SetSecondaryTransportID(session_id, + kDisabledSecondary); + } + } else { + LOG4CXX_WARN( + logger_, + "Failed to set up secondary transport and service type params"); + connection_handler_.SetSecondaryTransportID(session_id, + kDisabledSecondary); + } + } else { + LOG4CXX_INFO(logger_, "Older protocol version. No multiple transports"); + connection_handler_.SetSecondaryTransportID(session_id, + kDisabledSecondary); + } } uint8_t* payloadBytes = bson_object_to_bytes(¶ms); ptr->set_data(payloadBytes, bson_object_size(¶ms)); @@ -334,6 +424,16 @@ void ProtocolHandlerImpl::SendStartSessionAck( << static_cast<int32_t>(service_type) << " session_id " << static_cast<int32_t>(session_id) << " protection " << (protection ? "ON" : "OFF")); + + if (send_transport_update_event) { + // Wait until the StartService ACK has been processed for sending. + // The TransportUpdateEvent has a higher priority, being that it's + // a SERVICE_TYPE_CONTROL message. (The ACK is SERVICE_TYPE_RPC.) + LOG4CXX_DEBUG(logger_, "Waiting for the MessageToMobile queue to be empty"); + raw_ford_messages_to_mobile_.WaitDumpQueue(); + LOG4CXX_DEBUG(logger_, "Sending the TransportUpdate event"); + SendTransportUpdateEvent(connection_id, session_id); + } } void ProtocolHandlerImpl::SendStartSessionNAck(ConnectionID connection_id, @@ -491,14 +591,18 @@ void ProtocolHandlerImpl::SendEndSessionAck(ConnectionID connection_id, << static_cast<int32_t>(session_id)); } -void ProtocolHandlerImpl::SendEndServicePrivate(int32_t connection_id, +void ProtocolHandlerImpl::SendEndServicePrivate(int32_t primary_connection_id, + int32_t connection_id, uint8_t session_id, uint8_t service_type) { LOG4CXX_AUTO_TRACE(logger_); uint8_t protocol_version; if (session_observer_.ProtocolVersionUsed( - connection_id, session_id, protocol_version)) { + primary_connection_id, session_id, protocol_version)) { + LOG4CXX_TRACE(logger_, + "SendEndServicePrivate using protocol version " + << static_cast<int32_t>(protocol_version)); ProtocolFramePtr ptr( new protocol_handler::ProtocolPacket(connection_id, protocol_version, @@ -513,25 +617,31 @@ void ProtocolHandlerImpl::SendEndServicePrivate(int32_t connection_id, raw_ford_messages_to_mobile_.PostMessage( impl::RawFordMessageToMobile(ptr, false)); LOG4CXX_DEBUG(logger_, - "SendEndSession() for connection " - << connection_id << " for service_type " << service_type + "SendEndServicePrivate() for connection " + << primary_connection_id << " for service_type " + << static_cast<int>(service_type) + << " service connection " << connection_id << " session_id " << static_cast<int32_t>(session_id)); } else { LOG4CXX_WARN( logger_, - "SendEndSession is failed connection or session does not exist"); + "SendEndServicePrivate is failed connection or session does not exist"); } } void ProtocolHandlerImpl::SendEndSession(int32_t connection_id, uint8_t session_id) { - SendEndServicePrivate(connection_id, session_id, SERVICE_TYPE_RPC); + // A session is always associated with a primary connection ID + SendEndServicePrivate( + connection_id, connection_id, session_id, SERVICE_TYPE_RPC); } -void ProtocolHandlerImpl::SendEndService(int32_t connection_id, +void ProtocolHandlerImpl::SendEndService(int32_t primary_connection_id, + int32_t connection_id, uint8_t session_id, uint8_t service_type) { - SendEndServicePrivate(connection_id, session_id, service_type); + SendEndServicePrivate( + primary_connection_id, connection_id, session_id, service_type); } RESULT_CODE ProtocolHandlerImpl::SendHeartBeatAck(ConnectionID connection_id, @@ -563,6 +673,139 @@ RESULT_CODE ProtocolHandlerImpl::SendHeartBeatAck(ConnectionID connection_id, return RESULT_FAIL; } +void ProtocolHandlerImpl::SendTransportUpdateEvent(ConnectionID connection_id, + uint8_t session_id) { + LOG4CXX_AUTO_TRACE(logger_); + + uint8_t protocol_version; + if (session_observer_.ProtocolVersionUsed( + connection_id, session_id, protocol_version)) { + ProtocolFramePtr ptr( + new protocol_handler::ProtocolPacket(connection_id, + protocol_version, + PROTECTION_OFF, + FRAME_TYPE_CONTROL, + SERVICE_TYPE_CONTROL, + FRAME_DATA_TRANSPORT_EVENT_UPDATE, + session_id, + 0, + message_counters_[session_id]++)); + + BsonObject payload_obj; + bson_object_initialize_default(&payload_obj); + + int32_t tcp_port = atoi(tcp_port_.c_str()); + char tcp_ip_address[INET6_ADDRSTRLEN + 1]; + if (tcp_enabled_ && (tcp_port != 0)) { + strncpy(tcp_ip_address, tcp_ip_address_.c_str(), INET6_ADDRSTRLEN); + tcp_ip_address[INET6_ADDRSTRLEN] = '\0'; + bson_object_put_string( + &payload_obj, strings::tcp_ip_address, tcp_ip_address); + bson_object_put_int32(&payload_obj, strings::tcp_port, tcp_port); + } else { + tcp_ip_address[0] = '\0'; + bson_object_put_string( + &payload_obj, strings::tcp_ip_address, tcp_ip_address); + // omit TCP port number + } + LOG4CXX_INFO(logger_, + "SendTransportUpdateEvent IP address: " + << tcp_ip_address << " Port: " << tcp_port); + + uint8_t* payloadBytes = bson_object_to_bytes(&payload_obj); + ptr->set_data(payloadBytes, bson_object_size(&payload_obj)); + free(payloadBytes); + bson_object_deinitialize(&payload_obj); + + raw_ford_messages_to_mobile_.PostMessage( + impl::RawFordMessageToMobile(ptr, false)); + + LOG4CXX_DEBUG(logger_, + "SendTransportUpdateEvent() for connection " + << connection_id << " for session " + << static_cast<int32_t>(session_id)); + } else { + LOG4CXX_WARN(logger_, + "SendTransportUpdateEvent is failed connection or session " + "does not exist"); + } +} + +RESULT_CODE ProtocolHandlerImpl::SendRegisterSecondaryTransportAck( + ConnectionID connection_id, + ConnectionID primary_transport_connection_id, + uint8_t session_id) { + LOG4CXX_AUTO_TRACE(logger_); + + // acquire the protocol version from primary transport + uint8_t protocol_version; + if (session_observer_.ProtocolVersionUsed( + primary_transport_connection_id, session_id, protocol_version)) { + ProtocolFramePtr ptr(new protocol_handler::ProtocolPacket( + connection_id, + protocol_version, + PROTECTION_OFF, + FRAME_TYPE_CONTROL, + SERVICE_TYPE_CONTROL, + FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_ACK, + session_id, + 0u, + 2)); + + raw_ford_messages_to_mobile_.PostMessage( + impl::RawFordMessageToMobile(ptr, false)); + return RESULT_OK; + } + LOG4CXX_WARN(logger_, + "RegisterSecondaryTransportAck is failed connection or session " + "does not exist"); + return RESULT_FAIL; +} + +RESULT_CODE ProtocolHandlerImpl::SendRegisterSecondaryTransportNAck( + ConnectionID connection_id, + ConnectionID primary_transport_connection_id, + uint8_t session_id, + BsonObject* reason) { + LOG4CXX_AUTO_TRACE(logger_); + + // If mobile sends an invalid session ID and we cannot find out the Connection + // ID of primary transport, then we use version 5. (The multiple-transports + // feature is added in 5.1.0.) + uint8_t protocol_version = PROTOCOL_VERSION_5; + if (primary_transport_connection_id > 0) { + // acquire the protocol version from primary transport + if (!session_observer_.ProtocolVersionUsed( + primary_transport_connection_id, session_id, protocol_version)) { + LOG4CXX_WARN(logger_, + "Failed to acquire protocol version for " + "RegisterSecondaryTransportNAck"); + return RESULT_FAIL; + } + } + + ProtocolFramePtr ptr(new protocol_handler::ProtocolPacket( + connection_id, + protocol_version, + PROTECTION_OFF, + FRAME_TYPE_CONTROL, + SERVICE_TYPE_CONTROL, + FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_NACK, + session_id, + 0u, + 2)); + + if (reason) { + uint8_t* payloadBytes = bson_object_to_bytes(reason); + ptr->set_data(payloadBytes, bson_object_size(reason)); + free(payloadBytes); + } + + raw_ford_messages_to_mobile_.PostMessage( + impl::RawFordMessageToMobile(ptr, false)); + return RESULT_OK; +} + void ProtocolHandlerImpl::SendHeartBeat(int32_t connection_id, uint8_t session_id) { LOG4CXX_AUTO_TRACE(logger_); @@ -857,6 +1100,12 @@ void ProtocolHandlerImpl::OnConnectionClosed( multiframe_builder_.RemoveConnection(connection_id); } +void ProtocolHandlerImpl::OnUnexpectedDisconnect( + const transport_manager::ConnectionUID connection_id, + const transport_manager::CommunicationError& error) { + OnConnectionClosed(connection_id); +} + void ProtocolHandlerImpl::NotifyOnFailedHandshake() { LOG4CXX_AUTO_TRACE(logger_); #ifdef ENABLE_SECURITY @@ -864,6 +1113,72 @@ void ProtocolHandlerImpl::NotifyOnFailedHandshake() { #endif // ENABLE_SECURITY } +void ProtocolHandlerImpl::OnTransportConfigUpdated( + const transport_manager::transport_adapter::TransportConfig& configs) { + LOG4CXX_AUTO_TRACE(logger_); + + transport_manager::transport_adapter::TransportConfig::const_iterator it = + configs.find(transport_manager::transport_adapter::tc_enabled); + if (configs.end() == it) { + LOG4CXX_WARN(logger_, "No enabled field in OnTransportConfigUpdated"); + return; + } + + bool tcp_enabled = (0 == strcmp("true", it->second.c_str())); + std::string tcp_port; + + if (tcp_enabled) { + it = configs.find(transport_manager::transport_adapter::tc_tcp_port); + if (configs.end() == it) { + LOG4CXX_WARN(logger_, "No port field in OnTransportConfigUpdated"); + return; + } + tcp_port = it->second; + + it = configs.find(transport_manager::transport_adapter::tc_tcp_ip_address); + if (configs.end() == it) { + LOG4CXX_WARN(logger_, "No IP address field in OnTransportConfigUpdated"); + return; + } + tcp_enabled_ = true; + tcp_port_ = tcp_port; + tcp_ip_address_ = it->second; + } else { + tcp_enabled_ = false; + tcp_port_.clear(); + tcp_ip_address_.clear(); + } + + LOG4CXX_INFO(logger_, + "OnTransportConfigUpdated: new config enabled is " + << tcp_enabled_ << ". Port is " << tcp_port_ + << ". IP Address is " << tcp_ip_address_); + + // Walk the SessionConnection map and find all sessions that need a + // TransportUpdate Event. Sessions flagged with kDisabledSecondary in their + // secondary transport are ineligible for secondary transport, and + // therefore don't get this event. + DataAccessor<connection_handler::SessionConnectionMap> + session_connection_map_accessor = + connection_handler_.session_connection_map(); + const connection_handler::SessionConnectionMap& session_connection_map = + session_connection_map_accessor.GetData(); + connection_handler::SessionConnectionMap::const_iterator itr = + session_connection_map.begin(); + while (itr != session_connection_map.end()) { + const connection_handler::SessionTransports st = itr->second; + LOG4CXX_INFO(logger_, + "OnTransportConfigUpdated found session " + << itr->first << " with primary connection " + << st.primary_transport << " and secondary connection " + << st.secondary_transport); + if (st.secondary_transport != kDisabledSecondary) { + SendTransportUpdateEvent(st.primary_transport, itr->first); + } + itr++; + } +} + RESULT_CODE ProtocolHandlerImpl::SendFrame(const ProtocolFramePtr packet) { LOG4CXX_AUTO_TRACE(logger_); if (!packet) { @@ -1048,6 +1363,13 @@ RESULT_CODE ProtocolHandlerImpl::HandleSingleFrameMessage( << packet->data_size() << "; message " << ConvertPacketDataToString(packet->data(), packet->data_size())); + // Replace a potential secondary transport ID in the packet with the primary + // transport ID + const connection_handler::SessionTransports st = + connection_handler_.GetSessionTransports(packet->session_id()); + if (st.primary_transport != 0) { + packet->set_connection_id(st.primary_transport); + } const uint32_t connection_key = session_observer_.KeyFromPair( packet->connection_id(), packet->session_id()); @@ -1080,6 +1402,14 @@ RESULT_CODE ProtocolHandlerImpl::HandleMultiFrameMessage( const ProtocolFramePtr packet) { LOG4CXX_AUTO_TRACE(logger_); + // Replace a potential secondary transport ID in the packet with the primary + // transport ID + const connection_handler::SessionTransports st = + connection_handler_.GetSessionTransports(packet->session_id()); + if (st.primary_transport != 0) { + packet->set_connection_id(st.primary_transport); + } + if (multiframe_builder_.AddFrame(packet) != RESULT_OK) { LOG4CXX_WARN(logger_, "Frame assembling issue"); } @@ -1117,6 +1447,10 @@ RESULT_CODE ProtocolHandlerImpl::HandleControlMessage( << packet->connection_id()); return RESULT_OK; } + case FRAME_DATA_REGISTER_SECONDARY_TRANSPORT: { + LOG4CXX_TRACE(logger_, "FrameData: RegisterSecondaryTransport"); + return HandleControlMessageRegisterSecondaryTransport(packet); + } default: LOG4CXX_WARN(logger_, "Control message of type " @@ -1401,6 +1735,11 @@ RESULT_CODE ProtocolHandlerImpl::HandleControlMessageStartSession( const ConnectionID connection_id = packet->connection_id(); const uint8_t session_id = packet->session_id(); + LOG4CXX_INFO(logger_, + "StartSession ID " << static_cast<int>(session_id) + << " and Connection ID " + << static_cast<int>(connection_id)); + { sync_primitives::AutoLock auto_lock(start_session_frame_map_lock_); start_session_frame_map_[std::make_pair(connection_id, session_id)] = @@ -1414,6 +1753,50 @@ RESULT_CODE ProtocolHandlerImpl::HandleControlMessageStartSession( return RESULT_OK; } +RESULT_CODE ProtocolHandlerImpl::HandleControlMessageRegisterSecondaryTransport( + const ProtocolFramePtr packet) { + LOG4CXX_AUTO_TRACE(logger_); + const uint8_t session_id = packet->session_id(); + const ConnectionID connection_id = packet->connection_id(); + ConnectionID primary_connection_id = 0; + + LOG4CXX_INFO(logger_, + "RegisterSecondaryTransport ID " + << static_cast<int>(session_id) << " and Connection ID " + << static_cast<int>(connection_id)); + + if (connection_handler_.OnSecondaryTransportStarted( + primary_connection_id, connection_id, session_id)) { + SendRegisterSecondaryTransportAck( + connection_id, primary_connection_id, session_id); + } else { + char reason[256]; + BsonObject registerSecondaryTransportNackObj; + bson_object_initialize_default(®isterSecondaryTransportNackObj); + if (0 == session_id) { + strncpy(reason, + "RegisterSecondaryTransport MUST include a non-zero session ID", + 255); + } else if (primary_connection_id == 0) { + strncpy(reason, "RegisterSecondaryTransport session ID not found", 255); + } else { + strncpy( + reason, + "RegisterSecondaryTransport session ID has already been registered", + 255); + } + bson_object_put_string( + ®isterSecondaryTransportNackObj, strings::reason, reason); + SendRegisterSecondaryTransportNAck(connection_id, + primary_connection_id, + session_id, + ®isterSecondaryTransportNackObj); + bson_object_deinitialize(®isterSecondaryTransportNackObj); + } + + return RESULT_OK; +} + void ProtocolHandlerImpl::NotifySessionStartedResult( int32_t connection_id, uint8_t session_id, @@ -1423,6 +1806,7 @@ void ProtocolHandlerImpl::NotifySessionStartedResult( std::vector<std::string>& rejected_params) { LOG4CXX_AUTO_TRACE(logger_); protocol_handler::SessionContext context(connection_id, + connection_id, session_id, generated_session_id, ServiceType::kInvalidServiceType, @@ -1989,4 +2373,241 @@ uint8_t ProtocolHandlerImpl::SupportedSDLProtocolVersion() const { LOG4CXX_AUTO_TRACE(logger_); return get_settings().max_supported_protocol_version(); } + +const impl::TransportTypes transportTypes = { + std::make_pair( + std::string("AOA_USB"), + impl::TransportDescription(impl::TransportType::TT_USB, false, true)), + std::make_pair(std::string("SPP_BLUETOOTH"), + impl::TransportDescription( + impl::TransportType::TT_BLUETOOTH, false, true)), + std::make_pair(std::string("IAP_BLUETOOTH"), + impl::TransportDescription( + impl::TransportType::TT_BLUETOOTH, true, false)), + std::make_pair( + std::string("IAP_USB"), + impl::TransportDescription(impl::TransportType::TT_USB, true, false)), + std::make_pair( + std::string("TCP_WIFI"), + impl::TransportDescription(impl::TransportType::TT_WIFI, true, true)), + std::make_pair( + std::string("IAP_USB_HOST_MODE"), + impl::TransportDescription(impl::TransportType::TT_USB, true, false)), + std::make_pair( + std::string("IAP_USB_DEVICE_MODE"), + impl::TransportDescription(impl::TransportType::TT_USB, true, false)), + std::make_pair( + std::string("IAP_CARPLAY"), + impl::TransportDescription(impl::TransportType::TT_WIFI, true, false))}; + +const impl::TransportDescription +ProtocolHandlerImpl::GetTransportTypeFromConnectionType( + const std::string& connection_type) const { + impl::TransportDescription result = + impl::TransportDescription(impl::TransportType::TT_NONE, false, false); + impl::TransportTypes::const_iterator it = + transportTypes.find(connection_type); + if (it != transportTypes.end()) { + result = it->second; + } else { + LOG4CXX_ERROR(logger_, "Unknown connection type " << connection_type); + } + + return result; +} + +const bool ProtocolHandlerImpl::ParseSecondaryTransportConfiguration( + const ConnectionID connection_id, + std::vector<std::string>& secondaryTransports, + std::vector<int32_t>& audioServiceTransports, + std::vector<int32_t>& videoServiceTransports) const { + LOG4CXX_AUTO_TRACE(logger_); + std::vector<std::string> secondary_transport_types; + + // First discover what the connection type of the primary transport is + // and look up the allowed secondary transports for that primary transport + const std::string connection_type = + session_observer_.TransportTypeProfileStringFromConnHandle(connection_id); + const impl::TransportDescription td = + GetTransportTypeFromConnectionType(connection_type); + if (settings_.multiple_transports_enabled()) { + if (td.transport_type_ == impl::TransportType::TT_USB) { + secondary_transport_types = settings_.secondary_transports_for_usb(); + } else if (td.transport_type_ == impl::TransportType::TT_BLUETOOTH) { + secondary_transport_types = + settings_.secondary_transports_for_bluetooth(); + } else if (td.transport_type_ == impl::TransportType::TT_WIFI) { + secondary_transport_types = settings_.secondary_transports_for_wifi(); + } else { + LOG4CXX_ERROR( + logger_, + "Bad or unknown device type in ParseSecondaryTransportConfiguration"); + return false; + } + } + // note: even if settings_.multiple_transports_enabled() is false, we still + // send out an empty "secondaryTransports" parameter, along with + // "videoServiceTransports" and "audioServiceTransports" params which are + // useful without secondary transport. + + // Then, generate the "secondaryTransports" array for the StartSession ACK + GenerateSecondaryTransportsForStartSessionAck(secondary_transport_types, + td.ios_transport_, + td.android_transport_, + secondaryTransports); + + // Next, figure out which connections audio or video services are allowed on + GenerateServiceTransportsForStartSessionAck( + settings_.multiple_transports_enabled(), + settings_.audio_service_transports(), + connection_type, + td.transport_type_, + secondary_transport_types, + audioServiceTransports); + + GenerateServiceTransportsForStartSessionAck( + settings_.multiple_transports_enabled(), + settings_.video_service_transports(), + connection_type, + td.transport_type_, + secondary_transport_types, + videoServiceTransports); + + return true; +} + +void ProtocolHandlerImpl::GenerateSecondaryTransportsForStartSessionAck( + const std::vector<std::string>& secondary_transport_types, + bool device_is_ios, + bool device_is_android, + std::vector<std::string>& secondaryTransports) const { + LOG4CXX_AUTO_TRACE(logger_); + + // Parse the "secondary_transport_types" vector (which comes from + // smartDeviceLink.ini). For each entry in the vector, add an + // appropriate string to the secondaryTransports + std::vector<std::string>::const_iterator it = + secondary_transport_types.begin(); + while (it != secondary_transport_types.end()) { + const utils::custom_string::CustomString transport_type(*it); + if (transport_type.CompareIgnoreCase("USB")) { + if (device_is_ios) { + LOG4CXX_TRACE( + logger_, + "Adding IAP_USB to secondaryTransports for StartSessionAck"); + secondaryTransports.push_back("IAP_USB"); + } + if (device_is_android) { + LOG4CXX_TRACE( + logger_, + "Adding AOA_USB to secondaryTransports for StartSessionAck"); + secondaryTransports.push_back("AOA_USB"); + } + } else if (transport_type.CompareIgnoreCase("Bluetooth")) { + if (device_is_ios) { + LOG4CXX_TRACE( + logger_, + "Adding IAP_BLUETOOTH to secondaryTransports for StartSessionAck"); + secondaryTransports.push_back("IAP_BLUETOOTH"); + } + if (device_is_android) { + LOG4CXX_TRACE( + logger_, + "Adding SPP_BLUETOOTH to secondaryTransports for StartSessionAck"); + secondaryTransports.push_back("SPP_BLUETOOTH"); + } + } + if (transport_type.CompareIgnoreCase("WiFi")) { + LOG4CXX_TRACE( + logger_, + "Adding TCP_WIFI to secondaryTransports for StartSessionAck"); + secondaryTransports.push_back("TCP_WIFI"); + } + + it++; + } +} + +void ProtocolHandlerImpl::GenerateServiceTransportsForStartSessionAck( + bool secondary_enabled, + const std::vector<std::string>& service_transports, + const std::string& primary_connection_type, + const impl::TransportType primary_transport_type, + const std::vector<std::string>& secondary_transport_types, + std::vector<int32_t>& serviceTransports) const { + LOG4CXX_AUTO_TRACE(logger_); + + if (service_transports.size() == 0) { + if (secondary_enabled && !secondary_transport_types.empty()) { + LOG4CXX_TRACE(logger_, + "Empty Service Transports. Allowing service to run on both " + "connections"); + serviceTransports.push_back(1); + serviceTransports.push_back(2); + } else { + serviceTransports.push_back(1); + } + } else { + bool fPrimaryAdded = false; + bool fSecondaryAdded = false; + std::vector<std::string>::const_iterator it = service_transports.begin(); + for (; it != service_transports.end(); it++) { + const utils::custom_string::CustomString transport(*it); + LOG4CXX_TRACE(logger_, + "Service Allowed to run on " << transport.c_str() + << " transport"); + + if (!fPrimaryAdded && + (transport.CompareIgnoreCase(primary_connection_type.c_str()) || + (transport.CompareIgnoreCase("IAP_USB") && + primary_transport_type == impl::TransportType::TT_USB))) { + LOG4CXX_TRACE(logger_, "Service allowed on primary transport"); + serviceTransports.push_back(1); + fPrimaryAdded = true; + } + + if (!fSecondaryAdded) { + const utils::custom_string::CustomString transport_type( + TransportTypeFromTransport(transport)); + std::vector<std::string>::const_iterator found = + std::find_if(secondary_transport_types.begin(), + secondary_transport_types.end(), + [&](const std::string& secondary_transport_type) { + return transport_type.CompareIgnoreCase( + secondary_transport_type.c_str()); + }); + if (found != secondary_transport_types.end()) { + LOG4CXX_TRACE(logger_, "Service allowed on secondary transport"); + serviceTransports.push_back(2); + fSecondaryAdded = true; + } + } + + if (fPrimaryAdded && fSecondaryAdded) { + break; + } + } + } +} + +const std::string ProtocolHandlerImpl::TransportTypeFromTransport( + const utils::custom_string::CustomString& transport) const { + std::string transport_type; + + if (transport.CompareIgnoreCase("IAP_BLUETOOTH") || + transport.CompareIgnoreCase("SPP_BLUETOOTH")) { + transport_type = "Bluetooth"; + } else if (transport.CompareIgnoreCase("IAP_USB") || + transport.CompareIgnoreCase("AOA_USB") || + transport.CompareIgnoreCase("IAP_USB_HOST_MODE") || + transport.CompareIgnoreCase("IAP_USB_DEVICE_MODE")) { + transport_type = "USB"; + } else if (transport.CompareIgnoreCase("TCP_WIFI") || + transport.CompareIgnoreCase("IAP_CARPLAY")) { + transport_type = "WiFi"; + } + + return transport_type; +} + } // namespace protocol_handler diff --git a/src/components/protocol_handler/src/protocol_packet.cc b/src/components/protocol_handler/src/protocol_packet.cc index a490916c99..3cd9e7f781 100644 --- a/src/components/protocol_handler/src/protocol_packet.cc +++ b/src/components/protocol_handler/src/protocol_packet.cc @@ -304,8 +304,8 @@ RESULT_CODE ProtocolPacket::ProtocolHeaderValidator::validate( // Check frame info for each frame type // Frame type shall be 0x00 (Control), 0x01 (Single), 0x02 (First), 0x03 // (Consecutive) - // For Control frames Frame info value shall be from 0x00 to 0x06 or 0xFE(Data - // Ack), 0xFF(HB Ack) + // For Control frames Frame info value shall be from 0x00 to 0x09 or + // 0xFD(Transport Event Update), 0xFE(Data Ack), 0xFF(HB Ack) // For Single and First frames Frame info value shall be equal 0x00 switch (header.frameType) { case FRAME_TYPE_CONTROL: { @@ -317,6 +317,10 @@ RESULT_CODE ProtocolPacket::ProtocolHeaderValidator::validate( case FRAME_DATA_END_SERVICE: case FRAME_DATA_END_SERVICE_ACK: case FRAME_DATA_END_SERVICE_NACK: + case FRAME_DATA_REGISTER_SECONDARY_TRANSPORT: + case FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_ACK: + case FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_NACK: + case FRAME_DATA_TRANSPORT_EVENT_UPDATE: case FRAME_DATA_SERVICE_DATA_ACK: case FRAME_DATA_HEART_BEAT_ACK: break; @@ -642,6 +646,10 @@ ConnectionID ProtocolPacket::connection_id() const { return connection_id_; } +void ProtocolPacket::set_connection_id(ConnectionID connection_id) { + connection_id_ = connection_id; +} + uint32_t ProtocolPacket::payload_size() const { return payload_size_; } diff --git a/src/components/protocol_handler/test/incoming_data_handler_test.cc b/src/components/protocol_handler/test/incoming_data_handler_test.cc index d0a311583c..393579927c 100644 --- a/src/components/protocol_handler/test/incoming_data_handler_test.cc +++ b/src/components/protocol_handler/test/incoming_data_handler_test.cc @@ -393,13 +393,13 @@ TEST_F(IncomingDataHandlerTest, MalformedPacket_FrameType) { } } -// For Control frames Frame info value shall be from 0x00 to 0x06 or 0xFE(Data -// Ack), 0xFF(HB Ack) +// For Control frames Frame info value shall be from 0x00 to 0x09 or 0xFD +// (Transport Update Event), 0xFE(Data Ack), 0xFF(HB Ack) TEST_F(IncomingDataHandlerTest, MalformedPacket_ControlFrame) { FrameList malformed_packets; std::vector<uint8_t> malformed_frame_data; - for (uint8_t frame_type = FRAME_DATA_END_SERVICE_NACK + 1; - frame_type < FRAME_DATA_SERVICE_DATA_ACK; + for (uint8_t frame_type = FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_NACK + 1; + frame_type < FRAME_DATA_TRANSPORT_EVENT_UPDATE; ++frame_type) { malformed_frame_data.push_back(frame_type); } diff --git a/src/components/protocol_handler/test/protocol_handler_tm_test.cc b/src/components/protocol_handler/test/protocol_handler_tm_test.cc index 0cb8e155d0..2e06cd702e 100644 --- a/src/components/protocol_handler/test/protocol_handler_tm_test.cc +++ b/src/components/protocol_handler/test/protocol_handler_tm_test.cc @@ -41,6 +41,7 @@ #include "protocol_handler/mock_protocol_handler_settings.h" #include "protocol_handler/mock_session_observer.h" #include "connection_handler/mock_connection_handler.h" +#include "connection_handler/connection_handler_impl.h" #ifdef ENABLE_SECURITY #include "security_manager/mock_security_manager.h" #include "security_manager/mock_ssl_context.h" @@ -51,6 +52,15 @@ #include "utils/test_async_waiter.h" #include <bson_object.h> +namespace transport_manager { +namespace transport_adapter { +// taken from transport_adapter_impl.cc +const char* tc_enabled = "enabled"; +const char* tc_tcp_port = "tcp_port"; +const char* tc_tcp_ip_address = "tcp_ip_address"; +} +} + namespace test { namespace components { namespace protocol_handler_test { @@ -88,6 +98,10 @@ using protocol_handler::FRAME_DATA_SERVICE_DATA_ACK; using protocol_handler::FRAME_DATA_SINGLE; using protocol_handler::FRAME_DATA_FIRST; using protocol_handler::FRAME_DATA_LAST_CONSECUTIVE; +using protocol_handler::FRAME_DATA_REGISTER_SECONDARY_TRANSPORT; +using protocol_handler::FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_ACK; +using protocol_handler::FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_NACK; +using protocol_handler::FRAME_DATA_TRANSPORT_EVENT_UPDATE; using protocol_handler::kRpc; using protocol_handler::kControl; using protocol_handler::kAudio; @@ -108,10 +122,12 @@ using ContextCreationStrategy = using connection_handler::DeviceHandle; // Google Testing Framework Entities using ::testing::Return; +using ::testing::ReturnRef; using ::testing::ReturnRefOfCopy; using ::testing::ReturnNull; using ::testing::An; using ::testing::AnyOf; +using ::testing::AtLeast; using ::testing::ByRef; using ::testing::DoAll; using ::testing::SaveArg; @@ -190,6 +206,9 @@ class ProtocolHandlerImplTest : public ::testing::Test { . // Return false to avoid call KeepConnectionAlive WillRepeatedly(Return(false)); + + session_connection_map_lock_ptr_ = + std::make_shared<sync_primitives::Lock>(); } void TearDown() OVERRIDE { @@ -216,6 +235,7 @@ class ProtocolHandlerImplTest : public ::testing::Test { const uint32_t hash_id, const bool protection_flag) { return protocol_handler::SessionContext(connection_id, + connection_id, initial_session_id, new_session_id, service_type, @@ -320,10 +340,11 @@ class ProtocolHandlerImplTest : public ::testing::Test { uint8_t service_type, uint8_t sessionId, uint32_t frame_data, + uint8_t protocol_version = PROTOCOL_VERSION_3, uint32_t dataSize = 0u, const uint8_t* data = NULL) { SendTMMessage(connection_id, - PROTOCOL_VERSION_3, + protocol_version, protection, FRAME_TYPE_CONTROL, service_type, @@ -334,6 +355,18 @@ class ProtocolHandlerImplTest : public ::testing::Test { data); } + void VerifySecondaryTransportParamsInStartSessionAck( + bool config_multiple_transports_enabled, + const std::vector<std::string>& config_secondary_transports_for_usb, + const std::vector<std::string>& config_secondary_transports_for_bluetooth, + const std::vector<std::string>& config_secondary_transports_for_wifi, + const std::vector<std::string>& config_audio_service_transports, + const std::vector<std::string>& config_video_service_transports, + const std::string& connection_type_string, + const std::vector<std::string>& expected_transport_strings, + const std::vector<int32_t>& expected_audio_service_transports, + const std::vector<int32_t>& expected_video_service_transports); + testing::NiceMock<MockProtocolHandlerSettings> protocol_handler_settings_mock; ::utils::SharedPtr<ProtocolHandlerImpl> protocol_handler_impl; TransportManagerListener* tm_listener; @@ -360,6 +393,10 @@ class ProtocolHandlerImplTest : public ::testing::Test { std::vector<int> force_unprotected_services; #endif // ENABLE_SECURITY std::vector<std::string> empty_rejected_param_; + // Used by OnTransportConfigUpdated() tests. The lifetime of these objects + // should be longer than that of a test case. + connection_handler::SessionConnectionMap session_connection_map_; + std::shared_ptr<sync_primitives::Lock> session_connection_map_lock_ptr_; }; #ifdef ENABLE_SECURITY @@ -1485,6 +1522,1131 @@ TEST_F(ProtocolHandlerImplTest, } #endif // ENABLE_SECURITY +void ProtocolHandlerImplTest::VerifySecondaryTransportParamsInStartSessionAck( + bool config_multiple_transports_enabled, + const std::vector<std::string>& config_secondary_transports_for_usb, + const std::vector<std::string>& config_secondary_transports_for_bluetooth, + const std::vector<std::string>& config_secondary_transports_for_wifi, + const std::vector<std::string>& config_audio_service_transports, + const std::vector<std::string>& config_video_service_transports, + const std::string& connection_type_string, + const std::vector<std::string>& expected_transport_strings, + const std::vector<int32_t>& expected_audio_service_transports, + const std::vector<int32_t>& expected_video_service_transports) { + const size_t maximum_rpc_payload_size = 1500; + EXPECT_CALL(protocol_handler_settings_mock, maximum_rpc_payload_size()) + .WillRepeatedly(Return(maximum_rpc_payload_size)); + InitProtocolHandlerImpl(0u, 0u); + + TestAsyncWaiter waiter; + uint32_t times = 0; + + const uint8_t input_protocol_version = 5; + const uint32_t hash_id = 123456; + ProtocolPacket::ProtocolVersion full_version(5, 1, 0); + char full_version_string[] = "5.1.0"; + + // configuration setup + EXPECT_CALL(protocol_handler_settings_mock, max_supported_protocol_version()) + .WillRepeatedly(Return(PROTOCOL_VERSION_5)); + EXPECT_CALL(protocol_handler_settings_mock, multiple_transports_enabled()) + .WillRepeatedly(Return(config_multiple_transports_enabled)); + EXPECT_CALL(protocol_handler_settings_mock, secondary_transports_for_usb()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_usb)); + EXPECT_CALL(protocol_handler_settings_mock, + secondary_transports_for_bluetooth()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_bluetooth)); + EXPECT_CALL(protocol_handler_settings_mock, secondary_transports_for_wifi()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_wifi)); + EXPECT_CALL(protocol_handler_settings_mock, audio_service_transports()) + .WillOnce(ReturnRef(config_audio_service_transports)); + EXPECT_CALL(protocol_handler_settings_mock, video_service_transports()) + .WillOnce(ReturnRef(config_video_service_transports)); + + EXPECT_CALL(session_observer_mock, + TransportTypeProfileStringFromConnHandle(connection_id)) + .WillRepeatedly(Return(connection_type_string)); + + // Prepare expected BSON parameters. When we add another param in Start + // Service ACK frame in future, it should be also added here. + BsonObject expected_obj; + bson_object_initialize_default(&expected_obj); + // mtu + bson_object_put_int64(&expected_obj, + protocol_handler::strings::mtu, + static_cast<int64_t>(maximum_rpc_payload_size)); + // hashId + bson_object_put_int32(&expected_obj, + protocol_handler::strings::hash_id, + static_cast<int32_t>(hash_id)); + // protocolVersion + bson_object_put_string(&expected_obj, + protocol_handler::strings::protocol_version, + full_version_string); + // secondaryTransports + BsonArray secondary_transports; + bson_array_initialize(&secondary_transports, + expected_transport_strings.size()); + for (std::vector<std::string>::const_iterator it = + expected_transport_strings.begin(); + it != expected_transport_strings.end(); + ++it) { + // note: if there is no transport allowed, we can either make the array + // empty, or completely omit the array. (The spec allows both cases.) In + // this test case we make the array empty. + bson_array_add_string(&secondary_transports, + const_cast<char*>(it->c_str())); + } + bson_object_put_array(&expected_obj, + protocol_handler::strings::secondary_transports, + &secondary_transports); + // audioServiceTransports + BsonArray audio_service_transports; + if (expected_audio_service_transports.size() > 0) { + bson_array_initialize(&audio_service_transports, + expected_audio_service_transports.size()); + for (std::vector<int32_t>::const_iterator it = + expected_audio_service_transports.begin(); + it != expected_audio_service_transports.end(); + ++it) { + bson_array_add_int32(&audio_service_transports, *it); + } + bson_object_put_array(&expected_obj, + protocol_handler::strings::audio_service_transports, + &audio_service_transports); + } + // videoServiceTransports + BsonArray video_service_transports; + if (expected_video_service_transports.size() > 0) { + bson_array_initialize(&video_service_transports, + expected_video_service_transports.size()); + for (std::vector<int32_t>::const_iterator it = + expected_video_service_transports.begin(); + it != expected_video_service_transports.end(); + ++it) { + bson_array_add_int32(&video_service_transports, *it); + } + bson_object_put_array(&expected_obj, + protocol_handler::strings::video_service_transports, + &video_service_transports); + } + + std::vector<uint8_t> expected_param = + CreateVectorFromBsonObject(&expected_obj); + + bson_object_deinitialize(&expected_obj); + + EXPECT_CALL(transport_manager_mock, + SendMessageToDevice(ControlMessage(FRAME_DATA_START_SERVICE_ACK, + PROTECTION_OFF, + connection_id, + Eq(expected_param)))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + +#ifdef ENABLE_SECURITY + AddSecurityManager(); + + EXPECT_CALL(session_observer_mock, KeyFromPair(connection_id, session_id)) + .WillOnce(Return(connection_key)); + + EXPECT_CALL(session_observer_mock, GetSSLContext(connection_key, kRpc)) + .WillOnce(ReturnNull()); +#endif // ENABLE_SECURITY + + protocol_handler_impl->SendStartSessionAck(connection_id, + session_id, + input_protocol_version, + hash_id, + protocol_handler::SERVICE_TYPE_RPC, + false /* protection */, + full_version); + + EXPECT_TRUE(waiter.WaitFor(times, kAsyncExpectationsTimeout)); +} + +TEST_F(ProtocolHandlerImplTest, + StartSessionAck_SecondaryTransportParams_Enabled) { + // config allows secondary transport only when connected through Bluetooth, + // and the secondary is Wi-Fi + std::vector<std::string> secondary_transports_for_usb; // empty + std::vector<std::string> secondary_transports_for_bluetooth; + secondary_transports_for_bluetooth.push_back("WiFi"); + std::vector<std::string> secondary_transports_for_wifi; // empty + // config allows video and audio services to run on all transports except + // Bluetooth + std::vector<std::string> audio_service_transports; + audio_service_transports.push_back("IAP_USB"); + audio_service_transports.push_back("IAP_USB_HOST_MODE"); + audio_service_transports.push_back("IAP_USB_DEVICE_MODE"); + audio_service_transports.push_back("IAP_CARPLAY"); + audio_service_transports.push_back("AOA_USB"); + audio_service_transports.push_back("TCP_WIFI"); + std::vector<std::string> video_service_transports; + video_service_transports.push_back("IAP_USB"); + video_service_transports.push_back("IAP_USB_HOST_MODE"); + video_service_transports.push_back("IAP_USB_DEVICE_MODE"); + video_service_transports.push_back("IAP_CARPLAY"); + video_service_transports.push_back("AOA_USB"); + video_service_transports.push_back("TCP_WIFI"); + + // assume the device is Android and is connected through Bluetooth SPP + std::string connection_type_string("SPP_BLUETOOTH"); + + // Core should specify WiFi for secondary transport, and should allow video + // and audio services only on secondary transport + std::vector<std::string> expected_transport_strings; + expected_transport_strings.push_back("TCP_WIFI"); + std::vector<int32_t> expected_audio_service_transports; + expected_audio_service_transports.push_back(2); + std::vector<int32_t> expected_video_service_transports; + expected_video_service_transports.push_back(2); + + // A TransportUpdateEvent is also issued after Start Service ACK. We don't + // check it in this test case. + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly(Return(false)); + + VerifySecondaryTransportParamsInStartSessionAck( + true, + secondary_transports_for_usb, + secondary_transports_for_bluetooth, + secondary_transports_for_wifi, + audio_service_transports, + video_service_transports, + connection_type_string, + expected_transport_strings, + expected_audio_service_transports, + expected_video_service_transports); +} + +TEST_F(ProtocolHandlerImplTest, + StartSessionAck_SecondaryTransportParams_NoSecondaryTransport) { + // config allows secondary transport only when connected through Bluetooth, + // and the secondary is Wi-Fi + std::vector<std::string> secondary_transports_for_usb; // empty + std::vector<std::string> secondary_transports_for_bluetooth; + secondary_transports_for_bluetooth.push_back("WiFi"); + std::vector<std::string> secondary_transports_for_wifi; // empty + // config allows video and audio services to run on all transports except + // Bluetooth + std::vector<std::string> audio_service_transports; + audio_service_transports.push_back("IAP_USB"); + audio_service_transports.push_back("IAP_USB_HOST_MODE"); + audio_service_transports.push_back("IAP_USB_DEVICE_MODE"); + audio_service_transports.push_back("IAP_CARPLAY"); + audio_service_transports.push_back("AOA_USB"); + audio_service_transports.push_back("TCP_WIFI"); + std::vector<std::string> video_service_transports; + video_service_transports.push_back("IAP_USB"); + video_service_transports.push_back("IAP_USB_HOST_MODE"); + video_service_transports.push_back("IAP_USB_DEVICE_MODE"); + video_service_transports.push_back("IAP_CARPLAY"); + video_service_transports.push_back("AOA_USB"); + video_service_transports.push_back("TCP_WIFI"); + + // assume the device is iOS and is connected through iAP over USB + std::string connection_type_string("IAP_USB"); + + // Core should not offer any secondary transport. It will allow both video + // and audio services on primary transport. + std::vector<std::string> expected_transport_strings; // empty + std::vector<int32_t> expected_audio_service_transports; + expected_audio_service_transports.push_back(1); + std::vector<int32_t> expected_video_service_transports; + expected_video_service_transports.push_back(1); + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly(Return(false)); + + VerifySecondaryTransportParamsInStartSessionAck( + true, + secondary_transports_for_usb, + secondary_transports_for_bluetooth, + secondary_transports_for_wifi, + audio_service_transports, + video_service_transports, + connection_type_string, + expected_transport_strings, + expected_audio_service_transports, + expected_video_service_transports); +} + +TEST_F(ProtocolHandlerImplTest, + StartSessionAck_SecondaryTransportParams_MultipleSecondaryTransports) { + // config allows secondary transport only when connected through Bluetooth, + // and the secondary is Wi-Fi and USB + std::vector<std::string> secondary_transports_for_usb; // empty + std::vector<std::string> secondary_transports_for_bluetooth; + secondary_transports_for_bluetooth.push_back("WiFi"); + secondary_transports_for_bluetooth.push_back("USB"); + std::vector<std::string> secondary_transports_for_wifi; // empty + // config allows video and audio services to run on all transports except + // Bluetooth + std::vector<std::string> audio_service_transports; + audio_service_transports.push_back("IAP_USB"); + audio_service_transports.push_back("IAP_USB_HOST_MODE"); + audio_service_transports.push_back("IAP_USB_DEVICE_MODE"); + audio_service_transports.push_back("IAP_CARPLAY"); + audio_service_transports.push_back("AOA_USB"); + audio_service_transports.push_back("TCP_WIFI"); + std::vector<std::string> video_service_transports; + video_service_transports.push_back("IAP_USB"); + video_service_transports.push_back("IAP_USB_HOST_MODE"); + video_service_transports.push_back("IAP_USB_DEVICE_MODE"); + video_service_transports.push_back("IAP_CARPLAY"); + video_service_transports.push_back("AOA_USB"); + video_service_transports.push_back("TCP_WIFI"); + + // assume the device is iOS and is connected through iAP over Bluetooth + std::string connection_type_string("IAP_BLUETOOTH"); + + // Core should offer both Wi-Fi and USB for secondary transport. Since the + // device is iOS, Core should specify "IAP_USB". + std::vector<std::string> expected_transport_strings; + expected_transport_strings.push_back("TCP_WIFI"); + expected_transport_strings.push_back("IAP_USB"); + std::vector<int32_t> expected_audio_service_transports; + expected_audio_service_transports.push_back(2); + std::vector<int32_t> expected_video_service_transports; + expected_video_service_transports.push_back(2); + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly(Return(false)); + + VerifySecondaryTransportParamsInStartSessionAck( + true, + secondary_transports_for_usb, + secondary_transports_for_bluetooth, + secondary_transports_for_wifi, + audio_service_transports, + video_service_transports, + connection_type_string, + expected_transport_strings, + expected_audio_service_transports, + expected_video_service_transports); +} + +TEST_F( + ProtocolHandlerImplTest, + StartSessionAck_SecondaryTransportParams_ServiceAllowedOnBothTransports) { + std::vector<std::string> secondary_transports_for_usb; + secondary_transports_for_usb.push_back("WiFi"); + std::vector<std::string> secondary_transports_for_bluetooth; + secondary_transports_for_bluetooth.push_back("USB"); + std::vector<std::string> secondary_transports_for_wifi; // empty + // config allows video service to run on Wi-Fi transports only, and audio + // service to run on all transports + std::vector<std::string> audio_service_transports; + audio_service_transports.push_back("IAP_BLUETOOTH"); + audio_service_transports.push_back("IAP_USB"); + audio_service_transports.push_back("IAP_USB_HOST_MODE"); + audio_service_transports.push_back("IAP_USB_DEVICE_MODE"); + audio_service_transports.push_back("IAP_CARPLAY"); + audio_service_transports.push_back("SPP_BLUETOOTH"); + audio_service_transports.push_back("AOA_USB"); + audio_service_transports.push_back("TCP_WIFI"); + std::vector<std::string> video_service_transports; + video_service_transports.push_back("IAP_CARPLAY"); + video_service_transports.push_back("TCP_WIFI"); + + // assume the device is Android and is connected through AOA + std::string connection_type_string("AOA_USB"); + + // Core should offer Wi-Fi for secondary transport. It should allow audio + // service to run on both primary and secondary, while video service to run + // on secondary only. Since the list specifies AOA_USB then TCP_WIFI, the + // priority is primary > secondary. + std::vector<std::string> expected_transport_strings; + expected_transport_strings.push_back("TCP_WIFI"); + std::vector<int32_t> expected_audio_service_transports; + expected_audio_service_transports.push_back(1); // primary preferred + expected_audio_service_transports.push_back(2); + std::vector<int32_t> expected_video_service_transports; + expected_video_service_transports.push_back(2); + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly(Return(false)); + + VerifySecondaryTransportParamsInStartSessionAck( + true, + secondary_transports_for_usb, + secondary_transports_for_bluetooth, + secondary_transports_for_wifi, + audio_service_transports, + video_service_transports, + connection_type_string, + expected_transport_strings, + expected_audio_service_transports, + expected_video_service_transports); +} + +TEST_F(ProtocolHandlerImplTest, + StartSessionAck_SecondaryTransportParams_SecondaryDisabled) { + std::vector<std::string> secondary_transports_for_usb; // empty + std::vector<std::string> secondary_transports_for_bluetooth; // empty + std::vector<std::string> secondary_transports_for_wifi; // empty + // config allows video and audio services to run on all transports + std::vector<std::string> audio_service_transports; + audio_service_transports.push_back("IAP_BLUETOOTH"); + audio_service_transports.push_back("IAP_USB"); + audio_service_transports.push_back("IAP_USB_HOST_MODE"); + audio_service_transports.push_back("IAP_USB_DEVICE_MODE"); + audio_service_transports.push_back("IAP_CARPLAY"); + audio_service_transports.push_back("SPP_BLUETOOTH"); + audio_service_transports.push_back("AOA_USB"); + audio_service_transports.push_back("TCP_WIFI"); + std::vector<std::string> video_service_transports; + video_service_transports.push_back("IAP_BLUETOOTH"); + video_service_transports.push_back("IAP_USB"); + video_service_transports.push_back("IAP_USB_HOST_MODE"); + video_service_transports.push_back("IAP_USB_DEVICE_MODE"); + video_service_transports.push_back("IAP_CARPLAY"); + video_service_transports.push_back("SPP_BLUETOOTH"); + video_service_transports.push_back("AOA_USB"); + video_service_transports.push_back("TCP_WIFI"); + + // assume the device is iOS and is connected through iAP over Bluetooth + std::string connection_type_string("IAP_BLUETOOTH"); + + // Core should not offer any secondary transport. It should still send + // the video/audio service transport lists. + std::vector<std::string> expected_transport_strings; // empty + std::vector<int32_t> expected_audio_service_transports; + expected_audio_service_transports.push_back(1); + std::vector<int32_t> expected_video_service_transports; + expected_video_service_transports.push_back(1); + + connection_handler::SessionTransports dummy_st = {0, 0}; + EXPECT_CALL(connection_handler_mock, + SetSecondaryTransportID(_, kDisabledSecondary)) + .WillOnce(Return(dummy_st)); + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly(Return(false)); + + VerifySecondaryTransportParamsInStartSessionAck( + false, /* disabled */ + secondary_transports_for_usb, + secondary_transports_for_bluetooth, + secondary_transports_for_wifi, + audio_service_transports, + video_service_transports, + connection_type_string, + expected_transport_strings, + expected_audio_service_transports, + expected_video_service_transports); +} + +TEST_F(ProtocolHandlerImplTest, + StartSessionAck_SecondaryTransportParams_ServicesMapEmpty) { + std::vector<std::string> secondary_transports_for_usb; // empty + std::vector<std::string> secondary_transports_for_bluetooth; + secondary_transports_for_bluetooth.push_back("USB"); + std::vector<std::string> secondary_transports_for_wifi; + secondary_transports_for_wifi.push_back("USB"); + // config does not specify video and audio services + std::vector<std::string> audio_service_transports; // empty + std::vector<std::string> video_service_transports; // empty + + // assume the device is connected through Wi-Fi (so not sure if it's iOS or + // Android) + std::string connection_type_string("TCP_WIFI"); + + // Core should offer USB transport for secondary transport. (Since the OS type + // is unknown, it will offer both IAP_USB and AOA_USB.) Also, it should allow + // video/audio services on all transports. + std::vector<std::string> expected_transport_strings; + expected_transport_strings.push_back("IAP_USB"); + expected_transport_strings.push_back("AOA_USB"); + std::vector<int32_t> expected_audio_service_transports; + expected_audio_service_transports.push_back(1); + expected_audio_service_transports.push_back(2); + std::vector<int32_t> expected_video_service_transports; + expected_video_service_transports.push_back(1); + expected_video_service_transports.push_back(2); + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly(Return(false)); + + VerifySecondaryTransportParamsInStartSessionAck( + true, + secondary_transports_for_usb, + secondary_transports_for_bluetooth, + secondary_transports_for_wifi, + audio_service_transports, + video_service_transports, + connection_type_string, + expected_transport_strings, + expected_audio_service_transports, + expected_video_service_transports); +} + +TEST_F( + ProtocolHandlerImplTest, + StartSessionAck_SecondaryTransportParams_SecondaryDisabled_ServicesMapEmpty) { + std::vector<std::string> secondary_transports_for_usb; // empty + std::vector<std::string> secondary_transports_for_bluetooth; // empty + std::vector<std::string> secondary_transports_for_wifi; // empty + // config does not specify video and audio services + std::vector<std::string> audio_service_transports; // empty + std::vector<std::string> video_service_transports; // empty + + std::string connection_type_string("IAP_BLUETOOTH"); + + // Core should not offer any secondary transport. It should still send + // the video/audio service transport lists. + std::vector<std::string> expected_transport_strings; // empty + std::vector<int32_t> expected_audio_service_transports; + expected_audio_service_transports.push_back(1); + std::vector<int32_t> expected_video_service_transports; + expected_video_service_transports.push_back(1); + + connection_handler::SessionTransports dummy_st = {0, 0}; + EXPECT_CALL(connection_handler_mock, + SetSecondaryTransportID(_, kDisabledSecondary)) + .WillOnce(Return(dummy_st)); + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly(Return(false)); + + VerifySecondaryTransportParamsInStartSessionAck( + false, /* disabled */ + secondary_transports_for_usb, + secondary_transports_for_bluetooth, + secondary_transports_for_wifi, + audio_service_transports, + video_service_transports, + connection_type_string, + expected_transport_strings, + expected_audio_service_transports, + expected_video_service_transports); +} + +// Secondary transport param should not be included for apps with v5.0.0 +TEST_F(ProtocolHandlerImplTest, + StartSessionAck_Unprotected_NoSecondaryTransportParamsForV5) { + TestAsyncWaiter waiter; + uint32_t times = 0; + + const uint8_t input_protocol_version = 5; + const uint32_t hash_id = 123456; + ProtocolPacket::ProtocolVersion full_version(5, 0, 0); + char full_version_string[] = "5.0.0"; + + const size_t maximum_rpc_payload_size = 1500; + EXPECT_CALL(protocol_handler_settings_mock, maximum_rpc_payload_size()) + .WillRepeatedly(Return(maximum_rpc_payload_size)); + InitProtocolHandlerImpl(0u, 0u); + + // configuration + std::vector<std::string> config_secondary_transports_for_usb; // empty + std::vector<std::string> config_secondary_transports_for_bluetooth; + config_secondary_transports_for_bluetooth.push_back("USB"); + std::vector<std::string> config_secondary_transports_for_wifi; + config_secondary_transports_for_wifi.push_back("USB"); + + // assume the device is iOS and is connected through iAP over Bluetooth + std::string connection_type_string("IAP_BLUETOOTH"); + + // configuration setup + EXPECT_CALL(protocol_handler_settings_mock, max_supported_protocol_version()) + .WillRepeatedly(Return(PROTOCOL_VERSION_5)); + EXPECT_CALL(protocol_handler_settings_mock, multiple_transports_enabled()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(protocol_handler_settings_mock, secondary_transports_for_usb()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_usb)); + EXPECT_CALL(protocol_handler_settings_mock, + secondary_transports_for_bluetooth()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_bluetooth)); + EXPECT_CALL(protocol_handler_settings_mock, secondary_transports_for_wifi()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_wifi)); + + EXPECT_CALL(session_observer_mock, + TransportTypeProfileStringFromConnHandle(connection_id)) + .WillRepeatedly(Return(connection_type_string)); + + // BSON params should not include any of "secondaryTransports", + // "audioServiceTransports" and "videoServiceTransports" since v5.0.0 app + // does not understand them + BsonObject expected_obj; + bson_object_initialize_default(&expected_obj); + // mtu + bson_object_put_int64(&expected_obj, + protocol_handler::strings::mtu, + static_cast<int64_t>(maximum_rpc_payload_size)); + // hashId + bson_object_put_int32(&expected_obj, + protocol_handler::strings::hash_id, + static_cast<int32_t>(hash_id)); + // protocolVersion + bson_object_put_string(&expected_obj, + protocol_handler::strings::protocol_version, + full_version_string); + + std::vector<uint8_t> expected_param = + CreateVectorFromBsonObject(&expected_obj); + + bson_object_deinitialize(&expected_obj); + + EXPECT_CALL(transport_manager_mock, + SendMessageToDevice(ControlMessage(FRAME_DATA_START_SERVICE_ACK, + PROTECTION_OFF, + connection_id, + Eq(expected_param)))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + + connection_handler::SessionTransports dummy_st = {0, 0}; + EXPECT_CALL(connection_handler_mock, + SetSecondaryTransportID(_, kDisabledSecondary)) + .WillOnce(Return(dummy_st)); + + // Since the protocol version is less than 5.1.0, Core should not issue + // TransportEventUpdate frame. Enable ProtocolVersionUsed() call and verify + // that transport_manager_mock will NOT receive another SendMessageToDevice() + // call. + ON_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillByDefault(Return(true)); + +#ifdef ENABLE_SECURITY + AddSecurityManager(); + + EXPECT_CALL(session_observer_mock, KeyFromPair(connection_id, session_id)) + .WillOnce(Return(connection_key)); + + EXPECT_CALL(session_observer_mock, GetSSLContext(connection_key, kRpc)) + .WillOnce(ReturnNull()); +#endif // ENABLE_SECURITY + + protocol_handler_impl->SendStartSessionAck(connection_id, + session_id, + input_protocol_version, + hash_id, + protocol_handler::SERVICE_TYPE_RPC, + false /* protection */, + full_version); + + EXPECT_TRUE(waiter.WaitFor(times, kAsyncExpectationsTimeout)); +} + +TEST_F(ProtocolHandlerImplTest, StartSessionAck_PrimaryTransportUSBHostMode) { + // config allows secondary transport only when connected through Bluetooth, + // and the secondary is Wi-Fi + std::vector<std::string> secondary_transports_for_usb; + secondary_transports_for_usb.push_back("WiFi"); + std::vector<std::string> secondary_transports_for_bluetooth; // empty + std::vector<std::string> secondary_transports_for_wifi; // empty + // config allows video and audio services to run on all transports except + // Bluetooth + std::vector<std::string> audio_service_transports; + audio_service_transports.push_back("IAP_USB_DEVICE_MODE"); + audio_service_transports.push_back("IAP_CARPLAY"); + audio_service_transports.push_back("AOA_USB"); + audio_service_transports.push_back("TCP_WIFI"); + std::vector<std::string> video_service_transports; + video_service_transports.push_back("IAP_USB"); + video_service_transports.push_back("IAP_CARPLAY"); + video_service_transports.push_back("AOA_USB"); + video_service_transports.push_back("TCP_WIFI"); + + // assume the device is IOS and is connected through USB Host Mode + std::string connection_type_string("IAP_USB_HOST_MODE"); + + // Core should specify WiFi for secondary transport, and should allow video + // services on both transports, and audio only on secondary transport + std::vector<std::string> expected_transport_strings; + expected_transport_strings.push_back("TCP_WIFI"); + std::vector<int32_t> expected_audio_service_transports; + expected_audio_service_transports.push_back(2); + std::vector<int32_t> expected_video_service_transports; + expected_video_service_transports.push_back(1); + expected_video_service_transports.push_back(2); + + // A TransportUpdateEvent is also issued after Start Service ACK. We don't + // check it in this test case. + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly(Return(false)); + + VerifySecondaryTransportParamsInStartSessionAck( + true, + secondary_transports_for_usb, + secondary_transports_for_bluetooth, + secondary_transports_for_wifi, + audio_service_transports, + video_service_transports, + connection_type_string, + expected_transport_strings, + expected_audio_service_transports, + expected_video_service_transports); +} + +TEST_F(ProtocolHandlerImplTest, + TransportEventUpdate_afterVersionNegotiation_TCPEnabled) { + TestAsyncWaiter waiter; + uint32_t times = 0; + + const uint8_t input_protocol_version = 5; + const uint32_t hash_id = 123456; + ProtocolPacket::ProtocolVersion full_version(5, 1, 0); + + const size_t maximum_rpc_payload_size = 1500; + EXPECT_CALL(protocol_handler_settings_mock, maximum_rpc_payload_size()) + .WillRepeatedly(Return(maximum_rpc_payload_size)); + InitProtocolHandlerImpl(0u, 0u); + + // TCP configuration setup + bool tcp_enabled = true; + char tcp_address[] = "192.168.1.1"; + int32_t tcp_port = 12345; + std::string tcp_port_str = "12345"; + protocol_handler_impl->set_tcp_config( + tcp_enabled, std::string(tcp_address), tcp_port_str); + + // configuration setup + std::vector<std::string> config_secondary_transports_for_usb; // empty + std::vector<std::string> config_secondary_transports_for_bluetooth; + config_secondary_transports_for_bluetooth.push_back("WiFi"); + std::vector<std::string> config_secondary_transports_for_wifi; // empty + std::vector<std::string> config_audio_service_transports; + config_audio_service_transports.push_back("IAP_USB"); + config_audio_service_transports.push_back("IAP_USB_HOST_MODE"); + config_audio_service_transports.push_back("IAP_USB_DEVICE_MODE"); + config_audio_service_transports.push_back("IAP_CARPLAY"); + config_audio_service_transports.push_back("AOA_USB"); + config_audio_service_transports.push_back("TCP_WIFI"); + std::vector<std::string> config_video_service_transports; + config_video_service_transports.push_back("IAP_USB"); + config_video_service_transports.push_back("IAP_USB_HOST_MODE"); + config_video_service_transports.push_back("IAP_USB_DEVICE_MODE"); + config_video_service_transports.push_back("IAP_CARPLAY"); + config_video_service_transports.push_back("AOA_USB"); + config_video_service_transports.push_back("TCP_WIFI"); + + EXPECT_CALL(protocol_handler_settings_mock, max_supported_protocol_version()) + .WillRepeatedly(Return(PROTOCOL_VERSION_5)); + EXPECT_CALL(protocol_handler_settings_mock, multiple_transports_enabled()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(protocol_handler_settings_mock, secondary_transports_for_usb()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_usb)); + EXPECT_CALL(protocol_handler_settings_mock, + secondary_transports_for_bluetooth()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_bluetooth)); + EXPECT_CALL(protocol_handler_settings_mock, secondary_transports_for_wifi()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_wifi)); + EXPECT_CALL(protocol_handler_settings_mock, audio_service_transports()) + .WillOnce(ReturnRef(config_audio_service_transports)); + EXPECT_CALL(protocol_handler_settings_mock, video_service_transports()) + .WillOnce(ReturnRef(config_video_service_transports)); + + // assume the device is iOS and is connected through iAP over Bluetooth + std::string connection_type_string("IAP_BLUETOOTH"); + + EXPECT_CALL(session_observer_mock, + TransportTypeProfileStringFromConnHandle(connection_id)) + .WillRepeatedly(Return(connection_type_string)); + + EXPECT_CALL( + transport_manager_mock, + SendMessageToDevice(ControlMessage( + FRAME_DATA_START_SERVICE_ACK, PROTECTION_OFF, connection_id, _))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly( + DoAll(SetArgReferee<2>(PROTOCOL_VERSION_5), Return(true))); + + BsonObject expected_obj; + bson_object_initialize_default(&expected_obj); + // IP address + bson_object_put_string( + &expected_obj, protocol_handler::strings::tcp_ip_address, tcp_address); + // TCP port number + bson_object_put_int32( + &expected_obj, protocol_handler::strings::tcp_port, tcp_port); + + std::vector<uint8_t> expected_param = + CreateVectorFromBsonObject(&expected_obj); + + bson_object_deinitialize(&expected_obj); + + EXPECT_CALL( + transport_manager_mock, + SendMessageToDevice(ControlMessage(FRAME_DATA_TRANSPORT_EVENT_UPDATE, + PROTECTION_OFF, + connection_id, + Eq(expected_param)))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + +#ifdef ENABLE_SECURITY + AddSecurityManager(); + + EXPECT_CALL(session_observer_mock, KeyFromPair(connection_id, session_id)) + .WillOnce(Return(connection_key)); + + EXPECT_CALL(session_observer_mock, GetSSLContext(connection_key, kRpc)) + .WillOnce(ReturnNull()); +#endif // ENABLE_SECURITY + + protocol_handler_impl->SendStartSessionAck(connection_id, + session_id, + input_protocol_version, + hash_id, + protocol_handler::SERVICE_TYPE_RPC, + false /* protection */, + full_version); + + EXPECT_TRUE(waiter.WaitFor(times, kAsyncExpectationsTimeout)); +} + +TEST_F(ProtocolHandlerImplTest, + TransportEventUpdate_afterVersionNegotiation_TCPDisabled) { + TestAsyncWaiter waiter; + uint32_t times = 0; + + const uint8_t input_protocol_version = 5; + const uint32_t hash_id = 123456; + ProtocolPacket::ProtocolVersion full_version(5, 1, 0); + + const size_t maximum_rpc_payload_size = 1500; + EXPECT_CALL(protocol_handler_settings_mock, maximum_rpc_payload_size()) + .WillRepeatedly(Return(maximum_rpc_payload_size)); + InitProtocolHandlerImpl(0u, 0u); + + // TCP configuration setup + bool tcp_enabled = false; + char tcp_address[] = "192.168.2.3"; + std::string tcp_port_str = "12345"; + protocol_handler_impl->set_tcp_config( + tcp_enabled, std::string(tcp_address), tcp_port_str); + + std::vector<std::string> config_secondary_transports_for_usb; // empty + std::vector<std::string> config_secondary_transports_for_bluetooth; + config_secondary_transports_for_bluetooth.push_back("WiFi"); + std::vector<std::string> config_secondary_transports_for_wifi; // empty + std::vector<std::string> config_audio_service_transports; + config_audio_service_transports.push_back("IAP_USB"); + config_audio_service_transports.push_back("IAP_USB_HOST_MODE"); + config_audio_service_transports.push_back("IAP_USB_DEVICE_MODE"); + config_audio_service_transports.push_back("IAP_CARPLAY"); + config_audio_service_transports.push_back("AOA_USB"); + config_audio_service_transports.push_back("TCP_WIFI"); + std::vector<std::string> config_video_service_transports; + config_video_service_transports.push_back("IAP_USB"); + config_video_service_transports.push_back("IAP_USB_HOST_MODE"); + config_video_service_transports.push_back("IAP_USB_DEVICE_MODE"); + config_video_service_transports.push_back("IAP_CARPLAY"); + config_video_service_transports.push_back("AOA_USB"); + config_video_service_transports.push_back("TCP_WIFI"); + + EXPECT_CALL(protocol_handler_settings_mock, max_supported_protocol_version()) + .WillRepeatedly(Return(PROTOCOL_VERSION_5)); + EXPECT_CALL(protocol_handler_settings_mock, multiple_transports_enabled()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(protocol_handler_settings_mock, secondary_transports_for_usb()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_usb)); + EXPECT_CALL(protocol_handler_settings_mock, + secondary_transports_for_bluetooth()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_bluetooth)); + EXPECT_CALL(protocol_handler_settings_mock, secondary_transports_for_wifi()) + .Times(AtLeast(0)) + .WillRepeatedly(ReturnRef(config_secondary_transports_for_wifi)); + EXPECT_CALL(protocol_handler_settings_mock, audio_service_transports()) + .WillOnce(ReturnRef(config_audio_service_transports)); + EXPECT_CALL(protocol_handler_settings_mock, video_service_transports()) + .WillOnce(ReturnRef(config_video_service_transports)); + + // assume the device is iOS and is connected through iAP over Bluetooth + std::string connection_type_string("IAP_BLUETOOTH"); + + EXPECT_CALL(session_observer_mock, + TransportTypeProfileStringFromConnHandle(connection_id)) + .WillRepeatedly(Return(connection_type_string)); + + EXPECT_CALL( + transport_manager_mock, + SendMessageToDevice(ControlMessage( + FRAME_DATA_START_SERVICE_ACK, PROTECTION_OFF, connection_id, _))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly( + DoAll(SetArgReferee<2>(PROTOCOL_VERSION_5), Return(true))); + + BsonObject expected_obj; + bson_object_initialize_default(&expected_obj); + // IP address + char empty_ip_address[] = ""; + bson_object_put_string(&expected_obj, + protocol_handler::strings::tcp_ip_address, + empty_ip_address); + // TCP port number should be omitted + + std::vector<uint8_t> expected_param = + CreateVectorFromBsonObject(&expected_obj); + + bson_object_deinitialize(&expected_obj); + + EXPECT_CALL( + transport_manager_mock, + SendMessageToDevice(ControlMessage(FRAME_DATA_TRANSPORT_EVENT_UPDATE, + PROTECTION_OFF, + connection_id, + Eq(expected_param)))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + +#ifdef ENABLE_SECURITY + AddSecurityManager(); + + EXPECT_CALL(session_observer_mock, KeyFromPair(connection_id, session_id)) + .WillOnce(Return(connection_key)); + + EXPECT_CALL(session_observer_mock, GetSSLContext(connection_key, kRpc)) + .WillOnce(ReturnNull()); +#endif // ENABLE_SECURITY + + protocol_handler_impl->SendStartSessionAck(connection_id, + session_id, + input_protocol_version, + hash_id, + protocol_handler::SERVICE_TYPE_RPC, + false /* protection */, + full_version); + + EXPECT_TRUE(waiter.WaitFor(times, kAsyncExpectationsTimeout)); +} + +TEST_F(ProtocolHandlerImplTest, + OnTransportConfigUpdated_TransportEventUpdate_TCPEnabled) { + using connection_handler::SessionConnectionMap; + using connection_handler::SessionTransports; + + TestAsyncWaiter waiter; + uint32_t times = 0; + + char tcp_address[] = "172.16.2.3"; + int32_t tcp_port = 23456; + std::string tcp_port_str = "23456"; + + transport_manager::transport_adapter::TransportConfig configs; + configs[transport_manager::transport_adapter::tc_enabled] = + std::string("true"); + configs[transport_manager::transport_adapter::tc_tcp_port] = tcp_port_str; + configs[transport_manager::transport_adapter::tc_tcp_ip_address] = + std::string(tcp_address); + + transport_manager::ConnectionUID device1_primary_connection_id = 100; + transport_manager::ConnectionUID device2_primary_connection_id = 101; + transport_manager::ConnectionUID device2_secondary_connection_id = 150; + + SessionTransports st1 = {device1_primary_connection_id, kDisabledSecondary}; + SessionTransports st2 = {device2_primary_connection_id, + device2_secondary_connection_id}; + session_connection_map_[0x11] = st1; + session_connection_map_[0x22] = st2; + + EXPECT_CALL(connection_handler_mock, session_connection_map()) + .WillOnce(Return(DataAccessor<SessionConnectionMap>( + session_connection_map_, session_connection_map_lock_ptr_))); + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly( + DoAll(SetArgReferee<2>(PROTOCOL_VERSION_5), Return(true))); + + BsonObject expected_obj; + bson_object_initialize_default(&expected_obj); + // IP address + bson_object_put_string( + &expected_obj, protocol_handler::strings::tcp_ip_address, tcp_address); + // TCP port number + bson_object_put_int32( + &expected_obj, protocol_handler::strings::tcp_port, tcp_port); + + std::vector<uint8_t> expected_param = + CreateVectorFromBsonObject(&expected_obj); + + bson_object_deinitialize(&expected_obj); + + // since device 1 doesn't support secondary transport feature, + // TransportEvetUpdate should be delivered only to device 2 + EXPECT_CALL( + transport_manager_mock, + SendMessageToDevice(ControlMessage(FRAME_DATA_TRANSPORT_EVENT_UPDATE, + PROTECTION_OFF, + device2_primary_connection_id, + Eq(expected_param)))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + + tm_listener->OnTransportConfigUpdated(configs); + + EXPECT_TRUE(waiter.WaitFor(times, kAsyncExpectationsTimeout)); +} + +TEST_F(ProtocolHandlerImplTest, + OnTransportConfigUpdated_TransportEventUpdate_TCPDisabled) { + using connection_handler::SessionConnectionMap; + using connection_handler::SessionTransports; + + TestAsyncWaiter waiter; + uint32_t times = 0; + + char tcp_address[] = "172.16.2.3"; + std::string tcp_port_str = "23456"; + + transport_manager::transport_adapter::TransportConfig configs; + configs[transport_manager::transport_adapter::tc_enabled] = + std::string("false"); + configs[transport_manager::transport_adapter::tc_tcp_port] = tcp_port_str; + configs[transport_manager::transport_adapter::tc_tcp_ip_address] = + std::string(tcp_address); + + transport_manager::ConnectionUID device1_primary_connection_id = 100; + transport_manager::ConnectionUID device1_secondary_connection_id = 150; + transport_manager::ConnectionUID device2_primary_connection_id = 101; + transport_manager::ConnectionUID device3_primary_connection_id = 102; + transport_manager::ConnectionUID device3_secondary_connection_id = 151; + + SessionTransports st1 = {device1_primary_connection_id, + device1_secondary_connection_id}; + SessionTransports st2 = {device2_primary_connection_id, kDisabledSecondary}; + SessionTransports st3 = {device3_primary_connection_id, + device3_secondary_connection_id}; + session_connection_map_[0x11] = st1; + session_connection_map_[0x22] = st2; + session_connection_map_[0x33] = st3; + + EXPECT_CALL(connection_handler_mock, session_connection_map()) + .WillOnce(Return(DataAccessor<SessionConnectionMap>( + session_connection_map_, session_connection_map_lock_ptr_))); + + EXPECT_CALL(session_observer_mock, ProtocolVersionUsed(_, _, _)) + .WillRepeatedly( + DoAll(SetArgReferee<2>(PROTOCOL_VERSION_5), Return(true))); + + BsonObject expected_obj; + bson_object_initialize_default(&expected_obj); + // IP address + char empty_ip_address[] = ""; + bson_object_put_string(&expected_obj, + protocol_handler::strings::tcp_ip_address, + empty_ip_address); + // TCP port number should be omitted + + std::vector<uint8_t> expected_param = + CreateVectorFromBsonObject(&expected_obj); + + bson_object_deinitialize(&expected_obj); + + // both device 1 and device 3 should receive TransportEventUpdate frames + EXPECT_CALL( + transport_manager_mock, + SendMessageToDevice(ControlMessage(FRAME_DATA_TRANSPORT_EVENT_UPDATE, + PROTECTION_OFF, + device1_primary_connection_id, + Eq(expected_param)))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + EXPECT_CALL( + transport_manager_mock, + SendMessageToDevice(ControlMessage(FRAME_DATA_TRANSPORT_EVENT_UPDATE, + PROTECTION_OFF, + device3_primary_connection_id, + Eq(expected_param)))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + + tm_listener->OnTransportConfigUpdated(configs); + + EXPECT_TRUE(waiter.WaitFor(times, kAsyncExpectationsTimeout)); +} + +TEST_F(ProtocolHandlerImplTest, RegisterSecondaryTransport_SUCCESS) { + AddConnection(); + + TestAsyncWaiter waiter; + uint32_t times = 0; + + transport_manager::ConnectionUID primary_connection_id = 123; + + EXPECT_CALL(session_observer_mock, + ProtocolVersionUsed(primary_connection_id, _, _)) + .WillRepeatedly( + DoAll(SetArgReferee<2>(PROTOCOL_VERSION_5), Return(true))); + + EXPECT_CALL(connection_handler_mock, + OnSecondaryTransportStarted(_, connection_id, session_id)) + .WillOnce(DoAll(SetArgReferee<0>(primary_connection_id), Return(true))); + + EXPECT_CALL(transport_manager_mock, + SendMessageToDevice( + ControlMessage(FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_ACK, + PROTECTION_OFF, + connection_id, + _))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + + SendControlMessage(PROTECTION_OFF, + kControl, + session_id, + FRAME_DATA_REGISTER_SECONDARY_TRANSPORT, + PROTOCOL_VERSION_5); + + EXPECT_TRUE(waiter.WaitFor(times, kAsyncExpectationsTimeout)); +} + +TEST_F(ProtocolHandlerImplTest, RegisterSecondaryTransport_FAILURE) { + AddConnection(); + + TestAsyncWaiter waiter; + uint32_t times = 0; + + transport_manager::ConnectionUID primary_connection_id = 123; + + EXPECT_CALL(session_observer_mock, + ProtocolVersionUsed(primary_connection_id, _, _)) + .WillRepeatedly( + DoAll(SetArgReferee<2>(PROTOCOL_VERSION_5), Return(true))); + + // check the behavior when OnSecondaryTransportStarted() returns false + EXPECT_CALL(connection_handler_mock, + OnSecondaryTransportStarted(_, connection_id, session_id)) + .WillOnce(DoAll(SetArgReferee<0>(primary_connection_id), Return(false))); + + EXPECT_CALL(transport_manager_mock, + SendMessageToDevice( + ControlMessage(FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_NACK, + PROTECTION_OFF, + connection_id, + _))) + .WillOnce(DoAll(NotifyTestAsyncWaiter(&waiter), Return(E_SUCCESS))); + times++; + + SendControlMessage(PROTECTION_OFF, + kControl, + session_id, + FRAME_DATA_REGISTER_SECONDARY_TRANSPORT, + PROTOCOL_VERSION_5); + + EXPECT_TRUE(waiter.WaitFor(times, kAsyncExpectationsTimeout)); +} + TEST_F(ProtocolHandlerImplTest, DISABLED_FloodVerification) { const size_t period_msec = 10000; const size_t max_messages = 1000; @@ -1990,7 +3152,8 @@ TEST_F(ProtocolHandlerImplTest, times++; // Act - protocol_handler_impl->SendEndService(connection_id, session_id, kControl); + protocol_handler_impl->SendEndService( + connection_id, connection_id, session_id, kControl); EXPECT_TRUE(waiter->WaitFor(times, kAsyncExpectationsTimeout)); } diff --git a/src/components/protocol_handler/test/protocol_header_validator_test.cc b/src/components/protocol_handler/test/protocol_header_validator_test.cc index e42ba96251..5554bfd280 100644 --- a/src/components/protocol_handler/test/protocol_header_validator_test.cc +++ b/src/components/protocol_handler/test/protocol_header_validator_test.cc @@ -230,12 +230,12 @@ TEST_F(ProtocolHeaderValidatorTest, Malformed_FrameType) { } } -// For Control frames Frame info value shall be from 0x00 to 0x06 or 0xFE(Data -// Ack), 0xFF(HB Ack) +// For Control frames Frame info value shall be from 0x00 to 0x09 or 0xFD +// (Transport Update Event), 0xFE(Data Ack), 0xFF(HB Ack) TEST_F(ProtocolHeaderValidatorTest, Malformed_ControlFrame) { std::vector<uint8_t> malformed_frame_data; - for (uint8_t frame_type = FRAME_DATA_END_SERVICE_NACK + 1; - frame_type < FRAME_DATA_SERVICE_DATA_ACK; + for (uint8_t frame_type = FRAME_DATA_REGISTER_SECONDARY_TRANSPORT_NACK + 1; + frame_type < FRAME_DATA_TRANSPORT_EVENT_UPDATE; ++frame_type) { malformed_frame_data.push_back(frame_type); } diff --git a/src/components/transport_manager/CMakeLists.txt b/src/components/transport_manager/CMakeLists.txt index 4fa224393d..2f734b3a05 100644 --- a/src/components/transport_manager/CMakeLists.txt +++ b/src/components/transport_manager/CMakeLists.txt @@ -43,6 +43,12 @@ include_directories ( ${LOG4CXX_INCLUDE_DIRECTORY} ) +if (CMAKE_SYSTEM_NAME STREQUAL "QNX") + include_directories ("${CMAKE_CURRENT_SOURCE_DIR}/include/transport_manager/tcp/platform_specific/qnx") +elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") + include_directories ("${CMAKE_CURRENT_SOURCE_DIR}/include/transport_manager/tcp/platform_specific/linux") +endif () + set(PATHS ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src @@ -96,8 +102,23 @@ if(NOT BUILD_TESTS) ) endif() +# exclude platform-dependent files before running collect_sources +list(APPEND EXCLUDE_PATHS + ${COMPONENTS_DIR}/transport_manager/include/transport_manager/tcp/platform_specific + ${COMPONENTS_DIR}/transport_manager/src/tcp/platform_specific +) + collect_sources(SOURCES "${PATHS}" "${EXCLUDE_PATHS}") +# then add platform-dependent files later +set(PLATFORM_DEPENDENT_SOURCES) +if (CMAKE_SYSTEM_NAME STREQUAL "QNX") + collect_sources(PLATFORM_DEPENDENT_SOURCES "${COMPONENTS_DIR}/transport_manager/src/tcp/platform_specific/qnx") +elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") + collect_sources(PLATFORM_DEPENDENT_SOURCES "${COMPONENTS_DIR}/transport_manager/src/tcp/platform_specific/linux") +endif () +list(APPEND SOURCES ${PLATFORM_DEPENDENT_SOURCES}) + add_library("TransportManager" ${SOURCES}) target_link_libraries("TransportManager" ${LIBRARIES}) diff --git a/src/components/transport_manager/include/transport_manager/tcp/network_interface_listener.h b/src/components/transport_manager/include/transport_manager/tcp/network_interface_listener.h new file mode 100644 index 0000000000..91ddb3839c --- /dev/null +++ b/src/components/transport_manager/include/transport_manager/tcp/network_interface_listener.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the names of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_NETWORK_INTERFACE_LISTENER_H_ +#define SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_NETWORK_INTERFACE_LISTENER_H_ + +namespace transport_manager { +namespace transport_adapter { + +/** + * @brief Listener to detect various events on network interfaces + */ +class NetworkInterfaceListener { + public: + /** + * @brief Destructor + */ + virtual ~NetworkInterfaceListener() {} + + /** + * @brief Initialize this listener + */ + virtual bool Init() = 0; + + /** + * @brief Deinitialize this listener + */ + virtual void Deinit() = 0; + + /** + * @brief Start this listener + */ + virtual bool Start() = 0; + + /** + * @brief Stop this listener + */ + virtual bool Stop() = 0; +}; + +} // namespace transport_adapter +} // namespace transport_manager + +#endif // SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_NETWORK_INTERFACE_LISTENER_H_ diff --git a/src/components/transport_manager/include/transport_manager/tcp/network_interface_listener_impl.h b/src/components/transport_manager/include/transport_manager/tcp/network_interface_listener_impl.h new file mode 100644 index 0000000000..11a9be05ed --- /dev/null +++ b/src/components/transport_manager/include/transport_manager/tcp/network_interface_listener_impl.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the names of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_NETWORK_INTERFACE_LISTENER_IMPL_H_ +#define SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_NETWORK_INTERFACE_LISTENER_IMPL_H_ + +#include <string> +#include <memory> + +#include "utils/macro.h" +#include "transport_manager/tcp/network_interface_listener.h" + +namespace transport_manager { +namespace transport_adapter { + +class TcpClientListener; + +/** + * @brief Listener to detect various events on network interfaces + */ +class NetworkInterfaceListenerImpl : public NetworkInterfaceListener { + public: + /** + * @brief Constructor + * + * @param tcp_client_listener an instance of TcpClientListener which receives + * status updates + * @param designated_interface if we want to listen only on a specific + * network interface, specify its name + */ + NetworkInterfaceListenerImpl(TcpClientListener* tcp_client_listener, + const std::string designated_interface); + + /** + * @brief Destructor + */ + virtual ~NetworkInterfaceListenerImpl(); + + /** + * @brief Initialize this listener + */ + bool Init() OVERRIDE; + + /** + * @brief Deinitialize this listener + */ + void Deinit() OVERRIDE; + + /** + * @brief Start this listener + */ + bool Start() OVERRIDE; + + /** + * @brief Stop this listener + */ + bool Stop() OVERRIDE; + + private: + std::unique_ptr<NetworkInterfaceListener> platform_specific_impl_; +}; + +} // namespace transport_adapter +} // namespace transport_manager + +#endif // SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_NETWORK_INTERFACE_LISTENER_IMPL_H_ diff --git a/src/components/transport_manager/include/transport_manager/tcp/platform_specific/linux/platform_specific_network_interface_listener_impl.h b/src/components/transport_manager/include/transport_manager/tcp/platform_specific/linux/platform_specific_network_interface_listener_impl.h new file mode 100644 index 0000000000..8a2279003a --- /dev/null +++ b/src/components/transport_manager/include/transport_manager/tcp/platform_specific/linux/platform_specific_network_interface_listener_impl.h @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the names of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_PLATFORM_SPECIFIC_LINUX_PLATFORM_SPECIFIC_NETWORK_INTERFACE_LISTENER_H_ +#define SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_PLATFORM_SPECIFIC_LINUX_PLATFORM_SPECIFIC_NETWORK_INTERFACE_LISTENER_H_ + +#include <map> +#include <string> +#include <vector> + +#include <netinet/in.h> +#include "transport_manager/tcp/network_interface_listener.h" +#include "utils/macro.h" +#include "utils/threads/thread_delegate.h" + +struct ifaddrmsg; + +namespace transport_manager { +namespace transport_adapter { + +class TcpClientListener; + +/** + * @brief Struct to keep network interface's status flags and IP addresses + */ +class InterfaceStatus { + public: + InterfaceStatus() : flags_(0), has_ipv4_(false), has_ipv6_(false) {} + ~InterfaceStatus() {} + + bool IsAvailable() const; + bool IsLoopback() const; + // only for debugging output + unsigned int GetFlags() const { + return flags_; + } + + bool HasIPAddress() const; + std::string GetIPv4Address() const; + std::string GetIPv6Address() const; + + void SetFlags(unsigned int flags) { + flags_ = flags; + } + + // specify NULL to remove existing address + void SetIPv4Address(struct in_addr* addr); + void SetIPv6Address(struct in6_addr* addr); + + private: + unsigned int flags_; + bool has_ipv4_; + bool has_ipv6_; + struct in_addr ipv4_address_; + struct in6_addr ipv6_address_; +}; + +typedef std::map<std::string, InterfaceStatus> InterfaceStatusTable; + +/** + * @brief Listener to detect various events on network interfaces + */ +class PlatformSpecificNetworkInterfaceListener + : public NetworkInterfaceListener { + public: + /** + * @brief Constructor + * + * @param tcp_client_listener an instance of TcpClientListener which receives + * status updates + * @param designated_interface if we want to listen only on a specific + * network interface, specify its name + */ + PlatformSpecificNetworkInterfaceListener( + TcpClientListener* tcp_client_listener, + const std::string designated_interface = ""); + + /** + * @brief Destructor + */ + virtual ~PlatformSpecificNetworkInterfaceListener(); + + /** + * @brief Initialize this listener + */ + bool Init() OVERRIDE; + + /** + * @brief Deinitialize this listener + */ + void Deinit() OVERRIDE; + + /** + * @brief Start this listener + */ + bool Start() OVERRIDE; + + /** + * @brief Stop this listener + */ + bool Stop() OVERRIDE; + +#ifdef BUILD_TESTS + void SetTesting(bool enabled) { + testing_ = enabled; + } + + int GetSocket() const { + return socket_; + } + + threads::Thread* GetThread() const { + return thread_; + } + + void OverwriteStatusTable(const InterfaceStatusTable dummy_table) { + status_table_ = dummy_table; + } + + void testCallNotifyIPAddresses() { + NotifyIPAddresses(); + } + + const std::string& GetSelectedInterfaceName() const { + return selected_interface_; + } +#endif // BUILD_TESTS + + private: + // Struct to hold an event on a network interface. + // The event can be either an update on flags or an update on IP address. + struct EventParam { + unsigned int if_index; + unsigned int flags; + struct sockaddr_storage address; + + EventParam(int interface_index, unsigned int interface_flags = 0) + : if_index(interface_index), flags(interface_flags) {} + }; + + // parent class which we will notify the events to + TcpClientListener* tcp_client_listener_; + // if configured, NetworkInterfaceListener will always look into the IP + // addresses of this interface + const std::string designated_interface_; + + // a map to store status of each interface + InterfaceStatusTable status_table_; + // this is the name of the interface we are currently focusing on + std::string selected_interface_; + // previous IP addresses that we have notified + std::string notified_ipv4_addr_; + std::string notified_ipv6_addr_; + + int socket_; + int pipe_fds_[2]; + threads::Thread* thread_; + +#ifdef BUILD_TESTS + bool testing_; +#endif + + void Loop(); + bool StopLoop(); + + // reset status_table_ by fetching current status of each interface + bool InitializeStatus(); + // update status_table_ by applying the events + bool UpdateStatus(uint16_t type, std::vector<EventParam>& params); + // update notified_ipv4_addr_ and notified_ipv6_addr_ then notify the parent + // class of the change if necessary + void NotifyIPAddresses(); + // Select an appropriate network interface that we will get IP addresses. Also + // update selected_interface_. + const std::string SelectInterface(); + // convert ifaddrmsg to a list of EventParam structs + std::vector<EventParam> ParseIFAddrMessage(struct ifaddrmsg* message, + unsigned int size); + // for debugging + void DumpTable() const; + + class ListenerThreadDelegate : public threads::ThreadDelegate { + public: + explicit ListenerThreadDelegate( + PlatformSpecificNetworkInterfaceListener* parent); + virtual void threadMain(); + void exitThreadMain(); + + private: + PlatformSpecificNetworkInterfaceListener* parent_; + }; + + DISALLOW_COPY_AND_ASSIGN(PlatformSpecificNetworkInterfaceListener); +}; + +} // namespace transport_adapter +} // namespace transport_manager + +#endif // SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_PLATFORM_SPECIFIC_LINUX_PLATFORM_SPECIFIC_NETWORK_INTERFACE_LISTENER_H_ diff --git a/src/components/transport_manager/include/transport_manager/tcp/platform_specific/qnx/platform_specific_network_interface_listener_impl.h b/src/components/transport_manager/include/transport_manager/tcp/platform_specific/qnx/platform_specific_network_interface_listener_impl.h new file mode 100644 index 0000000000..c5982853dc --- /dev/null +++ b/src/components/transport_manager/include/transport_manager/tcp/platform_specific/qnx/platform_specific_network_interface_listener_impl.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the names of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_PLATFORM_SPECIFIC_QNX_PLATFORM_SPECIFIC_NETWORK_INTERFACE_LISTENER_H_ +#define SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_PLATFORM_SPECIFIC_QNX_PLATFORM_SPECIFIC_NETWORK_INTERFACE_LISTENER_H_ + +#include <map> +#include <string> +#include <vector> + +#include <netinet/in.h> +#include "transport_manager/tcp/network_interface_listener.h" +#include "utils/macro.h" +#include "utils/threads/thread_delegate.h" + +class Thread; +struct ifaddrmsg; + +namespace transport_manager { +namespace transport_adapter { + +class TcpClientListener; + +/** + * @brief Listener to detect various events on network interfaces + */ +class PlatformSpecificNetworkInterfaceListener + : public NetworkInterfaceListener { + public: + /** + * @brief Constructor + * + * @param tcp_client_listener an instance of TcpClientListener which receives + * status updates + * @param designated_interface if we want to listen only on a specific + * network interface, specify its name + */ + PlatformSpecificNetworkInterfaceListener( + TcpClientListener* tcp_client_listener, + const std::string designated_interface = ""); + + /** + * @brief Destructor + */ + virtual ~PlatformSpecificNetworkInterfaceListener(); + + /** + * @brief Initialize this listener + */ + bool Init() OVERRIDE; + + /** + * @brief Deinitialize this listener + */ + void Deinit() OVERRIDE; + + /** + * @brief Start this listener + */ + bool Start() OVERRIDE; + + /** + * @brief Stop this listener + */ + bool Stop() OVERRIDE; + + private: +}; + +} // namespace transport_adapter +} // namespace transport_manager + +#endif // SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_PLATFORM_SPECIFIC_QNX_PLATFORM_SPECIFIC_NETWORK_INTERFACE_LISTENER_H_ diff --git a/src/components/transport_manager/include/transport_manager/tcp/tcp_client_listener.h b/src/components/transport_manager/include/transport_manager/tcp/tcp_client_listener.h index 2640049ecc..dae66cd30e 100644 --- a/src/components/transport_manager/include/transport_manager/tcp/tcp_client_listener.h +++ b/src/components/transport_manager/include/transport_manager/tcp/tcp_client_listener.h @@ -5,6 +5,9 @@ * Copyright (c) 2013, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -16,7 +19,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -36,15 +39,18 @@ #ifndef SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_TCP_CLIENT_LISTENER_H_ #define SRC_COMPONENTS_TRANSPORT_MANAGER_INCLUDE_TRANSPORT_MANAGER_TCP_TCP_CLIENT_LISTENER_H_ +#include "utils/lock.h" #include "utils/threads/thread_delegate.h" #include "transport_manager/transport_adapter/client_connection_listener.h" class Thread; +struct in_addr; namespace transport_manager { namespace transport_adapter { class TransportAdapterController; +class NetworkInterfaceListener; /** * @brief Listener of device adapter that use TCP transport. @@ -57,11 +63,15 @@ class TcpClientListener : public ClientConnectionListener { * @param controller Pointer to the device adapter controller. * @param port Port No. * @param enable_keepalive If true enables TCP keepalive on accepted + * @param designated_interface Specify the name of the network interface to + *listen on. If empty, then this process will listen on all network + *interfaces. *connections */ TcpClientListener(TransportAdapterController* controller, uint16_t port, - bool enable_keepalive); + bool enable_keepalive, + const std::string designated_interface = ""); /** * @brief Destructor. @@ -101,7 +111,18 @@ class TcpClientListener : public ClientConnectionListener { */ virtual TransportAdapter::Error StopListening(); + /** + * @brief Called from NetworkInterfaceListener when IP address of the network + * interface is changed. + */ + virtual void OnIPAddressUpdated(const std::string ipv4_addr, + const std::string ipv6_addr); + #ifdef BUILD_TESTS + void set_network_interface_listener(NetworkInterfaceListener* listener) { + interface_listener_ = listener; + } + uint16_t port() const { return port_; } @@ -113,19 +134,47 @@ class TcpClientListener : public ClientConnectionListener { threads::Thread* thread() const { return thread_; } + + static void set_testing(bool enabled) { + testing_ = enabled; + } #endif // BUILD_TESTS private: const uint16_t port_; const bool enable_keepalive_; TransportAdapterController* controller_; + bool initialized_; + bool started_; threads::Thread* thread_; int socket_; bool thread_stop_requested_; + int pipe_fds_[2]; + NetworkInterfaceListener* interface_listener_; + const std::string designated_interface_; + std::string current_ip_address_; + sync_primitives::Lock start_stop_lock_; + +#ifdef BUILD_TESTS + static bool testing_; +#endif // BUILD_TESTS void Loop(); void StopLoop(); + TransportAdapter::Error StartListeningThread(); + TransportAdapter::Error StopListeningThread(); + + bool StartOnNetworkInterface(); + bool StopOnNetworkInterface(); + bool IsListeningOnSpecificInterface() const; + + static int CreateIPv4ServerSocket(uint16_t port, + const std::string interface_name = ""); + static void DestroyServerSocket(int sock); + static bool GetIPv4Address(const std::string interface_name, + struct in_addr* ip_address); + class ListeningThreadDelegate : public threads::ThreadDelegate { public: explicit ListeningThreadDelegate(TcpClientListener* parent); diff --git a/src/components/transport_manager/include/transport_manager/tcp/tcp_transport_adapter.h b/src/components/transport_manager/include/transport_manager/tcp/tcp_transport_adapter.h index 5431b4455d..647e14dbfa 100644 --- a/src/components/transport_manager/include/transport_manager/tcp/tcp_transport_adapter.h +++ b/src/components/transport_manager/include/transport_manager/tcp/tcp_transport_adapter.h @@ -5,6 +5,9 @@ * Copyright (c) 2013, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -16,7 +19,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -58,6 +61,18 @@ class TcpTransportAdapter : public TransportAdapterImpl { */ virtual ~TcpTransportAdapter(); + /** + * @brief Notification that transport's configuration is updated + * + * @param new_config The new configuration of the transport + */ + void TransportConfigUpdated(const TransportConfig& new_config) OVERRIDE; + + /** + * @brief Returns the transport's configuration information + */ + virtual TransportConfig GetTransportConfiguration() const OVERRIDE; + protected: /** * @brief Return type of device. @@ -77,6 +92,19 @@ class TcpTransportAdapter : public TransportAdapterImpl { * @return True on success false otherwise */ virtual bool Restore(); + + private: + /** + * @brief Keeps transport specific configuration + * + * TCP transport uses following information: + * - "enabled": whether the transport is currently enabled or not. Value can + * be "true" or "false". + * - "tcp_ip_address": string representation of IP address (either IPv4 or + * IPv6) + * - "tcp_port": string representation of TCP port number (e.g. "12345") + */ + TransportConfig transport_config_; }; } // namespace transport_adapter diff --git a/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_controller.h b/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_controller.h index 0b38f82637..a5a5eb4bdf 100644 --- a/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_controller.h +++ b/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_controller.h @@ -2,6 +2,9 @@ * Copyright (c) 2014, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -204,6 +207,13 @@ class TransportAdapterController { const ApplicationHandle& app_handle, ::protocol_handler::RawMessagePtr message, const DataSendError&) = 0; + + /** + * @brief Notification that transport's configuration is updated + * + * @param new_config The new configuration of the transport + */ + virtual void TransportConfigUpdated(const TransportConfig& new_config) = 0; }; } // namespace transport_adapter diff --git a/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_impl.h b/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_impl.h index 2b1ada79ad..b503e609f1 100644 --- a/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_impl.h +++ b/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_impl.h @@ -2,6 +2,9 @@ * Copyright (c) 2016, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -400,6 +403,13 @@ class TransportAdapterImpl : public TransportAdapter, const DataSendError& error) OVERRIDE; /** + * @brief Notification that transport's configuration is updated + * + * @param new_config The new configuration of the transport + */ + void TransportConfigUpdated(const TransportConfig& new_config) OVERRIDE; + + /** * @brief DoTransportSwitch notifies listeners of transport adapter events * that transport switching is requested by system */ @@ -420,6 +430,14 @@ class TransportAdapterImpl : public TransportAdapter, SwitchableDevices GetSwitchableDevices() const OVERRIDE; /** + * @brief Returns the transport's configuration information + */ + virtual TransportConfig GetTransportConfiguration() const OVERRIDE { + // default is empty + return TransportConfig(); + } + + /** * @brief Return name of device. * * @param device_id Device unique identifier. diff --git a/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_listener.h b/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_listener.h index 424fa53dea..07938224d6 100644 --- a/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_listener.h +++ b/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_listener.h @@ -2,6 +2,9 @@ * Copyright (c) 2014, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -277,6 +280,15 @@ class TransportAdapterListener { */ virtual void OnTransportSwitchRequested( const TransportAdapter* transport_adapter) = 0; + + /** + * @brief Notification that the transport's specific configuration has been + * updated. + * + * @param transport_adapter pointer to the transport adapter + */ + virtual void OnTransportConfigUpdated( + const TransportAdapter* transport_adapter) = 0; }; } // transport_adapter namespace diff --git a/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_listener_impl.h b/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_listener_impl.h index 8a8031c3cf..f1576d1dc3 100644 --- a/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_listener_impl.h +++ b/src/components/transport_manager/include/transport_manager/transport_adapter/transport_adapter_listener_impl.h @@ -2,6 +2,9 @@ * Copyright (c) 2014, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -272,6 +275,15 @@ class TransportAdapterListenerImpl */ void OnTransportSwitchRequested(const TransportAdapter* adapter) OVERRIDE; + /** + * @brief Notification that the transport's specific configuration has been + * updated. + * + * @param transport_adapter pointer to the transport adapter + */ + void OnTransportConfigUpdated( + const transport_adapter::TransportAdapter* adapter) OVERRIDE; + private: TransportManager* transport_manager_; TransportAdapter* transport_adapter_; diff --git a/src/components/transport_manager/src/tcp/network_interface_listener_impl.cc b/src/components/transport_manager/src/tcp/network_interface_listener_impl.cc new file mode 100644 index 0000000000..e362ee8a73 --- /dev/null +++ b/src/components/transport_manager/src/tcp/network_interface_listener_impl.cc @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the names of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "transport_manager/tcp/network_interface_listener_impl.h" +#include "platform_specific_network_interface_listener_impl.h" + +namespace transport_manager { +namespace transport_adapter { + +CREATE_LOGGERPTR_GLOBAL(logger_, "TransportManager") + +NetworkInterfaceListenerImpl::NetworkInterfaceListenerImpl( + TcpClientListener* tcp_client_listener, + const std::string designated_interface) + : platform_specific_impl_(new PlatformSpecificNetworkInterfaceListener( + tcp_client_listener, designated_interface)) { + LOG4CXX_AUTO_TRACE(logger_); +} + +NetworkInterfaceListenerImpl::~NetworkInterfaceListenerImpl() { + LOG4CXX_AUTO_TRACE(logger_); +} + +bool NetworkInterfaceListenerImpl::Init() { + LOG4CXX_AUTO_TRACE(logger_); + return platform_specific_impl_->Init(); +} + +void NetworkInterfaceListenerImpl::Deinit() { + LOG4CXX_AUTO_TRACE(logger_); + platform_specific_impl_->Deinit(); +} + +bool NetworkInterfaceListenerImpl::Start() { + LOG4CXX_AUTO_TRACE(logger_); + return platform_specific_impl_->Start(); +} + +bool NetworkInterfaceListenerImpl::Stop() { + LOG4CXX_AUTO_TRACE(logger_); + return platform_specific_impl_->Stop(); +} + +} // namespace transport_adapter +} // namespace transport_manager diff --git a/src/components/transport_manager/src/tcp/platform_specific/linux/platform_specific_network_interface_listener.cc b/src/components/transport_manager/src/tcp/platform_specific/linux/platform_specific_network_interface_listener.cc new file mode 100644 index 0000000000..29e55b97dd --- /dev/null +++ b/src/components/transport_manager/src/tcp/platform_specific/linux/platform_specific_network_interface_listener.cc @@ -0,0 +1,704 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the names of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "transport_manager/tcp/platform_specific/linux/platform_specific_network_interface_listener_impl.h" + +#include <arpa/inet.h> +#include <asm/types.h> +#include <errno.h> +#include <fcntl.h> +#include <ifaddrs.h> +#include <unistd.h> +#include <net/if.h> +#include <sys/types.h> +#include <sys/select.h> +#include <sys/socket.h> + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +#include "transport_manager/tcp/tcp_client_listener.h" +#include "utils/logger.h" +#include "utils/threads/thread.h" + +namespace transport_manager { +namespace transport_adapter { + +CREATE_LOGGERPTR_GLOBAL(logger_, "TransportManager") + +static std::string GetInterfaceName(unsigned int if_index); +static bool SetNonblocking(int s); + +bool InterfaceStatus::IsAvailable() const { + // check if the interface is UP and RUNNING + return ((flags_ & IFF_UP) > 0) && ((flags_ & IFF_RUNNING) > 0); +} + +bool InterfaceStatus::IsLoopback() const { + return flags_ & IFF_LOOPBACK; +} + +bool InterfaceStatus::HasIPAddress() const { + return has_ipv4_ || has_ipv6_; +} + +std::string InterfaceStatus::GetIPv4Address() const { + char buf[INET_ADDRSTRLEN] = ""; + if (has_ipv4_ && IsAvailable()) { + inet_ntop(AF_INET, &ipv4_address_, buf, sizeof(buf)); + } + return std::string(buf); +} + +std::string InterfaceStatus::GetIPv6Address() const { + char buf[INET6_ADDRSTRLEN] = ""; + if (has_ipv6_ && IsAvailable()) { + inet_ntop(AF_INET6, &ipv6_address_, buf, sizeof(buf)); + } + return std::string(buf); +} + +void InterfaceStatus::SetIPv4Address(struct in_addr* addr) { + if (addr == NULL) { + has_ipv4_ = false; + } else { + ipv4_address_ = *addr; + has_ipv4_ = true; + } +} + +void InterfaceStatus::SetIPv6Address(struct in6_addr* addr) { + if (addr == NULL) { + has_ipv6_ = false; + } else { + ipv6_address_ = *addr; + has_ipv6_ = true; + } +} + +PlatformSpecificNetworkInterfaceListener:: + PlatformSpecificNetworkInterfaceListener( + TcpClientListener* tcp_client_listener, + const std::string designated_interface) + : tcp_client_listener_(tcp_client_listener) + , designated_interface_(designated_interface) + , selected_interface_("") + , notified_ipv4_addr_("") + , notified_ipv6_addr_("") + , socket_(-1) +#ifdef BUILD_TESTS + , testing_(false) +#endif // BUILD_TESTS +{ + pipe_fds_[0] = pipe_fds_[1] = -1; + thread_ = threads::CreateThread("PlatformSpecificNetworkInterfaceListener", + new ListenerThreadDelegate(this)); +} + +PlatformSpecificNetworkInterfaceListener:: + ~PlatformSpecificNetworkInterfaceListener() { + LOG4CXX_AUTO_TRACE(logger_); + + Stop(); + Deinit(); + + delete thread_->delegate(); + threads::DeleteThread(thread_); +} + +bool PlatformSpecificNetworkInterfaceListener::Init() { + LOG4CXX_AUTO_TRACE(logger_); + + if (socket_ >= 0) { + LOG4CXX_WARN(logger_, "Network interface listener is already initialized"); + return false; + } + + socket_ = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (socket_ == -1) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to create netlink socket"); + return false; + } + + if (!SetNonblocking(socket_)) { + LOG4CXX_WARN(logger_, "Failed to configure netlink socket to non-blocking"); + } + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_pad = 0; + addr.nl_pid = 0; + addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; + + if (bind(socket_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to bind netlink socket"); + close(socket_); + socket_ = -1; + return false; + } + + if (pipe(pipe_fds_) != 0) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to create internal pipe"); + close(socket_); + socket_ = -1; + return false; + } + + if (!SetNonblocking(pipe_fds_[0])) { + LOG4CXX_WARN(logger_, "Failed to configure pipe to non-blocking"); + } + + return true; +} + +void PlatformSpecificNetworkInterfaceListener::Deinit() { + LOG4CXX_AUTO_TRACE(logger_); + + if (socket_ >= 0) { + close(socket_); + socket_ = -1; + } + if (pipe_fds_[1] >= 0) { + close(pipe_fds_[1]); + pipe_fds_[1] = -1; + } + if (pipe_fds_[0] >= 0) { + close(pipe_fds_[0]); + pipe_fds_[0] = -1; + } +} + +bool PlatformSpecificNetworkInterfaceListener::Start() { + LOG4CXX_AUTO_TRACE(logger_); + + if (socket_ < 0) { + LOG4CXX_WARN(logger_, "Interface listener is not initialized"); + return false; + } + + if (thread_->is_running()) { + LOG4CXX_WARN(logger_, "Interface listener is already started"); + return false; + } + + if (!thread_->start()) { + LOG4CXX_ERROR(logger_, "Failed to start interface listener"); + return false; + } + + LOG4CXX_INFO(logger_, "Network interface listener started"); + return true; +} + +bool PlatformSpecificNetworkInterfaceListener::Stop() { + LOG4CXX_AUTO_TRACE(logger_); + + if (!thread_->is_running()) { + LOG4CXX_DEBUG(logger_, "interface listener is not running"); + return false; + } + + thread_->join(); + + LOG4CXX_INFO(logger_, "Network interface listener stopped"); + return true; +} + +void PlatformSpecificNetworkInterfaceListener::Loop() { + LOG4CXX_AUTO_TRACE(logger_); + + // Initialize status_table_ by acquiring a list of interfaces and their + // current statuses. Also we will notify an event to the listener if IP + // address is already available. + InitializeStatus(); + NotifyIPAddresses(); + + // I am not sure required buffer size for netlink data structures. Most of + // implementation I found online uses 4096 so I followed them. + char buf[4096]; + fd_set rfds; + + while (1) { + FD_ZERO(&rfds); + FD_SET(socket_, &rfds); + FD_SET(pipe_fds_[0], &rfds); + int nfds = socket_ > pipe_fds_[0] ? socket_ : pipe_fds_[0]; + + // wait for some data from netlink socket (socket_) and our internal pipe + int ret = select(nfds + 1, &rfds, NULL, NULL, NULL); + if (ret < 0) { + if (errno == EINTR) { + continue; + } else { + LOG4CXX_WARN(logger_, + "select failed for netlink. Aborting interface listener."); + break; + } + } + + // Received data from internal pipe, indicating StopLoop() is called. + // We'll break the while() loop and eventually exit this thread. + if (FD_ISSET(pipe_fds_[0], &rfds)) { + ret = read(pipe_fds_[0], buf, sizeof(buf)); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { + LOG4CXX_WARN( + logger_, + "Failed to read from pipe. Aborting interface listener."); + break; + } + } else if (ret == 0) { + LOG4CXX_WARN(logger_, + "Pipe disconnected. Aborting interface listener."); + break; + } else { + LOG4CXX_DEBUG(logger_, "received terminating event through pipe"); + break; + } + } + +#ifdef BUILD_TESTS + if (testing_) { // don't enable events from network interface while testing + continue; + } +#endif // BUILD_TESTS + + // received data from netlink socket + if (FD_ISSET(socket_, &rfds)) { + ret = recv(socket_, buf, sizeof(buf), 0); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { + LOG4CXX_WARN(logger_, + "Failed to read from netlink socket. Aborting interface " + "listener."); + break; + } + } else if (ret == 0) { + LOG4CXX_WARN( + logger_, + "Netlink socket disconnected. Aborting interface listener."); + break; + } else { + struct nlmsghdr* header = reinterpret_cast<struct nlmsghdr*>(buf); + int len = ret; + + // Parse the stream. We may receive multiple (header + data) pairs at a + // time so we use for-loop to go through. + for (; NLMSG_OK(header, len); header = NLMSG_NEXT(header, len)) { + if (header->nlmsg_type == NLMSG_ERROR) { + LOG4CXX_WARN(logger_, "received error event from netlink"); + break; + } + + std::vector<EventParam> params; + + if (header->nlmsg_type == RTM_NEWLINK || + header->nlmsg_type == RTM_DELLINK) { + // For these events, data part contains an ifinfomsg struct and a + // series of rtattr structures. See rtnetlink(7). + // We are only interested in interface index and flags. + struct ifinfomsg* ifinfo_msg = + reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header)); + EventParam param(ifinfo_msg->ifi_index, ifinfo_msg->ifi_flags); + params.push_back(param); + + } else if (header->nlmsg_type == RTM_NEWADDR || + header->nlmsg_type == RTM_DELADDR) { + // For these events, data part contains an ifaddrmsg struct and + // optionally some rtattr structures. We'll extract IP address(es) + // from them. + struct ifaddrmsg* ifaddr_msg = + reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header)); + unsigned int size = IFA_PAYLOAD(header); + params = ParseIFAddrMessage(ifaddr_msg, size); + + } else { + continue; + } + + // update status_table_ based on received data + UpdateStatus(header->nlmsg_type, params); + } + } + + // notify the listener if necessary + NotifyIPAddresses(); + } + } +} + +bool PlatformSpecificNetworkInterfaceListener::StopLoop() { + LOG4CXX_AUTO_TRACE(logger_); + + LOG4CXX_INFO(logger_, "Stopping network interface listener"); + + if (pipe_fds_[1] < 0) { + LOG4CXX_WARN(logger_, "StopLoop called in invalid state"); + return false; + } + + char dummy[1] = {0}; + int ret = write(pipe_fds_[1], dummy, sizeof(dummy)); + if (ret <= 0) { + LOG4CXX_WARN_WITH_ERRNO( + logger_, "Failed to send stop message to interface listener"); + return false; + } + + return true; +} + +bool PlatformSpecificNetworkInterfaceListener::InitializeStatus() { + LOG4CXX_AUTO_TRACE(logger_); + +#ifdef BUILD_TESTS + if (testing_) { + // don't actually call getifaddrs() + return true; + } +#endif // BUILD_TESTS + + struct ifaddrs* if_list, *interface; + if (getifaddrs(&if_list) != 0) { + LOG4CXX_WARN(logger_, + "getifaddr failed, interface status won't be available until " + "a change occurs"); + return false; + } + + // clear existing table + status_table_.clear(); + + for (interface = if_list; interface != NULL; + interface = interface->ifa_next) { + if (interface->ifa_name == NULL || interface->ifa_name[0] == '\0') { + continue; + } + if (interface->ifa_addr == NULL) { + continue; + } + + std::string ifname(interface->ifa_name); + InterfaceStatus& status = status_table_[ifname]; + + switch (interface->ifa_addr->sa_family) { + case AF_INET: { + struct sockaddr_in* addr = + reinterpret_cast<struct sockaddr_in*>(interface->ifa_addr); + status.SetIPv4Address(&addr->sin_addr); + break; + } + case AF_INET6: { + struct sockaddr_in6* addr = + reinterpret_cast<struct sockaddr_in6*>(interface->ifa_addr); + status.SetIPv6Address(&addr->sin6_addr); + break; + } + default: + continue; + } + status.SetFlags(interface->ifa_flags); + } + + freeifaddrs(if_list); + + LOG4CXX_DEBUG(logger_, "Successfully acquired network interface status"); + DumpTable(); + return true; +} + +bool PlatformSpecificNetworkInterfaceListener::UpdateStatus( + uint16_t type, std::vector<EventParam>& params) { + LOG4CXX_AUTO_TRACE(logger_); + + for (std::vector<EventParam>::iterator it = params.begin(); + it != params.end(); + ++it) { + std::string ifname = GetInterfaceName(it->if_index); + if (ifname.empty()) { + continue; + } + + InterfaceStatus& status = status_table_[ifname]; + + switch (type) { + case RTM_NEWLINK: { + LOG4CXX_DEBUG(logger_, + "netlink event: interface " << ifname + << " created or updated"); + status.SetFlags(it->flags); + break; + } + case RTM_DELLINK: + LOG4CXX_DEBUG(logger_, + "netlink event: interface " << ifname << " removed"); + status_table_.erase(ifname); + break; + case RTM_NEWADDR: { + sockaddr* addr = reinterpret_cast<sockaddr*>(&it->address); + if (addr->sa_family == AF_INET) { + sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(addr); + status.SetIPv4Address(&addr_in->sin_addr); + LOG4CXX_DEBUG(logger_, + "netlink event: IPv4 address of interface " + << ifname << " updated to " + << status.GetIPv4Address()); + } else if (addr->sa_family == AF_INET6) { + sockaddr_in6* addr_in6 = reinterpret_cast<sockaddr_in6*>(addr); + status.SetIPv6Address(&addr_in6->sin6_addr); + LOG4CXX_DEBUG(logger_, + "netlink event: IPv6 address of interface " + << ifname << " updated to " + << status.GetIPv6Address()); + } + break; + } + case RTM_DELADDR: { + sockaddr* addr = reinterpret_cast<sockaddr*>(&it->address); + if (addr->sa_family == AF_INET) { + LOG4CXX_DEBUG(logger_, + "netlink event: IPv4 address of interface " + << ifname << " removed"); + status.SetIPv4Address(NULL); + } else if (addr->sa_family == AF_INET6) { + LOG4CXX_DEBUG(logger_, + "netlink event: IPv6 address of interface " + << ifname << " removed"); + status.SetIPv6Address(NULL); + } + break; + } + default: + LOG4CXX_WARN(logger_, "Unsupported netlink event (" << type << ")"); + break; + } + } + return true; +} + +void PlatformSpecificNetworkInterfaceListener::NotifyIPAddresses() { + LOG4CXX_AUTO_TRACE(logger_); + + std::string ipv4_addr; + std::string ipv6_addr; + const std::string interface_name = SelectInterface(); + + // note that if interface_name is empty (i.e. no interface is selected), + // the IP addresses will be empty + if (!interface_name.empty()) { + InterfaceStatusTable::iterator it = status_table_.find(interface_name); + if (status_table_.end() != it) { + InterfaceStatus& status = it->second; + ipv4_addr = status.GetIPv4Address(); + ipv6_addr = status.GetIPv6Address(); + } + } + + if (notified_ipv4_addr_ != ipv4_addr || notified_ipv6_addr_ != ipv6_addr) { + LOG4CXX_INFO(logger_, + "IP address updated: \"" << notified_ipv4_addr_ << "\" -> \"" + << ipv4_addr << "\", \"" + << notified_ipv6_addr_ << "\" -> \"" + << ipv6_addr << "\""); + + notified_ipv4_addr_ = ipv4_addr; + notified_ipv6_addr_ = ipv6_addr; + + tcp_client_listener_->OnIPAddressUpdated(notified_ipv4_addr_, + notified_ipv6_addr_); + } +} + +const std::string PlatformSpecificNetworkInterfaceListener::SelectInterface() { + LOG4CXX_AUTO_TRACE(logger_); + + if (!designated_interface_.empty()) { + return designated_interface_; + } + + InterfaceStatusTable::iterator it; + + if (!selected_interface_.empty()) { + // if current network interface is still available and has IP address, then + // we use it + it = status_table_.find(selected_interface_); + if (it != status_table_.end()) { + InterfaceStatus& status = it->second; + if (status.IsAvailable() && status.HasIPAddress()) { + return selected_interface_; + } + } + } + + // pick a network interface that has IP address + for (it = status_table_.begin(); it != status_table_.end(); ++it) { + InterfaceStatus& status = it->second; + // ignore loopback interfaces + if (status.IsLoopback()) { + continue; + } + // if the interface has to be UP and RUNNING, and must have an IP address + if (!(status.IsAvailable() && status.HasIPAddress())) { + continue; + } + + selected_interface_ = it->first; + LOG4CXX_DEBUG(logger_, + "selecting network interface: " << selected_interface_); + return selected_interface_; + } + + selected_interface_ = ""; + return selected_interface_; +} + +std::vector<PlatformSpecificNetworkInterfaceListener::EventParam> +PlatformSpecificNetworkInterfaceListener::ParseIFAddrMessage( + struct ifaddrmsg* message, unsigned int size) { + LOG4CXX_AUTO_TRACE(logger_); + + std::vector<EventParam> params; + + // Iterate through rtattr structs. (The first one can be acquired through + // IFA_RTA() macro) + for (struct rtattr* attr = IFA_RTA(message); RTA_OK(attr, size); + attr = RTA_NEXT(attr, size)) { + if (!(attr->rta_type == IFA_LOCAL || attr->rta_type == IFA_ADDRESS)) { + continue; + } + + EventParam param(message->ifa_index); + + if (message->ifa_family == AF_INET) { + // make sure the size of data is >= 4 bytes + if (RTA_PAYLOAD(attr) < sizeof(struct in_addr)) { + LOG4CXX_DEBUG(logger_, + "Invalid netlink event: insufficient IPv4 address data"); + continue; + } + + // Data part of rtattr contains IPv4 address. Copy it to param.address + struct in_addr* ipv4_addr = + reinterpret_cast<struct in_addr*>(RTA_DATA(attr)); + + struct sockaddr_in* sockaddr = + reinterpret_cast<struct sockaddr_in*>(¶m.address); + sockaddr->sin_family = AF_INET; + sockaddr->sin_addr = *ipv4_addr; + + } else if (message->ifa_family == AF_INET6) { + // make sure the size of data is >= 16 bytes + if (RTA_PAYLOAD(attr) < sizeof(struct in6_addr)) { + LOG4CXX_DEBUG(logger_, + "Invalid netlink event: insufficient IPv6 address data"); + continue; + } + + // Data part of rtattr contains IPv6 address. Copy it to param.address + struct in6_addr* ipv6_addr = + reinterpret_cast<struct in6_addr*>(RTA_DATA(attr)); + + struct sockaddr_in6* sockaddr = + reinterpret_cast<struct sockaddr_in6*>(¶m.address); + sockaddr->sin6_family = AF_INET6; + sockaddr->sin6_addr = *ipv6_addr; + + } else { + LOG4CXX_WARN(logger_, + "Unsupported family (" << message->ifa_family << ")"); + continue; + } + + params.push_back(param); + } + + return params; +} + +void PlatformSpecificNetworkInterfaceListener::DumpTable() const { + LOG4CXX_DEBUG(logger_, + "Number of network interfaces: " << status_table_.size()); + + for (auto it = status_table_.begin(); it != status_table_.end(); ++it) { + const std::string ifname = it->first; + const InterfaceStatus& status = it->second; + + LOG4CXX_DEBUG( + logger_, + " " << ifname << " : flags=" << status.GetFlags() + << " : available: " << (status.IsAvailable() ? "yes" : "no") + << " IPv4: " << status.GetIPv4Address() + << " IPv6: " << status.GetIPv6Address() + << (status.IsLoopback() ? " (loopback)" : "")); + } +} + +PlatformSpecificNetworkInterfaceListener::ListenerThreadDelegate:: + ListenerThreadDelegate(PlatformSpecificNetworkInterfaceListener* parent) + : parent_(parent) {} + +void PlatformSpecificNetworkInterfaceListener::ListenerThreadDelegate:: + threadMain() { + parent_->Loop(); +} + +void PlatformSpecificNetworkInterfaceListener::ListenerThreadDelegate:: + exitThreadMain() { + parent_->StopLoop(); +} + +static std::string GetInterfaceName(unsigned int if_index) { + char buf[IFNAMSIZ + 1] = ""; + if_indextoname(if_index, buf); + return std::string(buf); +} + +static bool SetNonblocking(int s) { + int prev_flag = fcntl(s, F_GETFL, 0); + if (prev_flag == -1) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to acquire socket flag"); + return false; + } + + int ret = fcntl(s, F_SETFL, prev_flag | O_NONBLOCK); + if (ret == -1) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, + "Failed to configure socket to non-blocking"); + return false; + } + + return true; +} + +} // namespace transport_adapter +} // namespace transport_manager diff --git a/src/components/transport_manager/src/tcp/platform_specific/qnx/platform_specific_network_interface_listener.cc b/src/components/transport_manager/src/tcp/platform_specific/qnx/platform_specific_network_interface_listener.cc new file mode 100644 index 0000000000..9ca7890278 --- /dev/null +++ b/src/components/transport_manager/src/tcp/platform_specific/qnx/platform_specific_network_interface_listener.cc @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the names of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include "transport_manager/tcp/platform_specific/qnx/platform_specific_network_interface_listener_impl.h" + +namespace transport_manager { +namespace transport_adapter { + +CREATE_LOGGERPTR_GLOBAL(logger_, "TransportManager") + +PlatformSpecificNetworkInterfaceListener:: + PlatformSpecificNetworkInterfaceListener( + TcpClientListener* tcp_client_listener, + const std::string designated_interface) {} + +PlatformSpecificNetworkInterfaceListener:: + ~PlatformSpecificNetworkInterfaceListener() { + LOG4CXX_AUTO_TRACE(logger_); +} + +bool PlatformSpecificNetworkInterfaceListener::Init() { + LOG4CXX_AUTO_TRACE(logger_); + return true; +} + +void PlatformSpecificNetworkInterfaceListener::Deinit() { + LOG4CXX_AUTO_TRACE(logger_); +} + +bool PlatformSpecificNetworkInterfaceListener::Start() { + LOG4CXX_AUTO_TRACE(logger_); + return true; +} + +bool PlatformSpecificNetworkInterfaceListener::Stop() { + LOG4CXX_AUTO_TRACE(logger_); + return true; +} + +} // namespace transport_adapter +} // namespace transport_manager diff --git a/src/components/transport_manager/src/tcp/tcp_client_listener.cc b/src/components/transport_manager/src/tcp/tcp_client_listener.cc index 207149eb8c..4b590318e6 100644 --- a/src/components/transport_manager/src/tcp/tcp_client_listener.cc +++ b/src/components/transport_manager/src/tcp/tcp_client_listener.cc @@ -3,6 +3,9 @@ * Copyright (c) 2017, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -14,7 +17,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -36,11 +39,14 @@ #include <memory.h> #include <signal.h> #include <errno.h> +#include <fcntl.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/types.h> +#include <sys/select.h> #include <sys/sysctl.h> #include <sys/socket.h> +#include <ifaddrs.h> #ifdef __linux__ #include <linux/tcp.h> #else // __linux__ @@ -56,6 +62,7 @@ #include "utils/make_shared.h" #include "utils/threads/thread.h" #include "transport_manager/transport_adapter/transport_adapter_controller.h" +#include "transport_manager/tcp/network_interface_listener_impl.h" #include "transport_manager/tcp/tcp_device.h" #include "transport_manager/tcp/tcp_socket_connection.h" @@ -64,72 +71,88 @@ namespace transport_adapter { CREATE_LOGGERPTR_GLOBAL(logger_, "TransportManager") +static bool SetNonblocking(int s); + +#ifdef BUILD_TESTS +bool TcpClientListener::testing_ = false; +#endif // BUILD_TESTS + TcpClientListener::TcpClientListener(TransportAdapterController* controller, const uint16_t port, - const bool enable_keepalive) + const bool enable_keepalive, + const std::string designated_interface) : port_(port) , enable_keepalive_(enable_keepalive) , controller_(controller) + , initialized_(false) + , started_(false) , thread_(0) , socket_(-1) - , thread_stop_requested_(false) { + , thread_stop_requested_(false) + , designated_interface_(designated_interface) { + pipe_fds_[0] = pipe_fds_[1] = -1; thread_ = threads::CreateThread("TcpClientListener", new ListeningThreadDelegate(this)); + interface_listener_ = + new NetworkInterfaceListenerImpl(this, designated_interface); } TransportAdapter::Error TcpClientListener::Init() { LOG4CXX_AUTO_TRACE(logger_); thread_stop_requested_ = false; - socket_ = socket(AF_INET, SOCK_STREAM, 0); - if (-1 == socket_) { - LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to create socket"); - return TransportAdapter::FAIL; - } - - sockaddr_in server_address = {0}; - server_address.sin_family = AF_INET; - server_address.sin_port = htons(port_); - server_address.sin_addr.s_addr = INADDR_ANY; - - int optval = 1; - if (0 != - setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) { - LOG4CXX_WARN_WITH_ERRNO(logger_, "setsockopt SO_REUSEADDR failed"); + if (!IsListeningOnSpecificInterface()) { + // Network interface is not specified. We will listen on all interfaces + // using INADDR_ANY. If socket creation fails, we will treat it an error. + socket_ = CreateIPv4ServerSocket(port_); + if (-1 == socket_) { + LOG4CXX_ERROR(logger_, "Failed to create TCP socket"); + return TransportAdapter::FAIL; + } + } else { + // Network interface is specified and we wiill listen only on the interface. + // In this case, the server socket will be created once + // NetworkInterfaceListener notifies the interface's IP address. + LOG4CXX_INFO(logger_, + "TCP server socket will listen on " + << designated_interface_ + << " once it has an IPv4 address."); } - if (bind(socket_, - reinterpret_cast<sockaddr*>(&server_address), - sizeof(server_address)) != 0) { - LOG4CXX_ERROR_WITH_ERRNO(logger_, "bind() failed"); + if (!interface_listener_->Init()) { + if (socket_ >= 0) { + close(socket_); + socket_ = -1; + } return TransportAdapter::FAIL; } - const int kBacklog = 128; - if (0 != listen(socket_, kBacklog)) { - LOG4CXX_ERROR_WITH_ERRNO(logger_, "listen() failed"); - return TransportAdapter::FAIL; - } + initialized_ = true; return TransportAdapter::OK; } void TcpClientListener::Terminate() { LOG4CXX_AUTO_TRACE(logger_); - if (socket_ == -1) { - LOG4CXX_WARN(logger_, "Socket has been closed"); + + if (!initialized_) { return; } - if (shutdown(socket_, SHUT_RDWR) != 0) { - LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to shutdown socket"); - } - if (close(socket_) != 0) { - LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to close socket"); + + if (!IsListeningOnSpecificInterface()) { + DestroyServerSocket(socket_); + socket_ = -1; + } else { + sync_primitives::AutoLock auto_lock(start_stop_lock_); + DestroyServerSocket(socket_); + socket_ = -1; } - socket_ = -1; + + interface_listener_->Deinit(); + initialized_ = false; } bool TcpClientListener::IsInitialised() const { - return thread_; + return initialized_; } TcpClientListener::~TcpClientListener() { @@ -138,6 +161,7 @@ TcpClientListener::~TcpClientListener() { delete thread_->delegate(); threads::DeleteThread(thread_); Terminate(); + delete interface_listener_; } void SetKeepaliveOptions(const int fd) { @@ -203,104 +227,151 @@ void SetKeepaliveOptions(const int fd) { void TcpClientListener::Loop() { LOG4CXX_AUTO_TRACE(logger_); - while (!thread_stop_requested_) { - sockaddr_in client_address; - socklen_t client_address_size = sizeof(client_address); - const int connection_fd = accept( - socket_, (struct sockaddr*)&client_address, &client_address_size); - if (thread_stop_requested_) { - LOG4CXX_DEBUG(logger_, "thread_stop_requested_"); - close(connection_fd); - break; - } + fd_set rfds; + char dummy[16]; - if (connection_fd < 0) { - LOG4CXX_ERROR_WITH_ERRNO(logger_, "accept() failed"); - continue; - } - - if (AF_INET != client_address.sin_family) { - LOG4CXX_DEBUG(logger_, "Address of connected client is invalid"); - close(connection_fd); - continue; + while (!thread_stop_requested_) { + FD_ZERO(&rfds); + FD_SET(socket_, &rfds); + FD_SET(pipe_fds_[0], &rfds); + int nfds = socket_ > pipe_fds_[0] ? socket_ : pipe_fds_[0]; + + int ret = select(nfds + 1, &rfds, NULL, NULL, NULL); + if (ret < 0) { + if (errno == EINTR) { + continue; + } else { + LOG4CXX_WARN(logger_, "select failed for TCP server socket"); + break; + } } - char device_name[32]; - strncpy(device_name, - inet_ntoa(client_address.sin_addr), - sizeof(device_name) / sizeof(device_name[0])); - LOG4CXX_INFO(logger_, "Connected client " << device_name); - LOG4CXX_INFO(logger_, "Port is: " << port_); - - if (enable_keepalive_) { - SetKeepaliveOptions(connection_fd); + if (FD_ISSET(pipe_fds_[0], &rfds)) { + ret = read(pipe_fds_[0], dummy, sizeof(dummy)); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { + LOG4CXX_WARN( + logger_, + "Failed to read from pipe, aborting TCP server socket loop."); + break; + } + } else if (ret == 0) { + LOG4CXX_WARN(logger_, + "Pipe disconnected, aborting TCP server socket loop."); + break; + } else { + LOG4CXX_DEBUG(logger_, + "received stop command of TCP server socket loop"); + break; + } } - const auto device_uid = - device_name + std::string(":") + std::to_string(port_); + if (FD_ISSET(socket_, &rfds)) { + sockaddr_in client_address; + socklen_t client_address_size = sizeof(client_address); + const int connection_fd = accept( + socket_, (struct sockaddr*)&client_address, &client_address_size); + if (thread_stop_requested_) { + LOG4CXX_DEBUG(logger_, "thread_stop_requested_"); + close(connection_fd); + break; + } + + if (connection_fd < 0) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "accept() failed"); + continue; + } + + if (AF_INET != client_address.sin_family) { + LOG4CXX_DEBUG(logger_, "Address of connected client is invalid"); + close(connection_fd); + continue; + } + + char device_name[32]; + strncpy(device_name, + inet_ntoa(client_address.sin_addr), + sizeof(device_name) / sizeof(device_name[0])); + LOG4CXX_INFO(logger_, "Connected client " << device_name); + LOG4CXX_INFO(logger_, "Port is: " << port_); + + if (enable_keepalive_) { + SetKeepaliveOptions(connection_fd); + } + + const auto device_uid = + device_name + std::string(":") + std::to_string(port_); #if defined(BUILD_TESTS) - TcpDevice* tcp_device = - new TcpDevice(client_address.sin_addr.s_addr, device_uid, device_name); + TcpDevice* tcp_device = new TcpDevice( + client_address.sin_addr.s_addr, device_uid, device_name); #else - TcpDevice* tcp_device = - new TcpDevice(client_address.sin_addr.s_addr, device_uid); + TcpDevice* tcp_device = + new TcpDevice(client_address.sin_addr.s_addr, device_uid); #endif // BUILD_TESTS - DeviceSptr device = controller_->AddDevice(tcp_device); - tcp_device = static_cast<TcpDevice*>(device.get()); - const ApplicationHandle app_handle = - tcp_device->AddIncomingApplication(connection_fd); - - utils::SharedPtr<TcpSocketConnection> connection = - utils::MakeShared<TcpSocketConnection>( - device->unique_device_id(), app_handle, controller_); - controller_->ConnectionCreated( - connection, device->unique_device_id(), app_handle); - connection->set_socket(connection_fd); - const TransportAdapter::Error error = connection->Start(); - if (TransportAdapter::OK != error) { - LOG4CXX_ERROR(logger_, - "TCP connection::Start() failed with error: " << error); + DeviceSptr device = controller_->AddDevice(tcp_device); + tcp_device = static_cast<TcpDevice*>(device.get()); + const ApplicationHandle app_handle = + tcp_device->AddIncomingApplication(connection_fd); + + utils::SharedPtr<TcpSocketConnection> connection = + utils::MakeShared<TcpSocketConnection>( + device->unique_device_id(), app_handle, controller_); + controller_->ConnectionCreated( + connection, device->unique_device_id(), app_handle); + connection->set_socket(connection_fd); + const TransportAdapter::Error error = connection->Start(); + if (TransportAdapter::OK != error) { + LOG4CXX_ERROR(logger_, + "TCP connection::Start() failed with error: " << error); + } } } + + LOG4CXX_INFO(logger_, "TCP server socket loop is terminated."); } void TcpClientListener::StopLoop() { LOG4CXX_AUTO_TRACE(logger_); + if (pipe_fds_[1] < 0) { + LOG4CXX_WARN(logger_, "StopLoop called in invalid state"); + return; + } + thread_stop_requested_ = true; - // We need to connect to the listening socket to unblock accept() call - int byesocket = socket(AF_INET, SOCK_STREAM, 0); - sockaddr_in server_address = {0}; - server_address.sin_family = AF_INET; - server_address.sin_port = htons(port_); - server_address.sin_addr.s_addr = INADDR_ANY; - if (0 != connect(byesocket, - reinterpret_cast<sockaddr*>(&server_address), - sizeof(server_address))) { - LOG4CXX_WARN_WITH_ERRNO(logger_, "Failed to connect byesocket"); - } else { - // Can only shutdown socket if connected - if (0 != shutdown(byesocket, SHUT_RDWR)) { - LOG4CXX_WARN_WITH_ERRNO(logger_, "Failed to shutdown byesocket"); - } + + char dummy[1] = {0}; + int ret = write(pipe_fds_[1], dummy, sizeof(dummy)); + if (ret <= 0) { + LOG4CXX_WARN_WITH_ERRNO( + logger_, "Failed to send stop message to TCP server socket loop"); } - close(byesocket); } TransportAdapter::Error TcpClientListener::StartListening() { LOG4CXX_AUTO_TRACE(logger_); - if (thread_->is_running()) { + if (started_) { LOG4CXX_WARN( logger_, "TransportAdapter::BAD_STATE. Listener has already been started"); return TransportAdapter::BAD_STATE; } - if (!thread_->start()) { - LOG4CXX_ERROR(logger_, "Tcp client listener thread start failed"); + if (!interface_listener_->Start()) { return TransportAdapter::FAIL; } + + if (!IsListeningOnSpecificInterface()) { + TransportAdapter::Error ret = StartListeningThread(); + if (TransportAdapter::OK != ret) { + LOG4CXX_ERROR(logger_, "Tcp client listener thread start failed"); + interface_listener_->Stop(); + return ret; + } + } + + started_ = true; LOG4CXX_INFO(logger_, "Tcp client listener has started successfully"); return TransportAdapter::OK; } @@ -319,16 +390,286 @@ TcpClientListener::ListeningThreadDelegate::ListeningThreadDelegate( TransportAdapter::Error TcpClientListener::StopListening() { LOG4CXX_AUTO_TRACE(logger_); - if (!thread_->is_running()) { + if (!started_) { LOG4CXX_DEBUG(logger_, "TcpClientListener is not running now"); return TransportAdapter::BAD_STATE; } - thread_->join(); + interface_listener_->Stop(); + + StopListeningThread(); + started_ = false; LOG4CXX_INFO(logger_, "Tcp client listener has stopped successfully"); return TransportAdapter::OK; } +TransportAdapter::Error TcpClientListener::StartListeningThread() { + LOG4CXX_AUTO_TRACE(logger_); + + // StartListening() can be called from multiple threads + sync_primitives::AutoLock auto_lock(start_stop_lock_); + + if (pipe_fds_[0] < 0 || pipe_fds_[1] < 0) { + // recreate the pipe every time, so that the thread loop will not get + // leftover + // data inside pipe after it is started + if (pipe(pipe_fds_) != 0) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to create internal pipe"); + return TransportAdapter::FAIL; + } + if (!SetNonblocking(pipe_fds_[0])) { + LOG4CXX_WARN(logger_, "Failed to configure pipe to non-blocking"); + } + } + + thread_stop_requested_ = false; + + if (!thread_->start()) { + return TransportAdapter::FAIL; + } + return TransportAdapter::OK; +} + +TransportAdapter::Error TcpClientListener::StopListeningThread() { + LOG4CXX_AUTO_TRACE(logger_); + + // StopListening() can be called from multiple threads + sync_primitives::AutoLock auto_lock(start_stop_lock_); + + thread_->join(); + + close(pipe_fds_[1]); + pipe_fds_[1] = -1; + close(pipe_fds_[0]); + pipe_fds_[0] = -1; + + return TransportAdapter::OK; +} + +void TcpClientListener::OnIPAddressUpdated(const std::string ipv4_addr, + const std::string ipv6_addr) { + LOG4CXX_AUTO_TRACE(logger_); + + // Since we only create a TCP socket with IPv4 option (AF_INET), currently we + // do not use IPv6 address. + if (ipv4_addr != current_ip_address_) { + if (IsListeningOnSpecificInterface()) { + if (!current_ip_address_.empty()) { + // the server socket is running, terminate it + LOG4CXX_DEBUG(logger_, + "Stopping current TCP server socket on " + << designated_interface_); + StopOnNetworkInterface(); + } + if (!ipv4_addr.empty()) { + // start (or restart) server socket with the new IP address + LOG4CXX_DEBUG( + logger_, "Starting TCP server socket on " << designated_interface_); + StartOnNetworkInterface(); + } + } + + current_ip_address_ = ipv4_addr; + + std::string enabled = !current_ip_address_.empty() ? "true" : "false"; + std::ostringstream oss; + oss << port_; + + TransportConfig config; + config.insert(std::make_pair(tc_enabled, enabled)); + config.insert(std::make_pair(tc_tcp_ip_address, current_ip_address_)); + config.insert(std::make_pair(tc_tcp_port, oss.str())); + + controller_->TransportConfigUpdated(config); + } +} + +bool TcpClientListener::StartOnNetworkInterface() { + LOG4CXX_AUTO_TRACE(logger_); + + // this method is only for the case that network interface is specified + if (IsListeningOnSpecificInterface()) { + { + // make sure that two threads will not update socket_ at the same time + sync_primitives::AutoLock auto_lock(start_stop_lock_); + if (socket_ < 0) { + socket_ = CreateIPv4ServerSocket(port_, designated_interface_); + if (-1 == socket_) { + LOG4CXX_WARN(logger_, "Failed to create TCP socket"); + return false; + } + } + } + + if (TransportAdapter::OK != StartListeningThread()) { + LOG4CXX_WARN(logger_, "Failed to start TCP client listener"); + return false; + } + LOG4CXX_INFO(logger_, + "TCP server socket started on " << designated_interface_); + } + return true; +} + +bool TcpClientListener::StopOnNetworkInterface() { + LOG4CXX_AUTO_TRACE(logger_); + + if (IsListeningOnSpecificInterface()) { + if (TransportAdapter::OK != StopListeningThread()) { + LOG4CXX_WARN(logger_, "Failed to stop TCP client listener"); + return false; + } + + { + sync_primitives::AutoLock auto_lock(start_stop_lock_); + DestroyServerSocket(socket_); + socket_ = -1; + } + + LOG4CXX_INFO(logger_, + "TCP server socket on " << designated_interface_ + << " stopped"); + } + return true; +} + +bool TcpClientListener::IsListeningOnSpecificInterface() const { + return !designated_interface_.empty(); +} + +int TcpClientListener::CreateIPv4ServerSocket( + uint16_t port, const std::string interface_name) { + LOG4CXX_AUTO_TRACE(logger_); + + struct in_addr ipv4_address; + memset(&ipv4_address, 0, sizeof(ipv4_address)); + if (interface_name.empty()) { + ipv4_address.s_addr = htonl(INADDR_ANY); + } else if (!GetIPv4Address(interface_name, &ipv4_address)) { + return -1; + } + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (-1 == sock) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to create socket"); + return -1; + } + + sockaddr_in server_address = {0}; + server_address.sin_family = AF_INET; + server_address.sin_port = htons(port); + server_address.sin_addr = ipv4_address; + + int optval = 1; + if (0 != + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))) { + LOG4CXX_WARN_WITH_ERRNO(logger_, "setsockopt SO_REUSEADDR failed"); + } + + if (bind(sock, + reinterpret_cast<sockaddr*>(&server_address), + sizeof(server_address)) != 0) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "bind() failed"); + close(sock); + return -1; + } + + const int kBacklog = 128; + if (0 != listen(sock, kBacklog)) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "listen() failed"); + close(sock); + return -1; + } + + return sock; +} + +void TcpClientListener::DestroyServerSocket(int sock) { + LOG4CXX_AUTO_TRACE(logger_); + if (sock >= 0) { + if (shutdown(sock, SHUT_RDWR) != 0) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to shutdown socket"); + } + if (close(sock) != 0) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to close socket"); + } + } +} + +bool TcpClientListener::GetIPv4Address(const std::string interface_name, + struct in_addr* ip_address) { + LOG4CXX_AUTO_TRACE(logger_); + +#ifdef BUILD_TESTS + if (testing_) { + // don't actually call getifaddrs(), instead return a dummy address of + // INADDR_LOOPBACK + struct in_addr dummy_addr; + dummy_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (ip_address != NULL) { + *ip_address = dummy_addr; + } + return true; + } +#endif // BUILD_TESTS + + struct ifaddrs* if_list; + if (getifaddrs(&if_list) != 0) { + LOG4CXX_WARN(logger_, "getifaddrs failed"); + return false; + } + + struct ifaddrs* interface; + bool found = false; + + for (interface = if_list; interface != NULL; + interface = interface->ifa_next) { + if (interface->ifa_name == NULL) { + continue; + } + if (interface_name == interface->ifa_name) { + if (interface->ifa_addr == NULL) { + continue; + } + switch (interface->ifa_addr->sa_family) { + case AF_INET: { + struct sockaddr_in* addr = + reinterpret_cast<struct sockaddr_in*>(interface->ifa_addr); + if (ip_address != NULL) { + *ip_address = addr->sin_addr; + } + found = true; + break; + } + default: + break; + } + } + } + + freeifaddrs(if_list); + + return found; +} + +static bool SetNonblocking(int s) { + int prev_flag = fcntl(s, F_GETFL, 0); + if (prev_flag == -1) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, "Failed to acquire socket flag"); + return false; + } + + int ret = fcntl(s, F_SETFL, prev_flag | O_NONBLOCK); + if (ret == -1) { + LOG4CXX_ERROR_WITH_ERRNO(logger_, + "Failed to configure socket to non-blocking"); + return false; + } + + return true; +} + } // namespace transport_adapter } // namespace transport_manager diff --git a/src/components/transport_manager/src/tcp/tcp_transport_adapter.cc b/src/components/transport_manager/src/tcp/tcp_transport_adapter.cc index 0e9e63263b..f475912401 100644 --- a/src/components/transport_manager/src/tcp/tcp_transport_adapter.cc +++ b/src/components/transport_manager/src/tcp/tcp_transport_adapter.cc @@ -2,6 +2,9 @@ * Copyright (c) 2017, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -55,14 +58,35 @@ TcpTransportAdapter::TcpTransportAdapter( const uint16_t port, resumption::LastState& last_state, const TransportManagerSettings& settings) - : TransportAdapterImpl(NULL, - new TcpConnectionFactory(this), - new TcpClientListener(this, port, true), - last_state, - settings) {} + : TransportAdapterImpl( + NULL, + new TcpConnectionFactory(this), + new TcpClientListener( + this, + port, + true, + settings.transport_manager_tcp_adapter_network_interface()), + last_state, + settings) {} TcpTransportAdapter::~TcpTransportAdapter() {} +void TcpTransportAdapter::TransportConfigUpdated( + const TransportConfig& new_config) { + LOG4CXX_AUTO_TRACE(logger_); + + transport_config_ = new_config; + + // call the method of parent class to trigger OnTransportConfigUpdated() for + // the listeners + TransportAdapterImpl::TransportConfigUpdated(new_config); +} + +TransportConfig TcpTransportAdapter::GetTransportConfiguration() const { + LOG4CXX_AUTO_TRACE(logger_); + return transport_config_; +} + DeviceType TcpTransportAdapter::GetDeviceType() const { return TCP; } diff --git a/src/components/transport_manager/src/transport_adapter/transport_adapter_impl.cc b/src/components/transport_manager/src/transport_adapter/transport_adapter_impl.cc index bdacd68006..89459b8ebb 100644 --- a/src/components/transport_manager/src/transport_adapter/transport_adapter_impl.cc +++ b/src/components/transport_manager/src/transport_adapter/transport_adapter_impl.cc @@ -2,6 +2,9 @@ * Copyright (c) 2017, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -43,6 +46,10 @@ namespace transport_manager { namespace transport_adapter { +const char* tc_enabled = "enabled"; +const char* tc_tcp_port = "tcp_port"; +const char* tc_tcp_ip_address = "tcp_ip_address"; + CREATE_LOGGERPTR_GLOBAL(logger_, "TransportManager") namespace { // @deprecated DeviceTypes: PASA_AOA, PASA_BLUETOOTH, MME @@ -54,7 +61,13 @@ DeviceTypes devicesType = { std::make_pair(DeviceType::MME, std::string("USB_IOS")), std::make_pair(DeviceType::IOS_BT, std::string("BLUETOOTH_IOS")), std::make_pair(DeviceType::IOS_USB, std::string("USB_IOS")), - std::make_pair(DeviceType::TCP, std::string("WIFI"))}; + std::make_pair(DeviceType::TCP, std::string("WIFI")), + std::make_pair(DeviceType::IOS_USB_HOST_MODE, + std::string("USB_IOS_HOST_MODE")), + std::make_pair(DeviceType::IOS_USB_DEVICE_MODE, + std::string("USB_IOS_DEVICE_MODE")), + std::make_pair(DeviceType::IOS_CARPLAY_WIRELESS, + std::string("CARPLAY_WIRELESS_IOS"))}; } TransportAdapterImpl::TransportAdapterImpl( @@ -695,6 +708,16 @@ void TransportAdapterImpl::DataSendFailed( LOG4CXX_TRACE(logger_, "exit"); } +void TransportAdapterImpl::TransportConfigUpdated( + const TransportConfig& new_config) { + LOG4CXX_AUTO_TRACE(logger_); + for (TransportAdapterListenerList::iterator it = listeners_.begin(); + it != listeners_.end(); + ++it) { + (*it)->OnTransportConfigUpdated(this); + } +} + void TransportAdapterImpl::DoTransportSwitch() const { LOG4CXX_AUTO_TRACE(logger_); std::for_each( diff --git a/src/components/transport_manager/src/transport_adapter/transport_adapter_listener_impl.cc b/src/components/transport_manager/src/transport_adapter/transport_adapter_listener_impl.cc index f1181ce921..b0ec3c8f8e 100644 --- a/src/components/transport_manager/src/transport_adapter/transport_adapter_listener_impl.cc +++ b/src/components/transport_manager/src/transport_adapter/transport_adapter_listener_impl.cc @@ -2,6 +2,9 @@ * Copyright (c) 2014, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -376,4 +379,23 @@ void TransportAdapterListenerImpl::OnTransportSwitchRequested( LOG4CXX_WARN(logger_, "Failed to receive event from device"); } } + +void TransportAdapterListenerImpl::OnTransportConfigUpdated( + const transport_adapter::TransportAdapter* adapter) { + LOG4CXX_AUTO_TRACE(logger_); + + const TransportAdapterEvent event(EventTypeEnum::ON_TRANSPORT_CONFIG_UPDATED, + transport_adapter_, + "", + 0, + ::protocol_handler::RawMessagePtr(), + BaseErrorPtr()); + + if (transport_manager_ != NULL && + transport_manager::E_SUCCESS != + transport_manager_->ReceiveEventFromDevice(event)) { + LOG4CXX_WARN(logger_, "Failed to receive event from device"); + } +} + } // namespace transport_manager diff --git a/src/components/transport_manager/src/transport_manager_impl.cc b/src/components/transport_manager/src/transport_manager_impl.cc index a364220a64..764accf7fe 100644 --- a/src/components/transport_manager/src/transport_manager_impl.cc +++ b/src/components/transport_manager/src/transport_manager_impl.cc @@ -2,6 +2,9 @@ * Copyright (c) 2017, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -1144,6 +1147,13 @@ void TransportManagerImpl::Handle(TransportAdapterEvent event) { LOG4CXX_DEBUG(logger_, "eevent_type = ON_UNEXPECTED_DISCONNECT"); break; } + case EventTypeEnum::ON_TRANSPORT_CONFIG_UPDATED: { + LOG4CXX_DEBUG(logger_, "event_type = ON_TRANSPORT_CONFIG_UPDATED"); + transport_adapter::TransportConfig config = + event.transport_adapter->GetTransportConfiguration(); + RaiseEvent(&TransportManagerListener::OnTransportConfigUpdated, config); + break; + } } // switch LOG4CXX_TRACE(logger_, "exit"); } diff --git a/src/components/transport_manager/test/CMakeLists.txt b/src/components/transport_manager/test/CMakeLists.txt index 41aae296ac..240784436b 100644 --- a/src/components/transport_manager/test/CMakeLists.txt +++ b/src/components/transport_manager/test/CMakeLists.txt @@ -42,11 +42,18 @@ include_directories( ) set(EXCLUDE_PATHS + platform_specific raw_message_matcher.cc ) collect_sources(SOURCES "${CMAKE_CURRENT_SOURCE_DIR}" "${EXCLUDE_PATHS}") +set(PLATFORM_DEPENDENT_SOURCES) +if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + collect_sources(PLATFORM_DEPENDENT_SOURCES platform_specific/linux) +endif() +list(APPEND SOURCES ${PLATFORM_DEPENDENT_SOURCES}) + set(LIBRARIES gmock ConfigProfile diff --git a/src/components/transport_manager/test/include/transport_manager/tcp/mock_tcp_client_listener.h b/src/components/transport_manager/test/include/transport_manager/tcp/mock_tcp_client_listener.h new file mode 100644 index 0000000000..b7db7c7e64 --- /dev/null +++ b/src/components/transport_manager/test/include/transport_manager/tcp/mock_tcp_client_listener.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SRC_COMPONENTS_TRANSPORT_MANAGER_TEST_INCLUDE_TRANSPORT_MANAGER_TCP_MOCK_TCP_CLIENT_LISTENER_H_ +#define SRC_COMPONENTS_TRANSPORT_MANAGER_TEST_INCLUDE_TRANSPORT_MANAGER_TCP_MOCK_TCP_CLIENT_LISTENER_H_ + +#include "gmock/gmock.h" +#include "transport_manager/tcp/tcp_client_listener.h" + +namespace test { +namespace components { +namespace transport_manager_test { + +using namespace ::transport_manager::transport_adapter; + +class MockTcpClientListener : public TcpClientListener { + public: + MockTcpClientListener(TransportAdapterController* controller, + uint16_t port, + bool enable_keepalive, + const std::string designated_interface = "") + : TcpClientListener( + controller, port, enable_keepalive, designated_interface) {} + MOCK_METHOD0(Init, TransportAdapter::Error()); + MOCK_METHOD0(Terminate, void()); + MOCK_CONST_METHOD0(IsInitialised, bool()); + MOCK_METHOD0(StartListening, TransportAdapter::Error()); + MOCK_METHOD0(StopListening, TransportAdapter::Error()); + MOCK_METHOD2(OnIPAddressUpdated, + void(const std::string ipv4_addr, const std::string ipv6_addr)); +}; + +} // namespace transport_manager_test +} // namespace components +} // namespace test + +#endif // SRC_COMPONENTS_TRANSPORT_MANAGER_TEST_INCLUDE_TRANSPORT_MANAGER_TCP_MOCK_TCP_CLIENT_LISTENER_H_ diff --git a/src/components/transport_manager/test/include/transport_manager/transport_adapter/mock_transport_adapter_listener.h b/src/components/transport_manager/test/include/transport_manager/transport_adapter/mock_transport_adapter_listener.h index 03e7630e8b..dce23189c8 100644 --- a/src/components/transport_manager/test/include/transport_manager/transport_adapter/mock_transport_adapter_listener.h +++ b/src/components/transport_manager/test/include/transport_manager/transport_adapter/mock_transport_adapter_listener.h @@ -122,6 +122,8 @@ class MockTransportAdapterListener : public TransportAdapterListener { const ApplicationHandle&)); MOCK_METHOD1(OnTransportSwitchRequested, void(const TransportAdapter* transport_adapter)); + MOCK_METHOD1(OnTransportConfigUpdated, + void(const TransportAdapter* transport_adapter)); }; } // namespace transport_manager_test diff --git a/src/components/transport_manager/test/network_interface_listener_test.cc b/src/components/transport_manager/test/network_interface_listener_test.cc new file mode 100644 index 0000000000..7a5b0315ab --- /dev/null +++ b/src/components/transport_manager/test/network_interface_listener_test.cc @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <arpa/inet.h> +#include <net/if.h> +#include <time.h> + +#include "gtest/gtest.h" +#include "transport_manager/tcp/network_interface_listener_impl.h" +#include "transport_manager/tcp/mock_tcp_client_listener.h" +#include "utils/test_async_waiter.h" +#include "utils/threads/thread.h" + +namespace test { +namespace components { +namespace transport_manager_test { + +namespace { +const long kThreadStartWaitMsec = 10; +const uint32_t kStartNotificationTimeoutMsec = 500; +} + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::SaveArg; + +class NetworkInterfaceListenerTest : public ::testing::Test { + public: + NetworkInterfaceListenerTest() + : interface_listener_impl_(NULL) + , mock_tcp_client_listener_(NULL, 0, false, "") {} + + virtual ~NetworkInterfaceListenerTest() {} + + protected: + struct InterfaceEntry { + const char* name; + const char* ipv4_address; + const char* ipv6_address; + unsigned int flags; + }; + + void Deinit() { + delete interface_listener_impl_; + } + + void SleepFor(long msec) const { + if (msec > 0) { + struct timespec ts = {0, msec * 1000 * 1000}; + nanosleep(&ts, NULL); + } + } + + NetworkInterfaceListenerImpl* interface_listener_impl_; + MockTcpClientListener mock_tcp_client_listener_; +}; + +} // namespace transport_manager_test +} // namespace components +} // namespace test diff --git a/src/components/transport_manager/test/platform_specific/linux/linux_network_interface_listener_test.cc b/src/components/transport_manager/test/platform_specific/linux/linux_network_interface_listener_test.cc new file mode 100644 index 0000000000..864cb9d657 --- /dev/null +++ b/src/components/transport_manager/test/platform_specific/linux/linux_network_interface_listener_test.cc @@ -0,0 +1,599 @@ +/* + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <arpa/inet.h> +#include <net/if.h> +#include <time.h> + +#include "gtest/gtest.h" +#include "platform_specific_network_interface_listener_impl.h" +#include "transport_manager/tcp/mock_tcp_client_listener.h" +#include "utils/test_async_waiter.h" +#include "utils/threads/thread.h" + +namespace test { +namespace components { +namespace transport_manager_test { + +namespace { +const long kThreadStartWaitMsec = 10; +const uint32_t kStartNotificationTimeoutMsec = 500; +} + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::SaveArg; + +class NetworkInterfaceListenerTest : public ::testing::Test { + public: + NetworkInterfaceListenerTest() + : interface_listener_impl_(NULL) + , mock_tcp_client_listener_(NULL, 0, false, "") {} + + virtual ~NetworkInterfaceListenerTest() {} + + protected: + struct InterfaceEntry { + const char* name; + const char* ipv4_address; + const char* ipv6_address; + unsigned int flags; + }; + + void Init(const std::string interface_name) { + interface_listener_impl_ = new PlatformSpecificNetworkInterfaceListener( + &mock_tcp_client_listener_, interface_name); + // disable events from actual network interfaces + interface_listener_impl_->SetTesting(true); + } + + void Deinit() { + delete interface_listener_impl_; + } + + void SetDummyInterfaceTable(struct InterfaceEntry* entries) { + InterfaceStatusTable dummy_table; + + while (entries->name != NULL) { + InterfaceStatus status; + if (entries->ipv4_address != NULL) { + struct in_addr addr; + ASSERT_EQ(1, inet_pton(AF_INET, entries->ipv4_address, &addr)); + status.SetIPv4Address(&addr); + } + if (entries->ipv6_address != NULL) { + struct in6_addr addr6; + ASSERT_EQ(1, inet_pton(AF_INET6, entries->ipv6_address, &addr6)); + status.SetIPv6Address(&addr6); + } + status.SetFlags(entries->flags); + + dummy_table.insert(std::make_pair(entries->name, status)); + entries++; + } + + interface_listener_impl_->OverwriteStatusTable(dummy_table); + } + + void SleepFor(long msec) const { + if (msec > 0) { + struct timespec ts = {0, msec * 1000 * 1000}; + nanosleep(&ts, NULL); + } + } + + PlatformSpecificNetworkInterfaceListener* interface_listener_impl_; + MockTcpClientListener mock_tcp_client_listener_; +}; + +TEST_F(NetworkInterfaceListenerTest, Init) { + Init(""); + + EXPECT_TRUE(interface_listener_impl_->Init()); + EXPECT_TRUE(0 <= interface_listener_impl_->GetSocket()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, Deinit) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + interface_listener_impl_->Deinit(); + + EXPECT_EQ(-1, interface_listener_impl_->GetSocket()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, Start_success) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries[] = { + {"dummy_int0", "1.2.3.4", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + SetDummyInterfaceTable(entries); + + // after stated, it is expected that the listener notifies current IP address + // (if it's available) + TestAsyncWaiter waiter; + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(entries[0].ipv4_address, "")) + .WillOnce(NotifyTestAsyncWaiter(&waiter)); + + EXPECT_TRUE(interface_listener_impl_->Start()); + + // the "isThreadRunning_" flag of the thread will be update slightly later + SleepFor(kThreadStartWaitMsec); + + EXPECT_TRUE(interface_listener_impl_->GetThread()->is_running()); + + EXPECT_TRUE(waiter.WaitFor(1, kStartNotificationTimeoutMsec)); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, Start_twice) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + // ignore OnIPAddressUpdated call + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated(_, _)) + .Times(AtLeast(0)); + + EXPECT_TRUE(interface_listener_impl_->Start()); + SleepFor(kThreadStartWaitMsec); + + EXPECT_FALSE(interface_listener_impl_->Start()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, Stop_success) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + // ignore OnIPAddressUpdated call + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated(_, _)) + .Times(AtLeast(0)); + + EXPECT_TRUE(interface_listener_impl_->Start()); + SleepFor(kThreadStartWaitMsec); + + EXPECT_TRUE(interface_listener_impl_->Stop()); + SleepFor(kThreadStartWaitMsec); + + EXPECT_FALSE(interface_listener_impl_->GetThread()->is_running()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, Stop_twice) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + // ignore OnIPAddressUpdated call + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated(_, _)) + .Times(AtLeast(0)); + + EXPECT_TRUE(interface_listener_impl_->Start()); + SleepFor(kThreadStartWaitMsec); + + EXPECT_TRUE(interface_listener_impl_->Stop()); + + EXPECT_FALSE(interface_listener_impl_->Stop()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, Stop_without_Start) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + EXPECT_FALSE(interface_listener_impl_->Stop()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, DesignatedInterface_IPAddressChanged) { + Init("dummy_int0"); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries1[] = { + {"dummy_int0", "1.2.3.4", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + struct InterfaceEntry entries2[] = { + {"dummy_int0", "5.6.7.8", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + SetDummyInterfaceTable(entries1); + + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(entries1[0].ipv4_address, "")).Times(1); + + // this test case doesn't call Start() - we only check the behavior of + // NotifyIPAddresses() + interface_listener_impl_->testCallNotifyIPAddresses(); + + SetDummyInterfaceTable(entries2); + + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(entries2[0].ipv4_address, "")).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, DesignatedInterface_IPAddressNotChanged) { + Init("dummy_int0"); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries1[] = { + {"dummy_int0", "1.2.3.4", NULL, IFF_UP | IFF_RUNNING}, + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + struct InterfaceEntry entries2[] = { + {"dummy_int0", "1.2.3.4", NULL, IFF_UP | IFF_RUNNING}, + {"dummy_int1", "172.16.23.30", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + SetDummyInterfaceTable(entries1); + + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(entries1[0].ipv4_address, "")).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + SetDummyInterfaceTable(entries2); + + // OnIPAddressUpdated() shouldn't be notified + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated(_, _)).Times(0); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, DesignatedInterface_GoesUnavailable) { + Init("dummy_int0"); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries1[] = { + {"dummy_int0", "1.2.3.4", "fdc2:12af:327a::1", IFF_UP | IFF_RUNNING}, + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + struct InterfaceEntry entries2[] = { + {"dummy_int0", "1.2.3.4", "fdc2:12af:327a::1", IFF_UP}, + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + SetDummyInterfaceTable(entries1); + + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(entries1[0].ipv4_address, + entries1[0].ipv6_address)).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + SetDummyInterfaceTable(entries2); + + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated("", "")).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, DesignatedInterface_Removed) { + Init("dummy_int0"); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries1[] = { + {"dummy_int0", "1.2.3.4", "fdc2:12af:327a::1", IFF_UP | IFF_RUNNING}, + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + struct InterfaceEntry entries2[] = { + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + SetDummyInterfaceTable(entries1); + + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(entries1[0].ipv4_address, + entries1[0].ipv6_address)).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + SetDummyInterfaceTable(entries2); + + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated("", "")).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, DesignatedInterface_Added) { + Init("dummy_int0"); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries1[] = { + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + struct InterfaceEntry entries2[] = { + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING}, + {"dummy_int0", "1.2.3.4", NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + SetDummyInterfaceTable(entries1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + SetDummyInterfaceTable(entries2); + + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(entries2[1].ipv4_address, "")).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, AutoSelectInterface_SelectInterface) { + // automatically select network interface + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries[] = { + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING}, + {"net_dummy2", "192.168.2.3", "fdc2:12af:327a::22", IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + SetDummyInterfaceTable(entries); + + std::string output_ipv4_address; + std::string output_ipv6_address; + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated(_, _)) + .WillOnce(DoAll(SaveArg<0>(&output_ipv4_address), + SaveArg<1>(&output_ipv6_address))); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + std::string selected_interface = + interface_listener_impl_->GetSelectedInterfaceName(); + + // the interface listener should pick one of the interfaces + EXPECT_TRUE((selected_interface == entries[0].name && + output_ipv4_address == entries[0].ipv4_address && + output_ipv6_address == "") || + (selected_interface == entries[1].name && + output_ipv4_address == entries[1].ipv4_address && + output_ipv6_address == entries[1].ipv6_address)); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, + AutoSelectInterface_SkipUnavailableInterface) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries[] = { + {"dummy_int1", "10.10.10.12", NULL, IFF_UP}, + {"net_dummy2", "192.168.2.3", "fdc2:12af:327a::22", IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + SetDummyInterfaceTable(entries); + + // dummy_int1 should not be selected + struct InterfaceEntry* expected = &entries[1]; + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(expected->ipv4_address, + expected->ipv6_address)).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + EXPECT_EQ(expected->name, + interface_listener_impl_->GetSelectedInterfaceName()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, AutoSelectInterface_SkipEmptyInterface) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries[] = { + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING}, + {"net_dummy2", NULL, NULL, IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + SetDummyInterfaceTable(entries); + + // net_dummy2 should not be selected + struct InterfaceEntry* expected = &entries[0]; + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(expected->ipv4_address, "")).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + EXPECT_EQ(expected->name, + interface_listener_impl_->GetSelectedInterfaceName()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, + AutoSelectInterface_SkipLoopbackInterface) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries[] = { + {"dummy_int1", "10.10.10.12", NULL, IFF_UP | IFF_RUNNING | IFF_LOOPBACK}, + {"net_dummy2", "192.168.2.3", "fdc2:12af:327a::22", IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + // dummy_int1 should not be selected + struct InterfaceEntry* expected = &entries[1]; + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(expected->ipv4_address, + expected->ipv6_address)).Times(1); + + SetDummyInterfaceTable(entries); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + EXPECT_EQ(expected->name, + interface_listener_impl_->GetSelectedInterfaceName()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, AutoSelectInterface_DisableInterface) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries[] = { + {"net_dummy0", "192.168.2.3", "fdc2:12af:327a::22", IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated(_, _)).Times(1); + SetDummyInterfaceTable(entries); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + // make the interface "not running" + entries[0].flags &= ~IFF_RUNNING; + SetDummyInterfaceTable(entries); + + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated("", "")).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + EXPECT_EQ("", interface_listener_impl_->GetSelectedInterfaceName()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, AutoSelectInterface_EnableInterface) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries[] = { + {"net_dummy0", "192.168.2.3", "fdc2:12af:327a::22", IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated(_, _)).Times(1); + SetDummyInterfaceTable(entries); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + // make the interface "not running" + entries[0].flags &= ~IFF_RUNNING; + SetDummyInterfaceTable(entries); + + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated(_, _)).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + // make it running again + entries[0].flags |= IFF_RUNNING; + SetDummyInterfaceTable(entries); + + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(entries[0].ipv4_address, + entries[0].ipv6_address)).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + EXPECT_EQ(entries[0].name, + interface_listener_impl_->GetSelectedInterfaceName()); + + Deinit(); +} + +TEST_F(NetworkInterfaceListenerTest, AutoSelectInterface_SwitchInterface) { + Init(""); + EXPECT_TRUE(interface_listener_impl_->Init()); + + struct InterfaceEntry entries[] = { + {"dummy_int1", + "10.10.10.12", + "fd53:ba79:241d:30c1::78", + IFF_UP | IFF_RUNNING}, + {"net_dummy2", "192.168.2.3", "fdc2:12af:327a::22", IFF_UP | IFF_RUNNING}, + {NULL, NULL, NULL, 0}}; + + EXPECT_CALL(mock_tcp_client_listener_, OnIPAddressUpdated(_, _)).Times(1); + SetDummyInterfaceTable(entries); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + const std::string selected_interface = + interface_listener_impl_->GetSelectedInterfaceName(); + struct InterfaceEntry* selected = &entries[0]; + while (selected->name != NULL) { + if (selected->name == selected_interface) { + break; + } + selected++; + } + ASSERT_TRUE(selected->name != NULL); + + // make the interface "not running" + selected->flags &= ~IFF_RUNNING; + SetDummyInterfaceTable(entries); + + struct InterfaceEntry* switched; + if (selected == &entries[0]) { + switched = &entries[1]; + } else { + switched = &entries[0]; + } + + EXPECT_CALL(mock_tcp_client_listener_, + OnIPAddressUpdated(switched->ipv4_address, + switched->ipv6_address)).Times(1); + + interface_listener_impl_->testCallNotifyIPAddresses(); + + EXPECT_EQ(switched->name, + interface_listener_impl_->GetSelectedInterfaceName()); + + Deinit(); +} + +} // namespace transport_manager_test +} // namespace components +} // namespace test diff --git a/src/components/transport_manager/test/tcp_client_listener_test.cc b/src/components/transport_manager/test/tcp_client_listener_test.cc index b179f63ece..7ab53915fe 100644 --- a/src/components/transport_manager/test/tcp_client_listener_test.cc +++ b/src/components/transport_manager/test/tcp_client_listener_test.cc @@ -2,6 +2,9 @@ * Copyright (c) 2015, Ford Motor Company * All rights reserved. * + * Copyright (c) 2018 Xevo Inc. + * All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * @@ -13,7 +16,7 @@ * disclaimer in the documentation and/or other materials provided with the * distribution. * - * Neither the name of the Ford Motor Company nor the names of its contributors + * Neither the name of the copyright holders nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * @@ -30,21 +33,38 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> +#include <time.h> + #include "gtest/gtest.h" #include "transport_manager/transport_adapter/mock_transport_adapter.h" #include "transport_manager/tcp/tcp_client_listener.h" +#include "transport_manager/tcp/network_interface_listener.h" #include "transport_manager/mock_transport_manager.h" #include "transport_manager/transport_adapter/transport_adapter_controller.h" -#include "transport_manager/transport_adapter/device.h" +#include "transport_manager/transport_adapter/mock_device.h" +#include "utils/make_shared.h" +#include "utils/test_async_waiter.h" +#include "utils/threads/thread.h" namespace test { namespace components { namespace transport_manager_test { +using ::testing::_; +using ::testing::AtLeast; using ::testing::Return; using namespace ::transport_manager; using namespace ::transport_manager::transport_adapter; +namespace { +const long kThreadStartWaitMsec = 10; +const uint32_t kConnectionCreatedTimeoutMsec = 200; +} + class MockTransportAdapterController : public TransportAdapterController { public: MOCK_METHOD1(AddDevice, DeviceSptr(DeviceSptr device)); @@ -95,37 +115,491 @@ class MockTransportAdapterController : public TransportAdapterController { MOCK_METHOD2(DeviceDisconnected, void(const DeviceUID& device_handle, const DisconnectDeviceError& error)); + MOCK_METHOD1(TransportConfigUpdated, + void(const transport_manager::transport_adapter::TransportConfig& + new_config)); +}; + +class MockNetworkInterfaceListener : public NetworkInterfaceListener { + public: + MOCK_METHOD0(Init, bool()); + MOCK_METHOD0(Deinit, void()); + MOCK_METHOD0(Start, bool()); + MOCK_METHOD0(Stop, bool()); }; -class TcpClientListenerTest : public ::testing::Test { +class TcpClientListenerTest : public ::testing::TestWithParam<std::string> { public: TcpClientListenerTest() - : port_(0) + : port_(0) /* On Linux, binding to port 0 lets the system choose an + available port */ , enable_keep_alive_(false) - , tcp_client_listener_( - &adapter_controller_mock_, port_, enable_keep_alive_) {} + , interface_listener_mock_(NULL) + , tcp_client_listener_(NULL) {} + virtual ~TcpClientListenerTest() { + delete tcp_client_listener_; + } protected: + void SetUp() OVERRIDE { + tcp_client_listener_ = new TcpClientListener( + &adapter_controller_mock_, port_, enable_keep_alive_, GetParam()); + interface_listener_mock_ = new MockNetworkInterfaceListener(); + tcp_client_listener_->set_testing(true); + tcp_client_listener_->set_network_interface_listener( + interface_listener_mock_); + } + + bool InterfaceNameSpecified() const { + return "" != GetParam(); + } + + void SleepFor(long msec) const { + if (msec > 0) { + struct timespec ts = {0, msec * 1000 * 1000}; + nanosleep(&ts, NULL); + } + } + uint16_t port_; bool enable_keep_alive_; MockTransportAdapterController adapter_controller_mock_; - TcpClientListener tcp_client_listener_; + MockNetworkInterfaceListener* interface_listener_mock_; + TcpClientListener* tcp_client_listener_; }; -TEST_F(TcpClientListenerTest, Ctor_test) { - EXPECT_EQ(0, tcp_client_listener_.port()); - EXPECT_TRUE(NULL != tcp_client_listener_.thread()); - EXPECT_EQ(-1, tcp_client_listener_.get_socket()); +TEST_P(TcpClientListenerTest, Ctor_test) { + EXPECT_EQ(0, tcp_client_listener_->port()); + EXPECT_TRUE(NULL != tcp_client_listener_->thread()); + EXPECT_EQ(-1, tcp_client_listener_->get_socket()); +} + +TEST_P(TcpClientListenerTest, IsInitialised) { + // should return false until Init() is called + EXPECT_FALSE(tcp_client_listener_->IsInitialised()); +} + +TEST_P(TcpClientListenerTest, Init) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + + if (InterfaceNameSpecified()) { + // TcpClientListener will create socket once IP address of the network is + // notified. + EXPECT_EQ(-1, tcp_client_listener_->get_socket()); + } else { + // Interface name is not designated. In this case, TcpClientListener will + // create socket with Init(). + EXPECT_TRUE(0 <= tcp_client_listener_->get_socket()); + } + + EXPECT_TRUE(tcp_client_listener_->IsInitialised()); + + // Deinit() will be called during destructor + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); +} + +TEST_P(TcpClientListenerTest, Terminate) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); + + tcp_client_listener_->Terminate(); + + EXPECT_EQ(-1, tcp_client_listener_->get_socket()); +} + +TEST_P(TcpClientListenerTest, StartListening) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + // the "isThreadRunning_" flag of the thread will be update slightly later + SleepFor(kThreadStartWaitMsec); + + if (InterfaceNameSpecified()) { + EXPECT_FALSE(tcp_client_listener_->thread()->is_running()); + } else { + EXPECT_TRUE(tcp_client_listener_->thread()->is_running()); + } + + // Stop() and Deinit() will be called during destructor + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); +} + +TEST_P(TcpClientListenerTest, StartListening_twice) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + // call again + EXPECT_EQ(TransportAdapter::BAD_STATE, + tcp_client_listener_->StartListening()); + + // Stop() and Deinit() will be called during destructor + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); +} + +TEST_P(TcpClientListenerTest, StopListening) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StopListening()); + EXPECT_FALSE(tcp_client_listener_->thread()->is_running()); + + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); +} + +TEST_P(TcpClientListenerTest, StopListening_twice) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StopListening()); + + // call again + EXPECT_EQ(TransportAdapter::BAD_STATE, tcp_client_listener_->StopListening()); + + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); +} + +TEST_P(TcpClientListenerTest, ClientConnection) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + if (InterfaceNameSpecified()) { + // set up a server socket by notifying a dummy IP address + EXPECT_CALL(adapter_controller_mock_, TransportConfigUpdated(_)).Times(1); + tcp_client_listener_->OnIPAddressUpdated(std::string("192.168.1.1"), + std::string("")); + } + + // get the port number (assigned by system) that the socket is listening on + struct sockaddr_in server_addr; + socklen_t server_addr_len = sizeof(server_addr); + EXPECT_EQ(0, + getsockname(tcp_client_listener_->get_socket(), + reinterpret_cast<struct sockaddr*>(&server_addr), + &server_addr_len)); + + // try connecting to the socket + struct sockaddr_in client_addr; + client_addr.sin_family = AF_INET; + client_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + client_addr.sin_port = server_addr.sin_port; + socklen_t client_addr_len = sizeof(client_addr); + + int s = socket(AF_INET, SOCK_STREAM, 0); + EXPECT_TRUE(0 <= s); + + TestAsyncWaiter waiter; + + // controller should be notified of AddDevice event + DeviceSptr mock_device = utils::MakeShared<MockTCPDevice>( + htonl(INADDR_LOOPBACK), "dummy_tcp_device"); + EXPECT_CALL(adapter_controller_mock_, AddDevice(_)) + .WillOnce(Return(mock_device)); + EXPECT_CALL(adapter_controller_mock_, ConnectionCreated(_, _, _)) + .WillOnce(NotifyTestAsyncWaiter(&waiter)); + + // adapter_controller_mock_ may also receive ConnectDone() and + // ConnectionFinished() from ThreadedSocketConnection. Ignore them as hey are + // not part ly client listener's tests. + EXPECT_CALL(adapter_controller_mock_, ConnectDone(_, _)).Times(AtLeast(0)); + EXPECT_CALL(adapter_controller_mock_, ConnectionFinished(_, _)) + .Times(AtLeast(0)); + + EXPECT_EQ(0, + connect(s, + reinterpret_cast<struct sockaddr*>(&client_addr), + client_addr_len)); + + // since the connection is handled on another thread, wait for some time + EXPECT_TRUE(waiter.WaitFor(1, kConnectionCreatedTimeoutMsec)); + + close(s); + + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); +} + +TEST_P(TcpClientListenerTest, OnIPAddressUpdated_ValidIPv4Address) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + const std::string test_ipv4_addr = "192.168.1.1"; + const std::string test_ipv6_addr = ""; + char buf[16]; + snprintf(buf, sizeof(buf), "%u", port_); + const std::string test_port(buf); + + TransportConfig expected_config; + expected_config.insert(std::make_pair(tc_enabled, "true")); + expected_config.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr)); + expected_config.insert(std::make_pair(tc_tcp_port, test_port)); + + EXPECT_CALL(adapter_controller_mock_, TransportConfigUpdated(expected_config)) + .Times(1); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr, test_ipv6_addr); + + if (InterfaceNameSpecified()) { + // when the client listener runs with designated interface name, it should + // start the thread here + EXPECT_TRUE(0 <= tcp_client_listener_->get_socket()); + + SleepFor(kThreadStartWaitMsec); + + EXPECT_TRUE(tcp_client_listener_->thread()->is_running()); + } + + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); +} + +TEST_P(TcpClientListenerTest, OnIPAddressUpdated_IPv4Address_changed) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + const std::string test_ipv4_addr_1 = "192.168.1.1"; + const std::string test_ipv6_addr = ""; + char buf[16]; + snprintf(buf, sizeof(buf), "%u", port_); + const std::string test_port(buf); + + TransportConfig expected_config_1; + expected_config_1.insert(std::make_pair(tc_enabled, "true")); + expected_config_1.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr_1)); + expected_config_1.insert(std::make_pair(tc_tcp_port, test_port)); + + EXPECT_CALL(adapter_controller_mock_, + TransportConfigUpdated(expected_config_1)).Times(1); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr_1, test_ipv6_addr); + + const std::string test_ipv4_addr_2 = "172.16.2.3"; + TransportConfig expected_config_2; + expected_config_2.insert(std::make_pair(tc_enabled, "true")); + expected_config_2.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr_2)); + expected_config_2.insert(std::make_pair(tc_tcp_port, test_port)); + + EXPECT_CALL(adapter_controller_mock_, + TransportConfigUpdated(expected_config_2)).Times(1); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr_2, test_ipv6_addr); + + if (InterfaceNameSpecified()) { + EXPECT_TRUE(0 <= tcp_client_listener_->get_socket()); + + SleepFor(kThreadStartWaitMsec); + + EXPECT_TRUE(tcp_client_listener_->thread()->is_running()); + } + + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); +} + +TEST_P(TcpClientListenerTest, OnIPAddressUpdated_IPv4Address_same) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + const std::string test_ipv4_addr_1 = "192.168.1.1"; + const std::string test_ipv6_addr = ""; + char buf[16]; + snprintf(buf, sizeof(buf), "%u", port_); + const std::string test_port(buf); + + TransportConfig expected_config_1; + expected_config_1.insert(std::make_pair(tc_enabled, "true")); + expected_config_1.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr_1)); + expected_config_1.insert(std::make_pair(tc_tcp_port, test_port)); + + EXPECT_CALL(adapter_controller_mock_, + TransportConfigUpdated(expected_config_1)).Times(1); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr_1, test_ipv6_addr); + + const std::string test_ipv4_addr_2 = "192.168.1.1"; // same as before + TransportConfig expected_config_2; + expected_config_2.insert(std::make_pair(tc_enabled, "true")); + expected_config_2.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr_2)); + expected_config_2.insert(std::make_pair(tc_tcp_port, test_port)); + + // client listener should not generate TransportConfigUpdated event + EXPECT_CALL(adapter_controller_mock_, TransportConfigUpdated(_)).Times(0); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr_2, test_ipv6_addr); + + if (InterfaceNameSpecified()) { + EXPECT_TRUE(0 <= tcp_client_listener_->get_socket()); + + SleepFor(kThreadStartWaitMsec); + + EXPECT_TRUE(tcp_client_listener_->thread()->is_running()); + } + + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); +} + +TEST_P(TcpClientListenerTest, OnIPAddressUpdated_IPv4Address_disabled) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + const std::string test_ipv4_addr_1 = "192.168.1.1"; + const std::string test_ipv6_addr = ""; + char buf[16]; + snprintf(buf, sizeof(buf), "%u", port_); + const std::string test_port(buf); + + TransportConfig expected_config_1; + expected_config_1.insert(std::make_pair(tc_enabled, "true")); + expected_config_1.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr_1)); + expected_config_1.insert(std::make_pair(tc_tcp_port, test_port)); + + EXPECT_CALL(adapter_controller_mock_, + TransportConfigUpdated(expected_config_1)).Times(1); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr_1, test_ipv6_addr); + + const std::string test_ipv4_addr_2 = ""; + TransportConfig expected_config_2; + expected_config_2.insert(std::make_pair(tc_enabled, "false")); + expected_config_2.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr_2)); + expected_config_2.insert(std::make_pair(tc_tcp_port, test_port)); + + EXPECT_CALL(adapter_controller_mock_, + TransportConfigUpdated(expected_config_2)).Times(1); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr_2, test_ipv6_addr); + + if (InterfaceNameSpecified()) { + EXPECT_EQ(-1, tcp_client_listener_->get_socket()); + + SleepFor(kThreadStartWaitMsec); + + EXPECT_FALSE(tcp_client_listener_->thread()->is_running()); + } + + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); } -TEST_F(TcpClientListenerTest, IsInitialised) { - EXPECT_TRUE(tcp_client_listener_.IsInitialised()); +TEST_P(TcpClientListenerTest, OnIPAddressUpdated_IPv4Address_reenabled) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + const std::string test_ipv4_addr_1 = "192.168.1.1"; + const std::string test_ipv6_addr = ""; + char buf[16]; + snprintf(buf, sizeof(buf), "%u", port_); + const std::string test_port(buf); + + TransportConfig expected_config_1; + expected_config_1.insert(std::make_pair(tc_enabled, "true")); + expected_config_1.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr_1)); + expected_config_1.insert(std::make_pair(tc_tcp_port, test_port)); + + EXPECT_CALL(adapter_controller_mock_, + TransportConfigUpdated(expected_config_1)).Times(1); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr_1, test_ipv6_addr); + + const std::string test_ipv4_addr_2 = ""; + TransportConfig expected_config_2; + expected_config_2.insert(std::make_pair(tc_enabled, "false")); + expected_config_2.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr_2)); + expected_config_2.insert(std::make_pair(tc_tcp_port, test_port)); + + EXPECT_CALL(adapter_controller_mock_, + TransportConfigUpdated(expected_config_2)).Times(1); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr_2, test_ipv6_addr); + + const std::string test_ipv4_addr_3 = "192.168.1.1"; + TransportConfig expected_config_3; + expected_config_3.insert(std::make_pair(tc_enabled, "true")); + expected_config_3.insert(std::make_pair(tc_tcp_ip_address, test_ipv4_addr_3)); + expected_config_3.insert(std::make_pair(tc_tcp_port, test_port)); + + EXPECT_CALL(adapter_controller_mock_, + TransportConfigUpdated(expected_config_3)).Times(1); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr_3, test_ipv6_addr); + + if (InterfaceNameSpecified()) { + EXPECT_TRUE(0 <= tcp_client_listener_->get_socket()); + + SleepFor(kThreadStartWaitMsec); + + EXPECT_TRUE(tcp_client_listener_->thread()->is_running()); + } + + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); } -TEST_F(TcpClientListenerTest, Init) { - EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_.Init()); +TEST_P(TcpClientListenerTest, OnIPAddressUpdated_EmptyIPv4Address) { + EXPECT_CALL(*interface_listener_mock_, Init()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->Init()); + EXPECT_CALL(*interface_listener_mock_, Start()).WillOnce(Return(true)); + EXPECT_EQ(TransportAdapter::OK, tcp_client_listener_->StartListening()); + + const std::string test_ipv4_addr = ""; + const std::string test_ipv6_addr = ""; + char buf[16]; + snprintf(buf, sizeof(buf), "%u", port_); + const std::string test_port(buf); + + // if the client listener receives an empty IP address after started, it + // should ignore it + EXPECT_CALL(adapter_controller_mock_, TransportConfigUpdated(_)).Times(0); + + tcp_client_listener_->OnIPAddressUpdated(test_ipv4_addr, test_ipv6_addr); + + if (InterfaceNameSpecified()) { + EXPECT_EQ(-1, tcp_client_listener_->get_socket()); + + SleepFor(kThreadStartWaitMsec); + + EXPECT_FALSE(tcp_client_listener_->thread()->is_running()); + } + + EXPECT_CALL(*interface_listener_mock_, Stop()).WillOnce(Return(true)); + EXPECT_CALL(*interface_listener_mock_, Deinit()).Times(1); } +INSTANTIATE_TEST_CASE_P(NetworkInterfaceName, + TcpClientListenerTest, + ::testing::Values(std::string(""), + std::string("dummy_interface0"))); + } // namespace transport_manager_test } // namespace components } // namespace test diff --git a/src/components/transport_manager/test/tcp_transport_adapter_test.cc b/src/components/transport_manager/test/tcp_transport_adapter_test.cc index dd587569d3..a6896dc48a 100644 --- a/src/components/transport_manager/test/tcp_transport_adapter_test.cc +++ b/src/components/transport_manager/test/tcp_transport_adapter_test.cc @@ -48,6 +48,7 @@ namespace components { namespace transport_manager_test { using ::testing::Return; +using ::testing::ReturnRef; using ::testing::_; using namespace ::protocol_handler; @@ -61,6 +62,13 @@ class TcpAdapterTest : public ::testing::Test { resumption::LastStateImpl last_state_; const uint32_t port = 12345; const std::string string_port = "12345"; + std::string network_interface = ""; + + void SetUp() OVERRIDE { + EXPECT_CALL(transport_manager_settings, + transport_manager_tcp_adapter_network_interface()) + .WillRepeatedly(ReturnRef(network_interface)); + } }; TEST_F(TcpAdapterTest, StoreDataWithOneDeviceAndOneApplication) { @@ -339,6 +347,37 @@ TEST_F(TcpAdapterTest, StoreDataWithSeveralDevices_RestoreData) { } } +TEST_F(TcpAdapterTest, NotifyTransportConfigUpdated) { + MockTransportAdapterListener mock_adapter_listener; + + MockTCPTransportAdapter transport_adapter( + port, last_state_, transport_manager_settings); + transport_adapter.AddListener(&mock_adapter_listener); + + TransportConfig config; + config[tc_enabled] = std::string("true"); + config[tc_tcp_ip_address] = std::string("192.168.1.1"); + config[tc_tcp_port] = std::string("12345"); + + EXPECT_CALL(mock_adapter_listener, OnTransportConfigUpdated(_)).Times(1); + + transport_adapter.TransportConfigUpdated(config); +} + +TEST_F(TcpAdapterTest, GetTransportConfiguration) { + MockTCPTransportAdapter transport_adapter( + port, last_state_, transport_manager_settings); + + TransportConfig config; + config[tc_enabled] = std::string("true"); + config[tc_tcp_ip_address] = std::string("192.168.1.1"); + config[tc_tcp_port] = std::string("12345"); + + transport_adapter.TransportConfigUpdated(config); + + EXPECT_EQ(config, transport_adapter.GetTransportConfiguration()); +} + } // namespace transport_manager_test } // namespace components } // namespace test diff --git a/src/components/transport_manager/test/transport_adapter_listener_test.cc b/src/components/transport_manager/test/transport_adapter_listener_test.cc index 14b8850b49..1494844519 100644 --- a/src/components/transport_manager/test/transport_adapter_listener_test.cc +++ b/src/components/transport_manager/test/transport_adapter_listener_test.cc @@ -230,6 +230,15 @@ TEST_F(TransportAdapterListenerTest, OnUnexpectedDisconnect) { &adapter_mock, dev_id, app_handle, err); } +TEST_F(TransportAdapterListenerTest, OnTransportConfigUpdated) { + EXPECT_CALL( + tr_mock, + ReceiveEventFromDevice(IsEvent( + EventTypeEnum::ON_TRANSPORT_CONFIG_UPDATED, &adapter_mock, "", 0))) + .WillOnce(Return(E_SUCCESS)); + transport_listener.OnTransportConfigUpdated(&adapter_mock); +} + } // namespace transport_manager_test } // namespace components } // namespace test diff --git a/src/components/transport_manager/test/transport_adapter_test.cc b/src/components/transport_manager/test/transport_adapter_test.cc index 6d709e0c17..68758f418f 100644 --- a/src/components/transport_manager/test/transport_adapter_test.cc +++ b/src/components/transport_manager/test/transport_adapter_test.cc @@ -809,6 +809,35 @@ TEST_F(TransportAdapterTest, StopDevice) { transport_adapter.StopDevice(uniq_id); } +TEST_F(TransportAdapterTest, TransportConfigUpdated) { + MockTransportAdapterImpl transport_adapter( + NULL, NULL, NULL, last_state_, transport_manager_settings); + EXPECT_CALL(transport_adapter, Restore()).WillOnce(Return(true)); + transport_adapter.Init(); + + MockTransportAdapterListener mock_listener; + transport_adapter.AddListener(&mock_listener); + + TransportConfig config; + config[tc_enabled] = std::string("true"); + config[tc_tcp_ip_address] = std::string("192.168.1.1"); + config[tc_tcp_port] = std::string("12345"); + + EXPECT_CALL(mock_listener, OnTransportConfigUpdated(_)); + transport_adapter.TransportConfigUpdated(config); +} + +TEST_F(TransportAdapterTest, GetTransportConfigration) { + MockTransportAdapterImpl transport_adapter( + NULL, NULL, NULL, last_state_, transport_manager_settings); + EXPECT_CALL(transport_adapter, Restore()).WillOnce(Return(true)); + transport_adapter.Init(); + + TransportConfig empty_config; + + EXPECT_EQ(empty_config, transport_adapter.GetTransportConfiguration()); +} + } // namespace transport_manager_test } // namespace components } // namespace test diff --git a/src/components/transport_manager/test/transport_manager_default_test.cc b/src/components/transport_manager/test/transport_manager_default_test.cc index d095a51cb6..8e8a964228 100644 --- a/src/components/transport_manager/test/transport_manager_default_test.cc +++ b/src/components/transport_manager/test/transport_manager_default_test.cc @@ -74,6 +74,10 @@ TEST(TestTransportManagerDefault, Init_LastStateNotUsed) { .WillRepeatedly(Return(false)); EXPECT_CALL(transport_manager_settings, transport_manager_tcp_adapter_port()) .WillRepeatedly(Return(12345u)); + std::string network_interface = ""; + EXPECT_CALL(transport_manager_settings, + transport_manager_tcp_adapter_network_interface()) + .WillRepeatedly(ReturnRef(network_interface)); transport_manager.Init(mock_last_state); transport_manager.Stop(); @@ -106,6 +110,10 @@ TEST(TestTransportManagerDefault, Init_LastStateUsed) { .WillRepeatedly(Return(true)); EXPECT_CALL(transport_manager_settings, transport_manager_tcp_adapter_port()) .WillRepeatedly(Return(12345u)); + std::string network_interface = ""; + EXPECT_CALL(transport_manager_settings, + transport_manager_tcp_adapter_network_interface()) + .WillRepeatedly(ReturnRef(network_interface)); transport_manager.Init(mock_last_state); transport_manager.Stop(); } @@ -137,6 +145,10 @@ TEST(TestTransportManagerDefault, Init_LastStateUsed_InvalidPort) { .WillRepeatedly(Return(true)); EXPECT_CALL(transport_manager_settings, transport_manager_tcp_adapter_port()) .WillRepeatedly(Return(1u)); + std::string network_interface = ""; + EXPECT_CALL(transport_manager_settings, + transport_manager_tcp_adapter_network_interface()) + .WillRepeatedly(ReturnRef(network_interface)); transport_manager.Init(mock_last_state); transport_manager.Stop(); } diff --git a/src/components/transport_manager/test/transport_manager_impl_test.cc b/src/components/transport_manager/test/transport_manager_impl_test.cc index eebb247908..9ba95ea99a 100644 --- a/src/components/transport_manager/test/transport_manager_impl_test.cc +++ b/src/components/transport_manager/test/transport_manager_impl_test.cc @@ -1323,6 +1323,29 @@ TEST_F(TransportManagerImplTest, tm_.OnDeviceListUpdated(mock_adapter_); } +TEST_F(TransportManagerImplTest, OnTransportConfigUpdated) { + TransportAdapterEvent test_event(EventTypeEnum::ON_TRANSPORT_CONFIG_UPDATED, + mock_adapter_, + "", + 0, + test_message_, + error_); + + transport_adapter::TransportConfig config; + config[transport_manager::transport_adapter::tc_enabled] = + std::string("true"); + config[transport_manager::transport_adapter::tc_tcp_ip_address] = + std::string("192.168.1.1"); + config[transport_manager::transport_adapter::tc_tcp_port] = + std::string("12345"); + + EXPECT_CALL(*mock_adapter_, GetTransportConfiguration()) + .WillOnce(Return(config)); + + EXPECT_CALL(*tm_listener_, OnTransportConfigUpdated(config)); + tm_.TestHandle(test_event); +} + } // namespace transport_manager_test } // namespace components } // namespace test |