diff --git a/ACL/CMakeLists.txt b/ACL/CMakeLists.txt index 4f5fbaf1f7..9adbdbcba1 100644 --- a/ACL/CMakeLists.txt +++ b/ACL/CMakeLists.txt @@ -4,4 +4,4 @@ project(ACL LANGUAGES CXX) include(../build/BuildDefaults.cmake) add_subdirectory("src") -acsdk_add_test_subdirectory_if_allowed() +add_subdirectory("test") diff --git a/ACL/include/ACL/AVSConnectionManager.h b/ACL/include/ACL/AVSConnectionManager.h index c123386b5e..22bb22d7ca 100644 --- a/ACL/include/ACL/AVSConnectionManager.h +++ b/ACL/include/ACL/AVSConnectionManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -22,10 +22,11 @@ #include #include -#include +#include #include #include #include +#include #include #include #include @@ -67,11 +68,13 @@ namespace acl { * changes, and when messages are received from AVS. These observer objects are optional. */ class AVSConnectionManager - : public avsCommon::avs::AbstractConnection + : public avsCommon::avs::AbstractAVSConnectionManager , public avsCommon::sdkInterfaces::MessageSenderInterface , public avsCommon::sdkInterfaces::AVSEndpointAssignerInterface , public MessageRouterObserverInterface - , public avsCommon::utils::RequiresShutdown { + , public avsCommon::sdkInterfaces::InternetConnectionObserverInterface + , public avsCommon::utils::RequiresShutdown + , public std::enable_shared_from_this { public: /** * A factory function that creates an AVSConnectionManager object. @@ -91,52 +94,20 @@ class AVSConnectionManager connectionStatusObservers = std::unordered_set>(), std::unordered_set> messageObservers = - std::unordered_set>()); - - /** - * Enable the AVSConnectionManager object to make connections to AVS. Once enabled, the object will attempt to - * create a connection to AVS. If the object is already connected, this function will do nothing. - */ - void enable(); - - /** - * Disable the AVSConnectionManager object. If the object is currently connected to AVS, then calling this - * function will cause the connection to be closed. If the object is not connected, then calling this function - * will do nothing. - */ - void disable(); - - /** - * Returns if the object is enabled for making connections to AVS. - * - * @return Whether this Connection object is enabled to make connections. - */ - bool isEnabled(); - - /** - * This function causes the object, if enabled, to create new connection to AVS. If the object is already - * connected, then that connection will be closed and a new one created. If the object is not connected, but - * perhaps in the process of waiting for its next connection attempt, then its waiting policy will be reset and - * it will attempt to create a new connection immediately. - * If the object is disabled, then this function will do nothing. - */ - void reconnect(); - + std::unordered_set>(), + std::shared_ptr internetConnectionMonitor = + nullptr); + + /// @name AVSConnectionManagerInterface method overrides. + /// @{ + void enable() override; + void disable() override; + bool isEnabled() override; + void reconnect() override; bool isConnected() const override; - - /** - * Adds an observer to be notified of message receptions. - * - * @param observer The observer object to add. - */ - void addMessageObserver(std::shared_ptr observer); - - /** - * Removes an observer from being notified of message receptions. - * - * @param observer The observer object to remove. - */ - void removeMessageObserver(std::shared_ptr observer); + void addMessageObserver(std::shared_ptr observer) override; + void removeMessageObserver(std::shared_ptr observer) override; + /// @} void sendMessage(std::shared_ptr request) override; @@ -146,6 +117,11 @@ class AVSConnectionManager */ void setAVSEndpoint(const std::string& avsEndpoint) override; + /// @name InternetConnectionObserverInterface method overrides. + /// @{ + void onConnectionStatusChanged(bool connected) override; + /// @} + private: /** * AVSConnectionManager constructor. @@ -161,7 +137,9 @@ class AVSConnectionManager connectionStatusObservers = std::unordered_set>(), std::unordered_set> messageObserver = - std::unordered_set>()); + std::unordered_set>(), + std::shared_ptr internetConnectionMonitor = + nullptr); void doShutdown() override; @@ -171,8 +149,11 @@ class AVSConnectionManager void receive(const std::string& contextId, const std::string& message) override; + /// Mutex to serialize access to @c m_isEnabled + std::mutex m_isEnabledMutex; + /// Internal state to indicate if the Connection object is enabled for making an AVS connection. - std::atomic m_isEnabled; + bool m_isEnabled; /// Client-provided message listener, which will receive all messages sent from AVS. std::unordered_set> m_messageObservers; @@ -182,6 +163,9 @@ class AVSConnectionManager /// Internal object that manages the actual connection to AVS. std::shared_ptr m_messageRouter; + + /// Object providing notification of gaining and losing internet connectivity. + std::shared_ptr m_internetConnectionMonitor; }; } // namespace acl diff --git a/ACL/include/ACL/Transport/DownchannelHandler.h b/ACL/include/ACL/Transport/DownchannelHandler.h new file mode 100644 index 0000000000..e60ef09d5a --- /dev/null +++ b/ACL/include/ACL/Transport/DownchannelHandler.h @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_DOWNCHANNELHANDLER_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_DOWNCHANNELHANDLER_H_ + +#include + +#include +#include + +#include "ACL/Transport/ExchangeHandler.h" +#include "ACL/Transport/MessageConsumerInterface.h" +#include "ACL/Transport/MimeResponseStatusHandlerInterface.h" + +namespace alexaClientSDK { +namespace acl { + +class DownchannelHandler; + +/** + * Handle the HTTP2 request and response that establishes and services the downchannel of an connection to AVS. + */ +class DownchannelHandler + : public ExchangeHandler + , public avsCommon::utils::http2::HTTP2RequestSourceInterface + , public MimeResponseStatusHandlerInterface + , public std::enable_shared_from_this { +public: + /** + * Create a DownchannelHandler, and send the downchannel request. + * + * @param context The ExchangeContext in which this MessageRequest handler will operate. + * @param authToken The token to use to authorize the request. + * @param messageConsumer Object to send decoded messages to. + * @param attachmentManager Object with which to get attachments to write to. + * @return The new DownchannelHandler or nullptr if the operation failed. + */ + static std::shared_ptr create( + std::shared_ptr context, + const std::string& authToken, + std::shared_ptr messageConsumer, + std::shared_ptr attachmentManager); + +private: + /** + * Constructor. + * + * @param context The ExchangeContext in which this MessageRequest handler will operate. + * @param authToken The token to use to authorize the request. + */ + DownchannelHandler(std::shared_ptr context, const std::string& authToken); + + /// @name HTTP2RequestSourceInterface methods + /// @{ + std::vector getRequestHeaderLines() override; + avsCommon::utils::http2::HTTP2SendDataResult onSendData(char* bytes, size_t size) override; + /// @} + + /// @name MimeResponseStatusHandlerInterface + /// @{ + void onActivity() override; + bool onReceiveResponseCode(long responseCode) override; + void onResponseFinished(avsCommon::utils::http2::HTTP2ResponseFinishedStatus status, const std::string& nonMimeBody) + override; + /// @} +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_DOWNCHANNELHANDLER_H_ diff --git a/ACL/include/ACL/Transport/ExchangeHandler.h b/ACL/include/ACL/Transport/ExchangeHandler.h new file mode 100644 index 0000000000..15b7a71494 --- /dev/null +++ b/ACL/include/ACL/Transport/ExchangeHandler.h @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_EXCHANGEHANDLER_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_EXCHANGEHANDLER_H_ + +#include + +#include "ACL/Transport/ExchangeHandlerContextInterface.h" + +namespace alexaClientSDK { +namespace acl { + +class HTTP2Transport; + +/** + * Common base class for HTTP2 request / response exchanges with AVS. + */ +class ExchangeHandler { +public: + /** + * Constructor. + * + * @param context The context in which this HTTP2 request / reply exchange will be performed. + * @param authToken The authorization token to send in the request. + */ + ExchangeHandler(std::shared_ptr context, const std::string& authToken); + + /** + * Destructor + */ + virtual ~ExchangeHandler() = default; + +protected: + /// The @c HTTP2Transport instance for which this exchange is to be performed. + std::shared_ptr m_context; + + /// The auth token used to make the request. + const std::string m_authToken; + + /// The AVS authorization header to send in the request. + const std::string m_authHeader; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_EXCHANGEHANDLER_H_ diff --git a/ACL/include/ACL/Transport/ExchangeHandlerContextInterface.h b/ACL/include/ACL/Transport/ExchangeHandlerContextInterface.h new file mode 100644 index 0000000000..53b234e72b --- /dev/null +++ b/ACL/include/ACL/Transport/ExchangeHandlerContextInterface.h @@ -0,0 +1,112 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_EXCHANGEHANDLERCONTEXTINTERFACE_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_EXCHANGEHANDLERCONTEXTINTERFACE_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acl { + +/** + * Interface providing context that an ExchangeHandler operates within. + */ +class ExchangeHandlerContextInterface { +public: + /** + * Destructor + */ + virtual ~ExchangeHandlerContextInterface() = default; + + /** + * Notification that the downchannel has been established. + */ + virtual void onDownchannelConnected() = 0; + + /** + * Notification that the downchannel has failed to be established or has disconnected. + */ + virtual void onDownchannelFinished() = 0; + + /** + * Notification that an @c MessgeRequest has been sent. + */ + virtual void onMessageRequestSent() = 0; + + /** + * Notification that sending a message request timed out. + */ + virtual void onMessageRequestTimeout() = 0; + + /** + * Notification that sending a @c MessageRequest has failed or been acknowledged by AVS + * (this is used to indicate it is okay to send the next message). + */ + virtual void onMessageRequestAcknowledged() = 0; + + /** + * Notification tht a message request has finished it's exchange with AVS. + */ + virtual void onMessageRequestFinished() = 0; + + /** + * Notification that sending a ping to AVS has failed or been acknowledged by AVS. + * + * @param success Whether the ping was acknowledged successfully. + */ + virtual void onPingRequestAcknowledged(bool success) = 0; + + /** + * Notification that a ping request timed out. + */ + virtual void onPingTimeout() = 0; + + /** + * Notification of network activity between this client and AVS. + * (this is used to detect sustained inactivity requiring the send of a ping). + */ + virtual void onActivity() = 0; + + /** + * Notification that a request received a FORBIDDEN (403) response. + * + * @param authToken The auth token used for the forbidden request or an empty string if the token is not specified. + */ + virtual void onForbidden(const std::string& authToken = "") = 0; + + /** + * Create an HTTP2Request for this HTTP2Transport. + * + * @param cfg The configuration object which defines the request. + * @return The new instance of HTTP2RequestInterface, or nullptr if the operation failed. + */ + virtual std::shared_ptr createAndSendRequest( + const avsCommon::utils::http2::HTTP2RequestConfig& cfg) = 0; + + /** + * Get AVS endpoint to send request to. + */ + virtual std::string getEndpoint() = 0; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_EXCHANGEHANDLERCONTEXTINTERFACE_H_ diff --git a/ACL/include/ACL/Transport/HTTP2MessageRouter.h b/ACL/include/ACL/Transport/HTTP2MessageRouter.h deleted file mode 100644 index fdcf11329a..0000000000 --- a/ACL/include/ACL/Transport/HTTP2MessageRouter.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2MESSAGEROUTER_H_ -#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2MESSAGEROUTER_H_ - -#include -#include - -#include - -#include "ACL/Transport/MessageRouter.h" -#include "ACL/Transport/MessageConsumerInterface.h" - -namespace alexaClientSDK { -namespace acl { - -/** - * An HTTP2MessageRouter routes request messages to the Alexa Voice Service, and response messages to the client. It - * uses an HTTP2 connection with AVS. - */ -class HTTP2MessageRouter : public MessageRouter { -public: - /** - * Constructor. - * - * @param authDelegate The AuthDelegate implementation. - * @param avsEndpoint The URL for the AVS endpoint to connect to. If empty the "endpoint" value of the "acl" - * configuration will be used. If there no such configuration value a default value will be used instead. - */ - HTTP2MessageRouter( - std::shared_ptr authDelegate, - std::shared_ptr attachmentManager, - const std::string& avsEndpoint = ""); - - /** - * Destructor. - */ - ~HTTP2MessageRouter(); - -private: - std::shared_ptr createTransport( - std::shared_ptr authDelegate, - std::shared_ptr attachmentManager, - const std::string& avsEndpoint, - std::shared_ptr messageConsumerInterface, - std::shared_ptr transportObserverInterface) override; -}; - -} // namespace acl -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2MESSAGEROUTER_H_ diff --git a/ACL/include/ACL/Transport/HTTP2Stream.h b/ACL/include/ACL/Transport/HTTP2Stream.h deleted file mode 100644 index 0ddb72bf8f..0000000000 --- a/ACL/include/ACL/Transport/HTTP2Stream.h +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2STREAM_H_ -#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2STREAM_H_ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "ACL/Transport/MimeParser.h" -#include "ACL/Transport/MessageConsumerInterface.h" - -/// Whether or not curl logs should be emitted. -#ifdef ACSDK_EMIT_SENSITIVE_LOGS - -#define ACSDK_EMIT_CURL_LOGS -#include - -#endif - -namespace alexaClientSDK { -namespace acl { - -// forward declaration -class HTTP2Transport; - -/** - * Class that represents an HTTP/2 stream. - */ -class HTTP2Stream { -public: - /** - * Constructor. - * - * @param messageConsumer The MessageConsumerInterface which should receive messages from AVS. - * @param attachmentManager The attachment manager. - */ - HTTP2Stream( - std::shared_ptr messageConsumer, - std::shared_ptr attachmentManager); - - /** - * Initializes streams that are supposed to POST the given request. - * Note the authToken that is passed in will get used at a later time. Ensure that the token will not - * expire soon. - * - * @param url The request URL - * @param authToken The LWA token - * @param request The MessageRequest to post - * @returns true if setup was successful - */ - bool initPost( - const std::string& url, - const std::string& authToken, - std::shared_ptr request); - - /** - * Initializes streams that are supposed to perform an HTTP GET - * Note the authToken that is passed in will get used at a later time. Ensure that the token will not - * expire soon. - * - * @param url The request URL - * @param authToken The LWA token - * @returns Whether the setup was successful or not - */ - bool initGet(const std::string& url, const std::string& authToken); - - /** - * Sets up a stream for re-use - * - * @return Whether the reset was successful or not - */ - bool reset(); - - /** - * Gets the CURL easy handle associated with this stream - * - * @returns The associated curl easy handle for the stream - */ - CURL* getCurlHandle(); - - /** - * Returns the HTTP response code to the action this stream performs. - * - * @returns The HTTP response code if one has been received, 0 if not, and < 0 if there is an error - */ - long getResponseCode(); - - /** - * Notify the current request observer that the transfer is complete with - * the appropriate SendCompleteStatus code. - */ - void notifyRequestObserver(); - - /** - * Notify the current request observer that the transfer is complete with - * the specified status code. - * - * @param status The completion status. - */ - void notifyRequestObserver(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status status); - - /** - * Callback that gets executed when data is received from the server - * See CurlEasyHandleWrapper::CurlCallback for details - */ - static size_t writeCallback(char* data, size_t size, size_t nmemb, void* userData); - - /** - * Callback that gets executed when HTTP headers are received from the server - * See CurlEasyHandleWrapper::CurlCallback for details - */ - static size_t headerCallback(char* data, size_t size, size_t nmemb, void* userData); - - /** - * Callback that gets executed when the server requires data. - * See CurlEasyHandleWrapper::curlCallback for details. - * - * @param data A pointer to the data buffer to either read or write to. - * @param size The size of a "block" of data (ala fwrite). - * @param nmemb The number of "blocks" to read or write. - * @param userData Some user data passed in with CURLOPT_READDATA. - * @return The amount of bytes read. - */ - static size_t readCallback(char* data, size_t size, size_t nmemb, void* userData); - - /** - * Sets the max amount of time in seconds the stream can take. If not set explicitly there is no timeout. - * - * @param timeoutSeconds The timeout in seconds, set to @c 0 to disable an existing timeout. - * @return Whether the addition was successful - */ - bool setStreamTimeout(const long timeoutSeconds); - - /** - * Sets how long, in seconds, the stream should take to establish a connection. If not explicitly set there is no - * timeout. - * - * @param timeoutSeconds The connection timeout, in seconds. Set to @c 0 to disable an existing timeout. - * @returns Whether setting the timeout was successful. - */ - bool setConnectionTimeout(const std::chrono::seconds timeoutSeconds); - - /** - * Un-pend all transfers for this stream - */ - void unPause(); - - /** - * Return whether this stream has pending transfers. - */ - bool isPaused() const; - - /** - * Set the logical stream ID for this stream. - * - * @param logicalStreamId The new value for this streams logical stream ID. - */ - void setLogicalStreamId(int logicalStreamId); - - /** - * Get the logical ID of this stream. - * - * @return The logical ID of this stream. - */ - unsigned int getLogicalStreamId() const; - - /** - * Set the timeout for this stream to make progress sending or receiving. - * - * @tparam TickType Type to represent tick count. - * @tparam TickPeriod @c std::ratio specifying ticks per second. - * @param duration Max time the stream may make no progress before @c getHasProgressTimedOut() returns true. - */ - template > - void setProgressTimeout(std::chrono::duration duration); - - /** - * Return whether or not the progress timeout has been reached. - * - * @return Whether or not the progress timeout has been reached. - */ - bool hasProgressTimedOut() const; - - /** - * Return a reference to the LogStringFormatter owned by this object. This is to allow a callback that uses this - * object to get access to a known good LogStringFormatter. - * - * @return A reference to a LogStringFormatter. - */ - const avsCommon::utils::logger::LogStringFormatter& getLogFormatter() const; - -private: - /** - * Configure the associated curl easy handle with options common to GET and POST - * - * @return Whether the setting was successful or not - */ - bool setCommonOptions(const std::string& url, const std::string& authToken); - - /** - * Helper function for calling @c curl_easy_setopt and checking the result. - * - * @param option The option parameter to pass through to curl_easy_setopt. - * @param optionName The name of the option to be set (for logging) - * @param param The param option to pass through to curl_easy_setopt. - * @return @c true of the operation was successful. - */ - template - bool setopt(CURLoption option, const char* optionName, ParamType param); - - /** - * Initialize capturing this streams activities in a log file. - */ - void initStreamLog(); - -#ifdef ACSDK_EMIT_CURL_LOGS - - /** - * Callback that is invoked when @c libcurl has debug information to provide. - * - * @param handle @c libcurl handle of the transfer being reported upon. - * @param type The type of data being reported. - * @param data Pointer to the data being provided. - * @param size Size (in bytes) of the data being reported. - * @param user User pointer used to pass which HTTP2Stream is being reported on. - * @return Always returns zero. - */ - static int debugFunction(CURL* handle, curl_infotype type, char* data, size_t size, void* user); - - /// File to log the stream I/O to - std::unique_ptr m_streamLog; - /// File to dump data streamed in - std::unique_ptr m_streamInDump; - /// File to dump data streamed out - std::unique_ptr m_streamOutDump; - -#endif // ACSDK_EMIT_CURL_LOGS - - /** - * The logical id for this particular object instance. (see note for @c m_streamIdCounter in this class). - * @note This is NOT the actual HTTP/2 stream id. Instead, this is an id which this class generates which is - * guaranteed to be different from the id of other objects of this class. Also, we make an attempt to emulate - * a real HTTP/2 stream id, by starting at '1' and incrementing by two for each new stream. - */ - unsigned int m_logicalStreamId; - /// The underlying curl easy handle. - avsCommon::utils::libcurlUtils::CurlEasyHandleWrapper m_transfer; - /// An DirectiveParser instance used to parse multipart MIME messages. - MimeParser m_parser; - /// The current request being sent on this HTTP/2 stream. - std::shared_ptr m_currentRequest; - /// Whether this stream has any paused transfers. - bool m_isPaused; - /** - * The exception message being received from AVS by this stream. It may be built up over several calls if either - * the write quanta are small, or if the message is long. - */ - std::string m_exceptionBeingProcessed; - /// Max time the stream may make no progress before @c hasProgressTimedOut() returns true. - std::atomic m_progressTimeout; - /// Last time something was transferred. - std::atomic m_timeOfLastTransfer; - /// Object to format log strings correctly. - avsCommon::utils::logger::LogStringFormatter m_logFormatter; -}; - -template -void HTTP2Stream::setProgressTimeout(std::chrono::duration duration) { - m_progressTimeout = std::chrono::duration_cast(duration).count(); -}; - -} // namespace acl -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2STREAM_H_ diff --git a/ACL/include/ACL/Transport/HTTP2StreamPool.h b/ACL/include/ACL/Transport/HTTP2StreamPool.h deleted file mode 100644 index 18a07cedab..0000000000 --- a/ACL/include/ACL/Transport/HTTP2StreamPool.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2STREAMPOOL_H_ -#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2STREAMPOOL_H_ - -#include -#include -#include -#include - -#include -#include - -#include "ACL/Transport/HTTP2Stream.h" -#include "ACL/Transport/MessageConsumerInterface.h" - -namespace alexaClientSDK { -namespace acl { - -class HTTP2StreamPool { -public: - /** - * Default constructor - * - * @params maxStreams The maximum number of streams that can be active - * @params attachmentManager The attachment manager. - */ - HTTP2StreamPool( - const int maxStreams, - std::shared_ptr attachmentManager); - - /** - * Grabs an HTTP2Stream from the pool and configures it to be an HTTP GET. - * - * @param url The request URL. - * @param authToken The LWA token to supply. - * @param messageConsumer The MessageConsumerInterface to pass messages to. - * @return An HTTP2Stream from the stream pool, or @c nullptr if there was an error. - */ - std::shared_ptr createGetStream( - const std::string& url, - const std::string& authToken, - std::shared_ptr messageConsumer); - - /** - * Grabs an HTTP2Stream from the pool and configures it to be an HTTP POST. - * - * @param url The request URL. - * @param authToken The LWA token to supply. - * @param request The message request. - * @param messageConsumer The MessageConsumerInterface to pass messages to. - * @return An HTTP2Stream from the stream pool, or @c nullptr if there was an error. - */ - std::shared_ptr createPostStream( - const std::string& url, - const std::string& authToken, - std::shared_ptr request, - std::shared_ptr messageConsumer); - - /** - * Returns an HTTP2Stream back into the pool. - * @param context Returns the given EventStream back into the pool. - */ - void releaseStream(std::shared_ptr stream); - -private: - /** - * Gets a stream from the stream pool If the pool is empty, returns a new HTTP2Stream. - * - * @param messageConsumer The MessageConsumerInterface which should receive messages from AVS. - * @return an HTTP2Stream from the pool, a new stream, or @c nullptr if there are too many active streams. - */ - std::shared_ptr getStream(std::shared_ptr messageConsumer); - - /// A growing pool of EventStreams. - std::vector> m_pool; - /// Mutex used to lock the EventStream pool. - std::mutex m_mutex; - /// The number of streams that have been acquired from the pool. - int m_numAcquiredStreams; - /// The maximum number of streams that can be active in the pool. - const int m_maxStreams; - /// The attachment manager. - std::shared_ptr m_attachmentManager; - /** - * A static counter to ensure each newly acquired stream across all pools has a different ID. The notion of a - * stream ID is needed to provide a per-HTTP/2-stream context for any given attachment received from AVS. AVS - * can only guarantee that the identifying 'contentId' for an attachment is unique within a HTTP/2 stream, - * hence this variable. - * @note: These IDs are not to be confused with HTTP2's internal stream IDs. - */ - static unsigned int m_nextStreamId; -}; - -} // namespace acl -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2STREAMPOOL_H_ diff --git a/ACL/include/ACL/Transport/HTTP2Transport.h b/ACL/include/ACL/Transport/HTTP2Transport.h index d0fe78c4c7..1fea5d1952 100644 --- a/ACL/include/ACL/Transport/HTTP2Transport.h +++ b/ACL/include/ACL/Transport/HTTP2Transport.h @@ -17,26 +17,22 @@ #define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2TRANSPORT_H_ #include +#include #include #include -#include #include #include -#include -#include #include #include -#include +#include #include +#include +#include -#include "AVSCommon/SDKInterfaces/AuthDelegateInterface.h" -#include "AVSCommon/SDKInterfaces/ContextManagerInterface.h" -#include "AVSCommon/Utils/LibcurlUtils/CurlMultiHandleWrapper.h" -#include "ACL/Transport/HTTP2Stream.h" -#include "ACL/Transport/HTTP2StreamPool.h" #include "ACL/Transport/MessageConsumerInterface.h" -#include "ACL/Transport/PostConnectObject.h" +#include "ACL/Transport/PingHandler.h" +#include "ACL/Transport/PostConnectFactoryInterface.h" #include "ACL/Transport/PostConnectObserverInterface.h" #include "ACL/Transport/PostConnectSendMessageInterface.h" #include "ACL/Transport/TransportInterface.h" @@ -49,278 +45,310 @@ namespace acl { * Class to create and manage an HTTP/2 connection to AVS. */ class HTTP2Transport - : public TransportInterface + : public std::enable_shared_from_this + , public TransportInterface , public PostConnectObserverInterface , public PostConnectSendMessageInterface - , public std::enable_shared_from_this { + , public avsCommon::sdkInterfaces::AuthObserverInterface + , public ExchangeHandlerContextInterface { public: + /* + * Defines a set of HTTP2/2 connection settings. + */ + struct Configuration { + /* + * Constructor. Initializes the configuration to default. + */ + Configuration(); + + /// The elapsed time without any activity before sending out a ping. + std::chrono::seconds inactivityTimeout; + }; + /** * A function that creates a HTTP2Transport object. * * @param authDelegate The AuthDelegate implementation. * @param avsEndpoint The URL for the AVS endpoint of this object. + * @param http2Connection Instance of HTTP2ConnectionInterface with which to perform HTTP2 operations. * @param messageConsumer The MessageConsumerInterface to pass messages to. * @param attachmentManager The attachment manager that manages the attachments. - * @param observer The observer to this class. + * @param transportObserver The observer of the new instance of TransportInterface. + * @param postConnectFactory The object used to create @c PostConnectInterface instances. + * @param configuration An optional configuration to specify HTTP2/2 connection settings. * @return A shared pointer to a HTTP2Transport object. */ static std::shared_ptr create( std::shared_ptr authDelegate, const std::string& avsEndpoint, - std::shared_ptr messageConsumerInterface, + std::shared_ptr http2Connection, + std::shared_ptr messageConsumer, std::shared_ptr attachmentManager, - std::shared_ptr observer); + std::shared_ptr transportObserver, + std::shared_ptr postConnectFactory, + Configuration configuration = Configuration()); /** - * @inheritDoc - * Initializes and prepares the multi-handle and downchannel for connection. - * Also starts the main network thread. + * Method to add a TransportObserverInterface instance. + * + * @param transportObserver The observer instance to add. */ - bool connect() override; - - void disconnect() override; - - bool isConnected() override; - - void onPostConnected() override; - - void sendPostConnectMessage(std::shared_ptr request) override; - void send(std::shared_ptr request) override; + void addObserver(std::shared_ptr transportObserver); /** - * Method to add observers for TranportObserverInterface. + * Method to remove a TransportObserverInterface instance. * - * @param observer The observer object to add. + * @param observer The observer instance to remove. */ - void addObserver(std::shared_ptr observer); + void removeObserver(std::shared_ptr observer); /** - * Method to add observers for TranportObserverInterface. + * Get the HTTP2ConnectionInterface instance being used by this HTTP2Transport. * - * @param observer The observer object to add. + * @return The HTTP2ConnectionInterface instance being used by this HTTP2Transport. */ - void removeObserver(std::shared_ptr observer); + std::shared_ptr getHTTP2Connection(); + + /// @name TransportInterface methods. + /// @{ + bool connect() override; + void disconnect() override; + bool isConnected() override; + void send(std::shared_ptr request) override; + /// @} + + /// @name PostConnectSendMessageInterface methods. + /// @{ + void sendPostConnectMessage(std::shared_ptr request) override; + /// @} + + /// @name PostConnectObserverInterface methods. + /// @{ + void onPostConnected() override; + /// @} + + /// @name AuthObserverInterface methods + /// @{ + void onAuthStateChange( + avsCommon::sdkInterfaces::AuthObserverInterface::State newState, + avsCommon::sdkInterfaces::AuthObserverInterface::Error error) override; + /// @} + + /// @name RequiresShutdown methods. + /// @{ + void doShutdown() override; + /// @} + + /// @name { ExchangeHandlerContextInterface methods. + /// @{ + void onDownchannelConnected() override; + void onDownchannelFinished() override; + void onMessageRequestSent() override; + void onMessageRequestTimeout() override; + void onMessageRequestAcknowledged() override; + void onMessageRequestFinished() override; + void onPingRequestAcknowledged(bool success) override; + void onPingTimeout() override; + void onActivity() override; + void onForbidden(const std::string& authToken = "") override; + std::shared_ptr createAndSendRequest( + const avsCommon::utils::http2::HTTP2RequestConfig& cfg) override; + std::string getEndpoint() override; + /// @} private: + /** + * Enum to track the (internal) state of the HTTP2Transport + */ + enum class State { + /// Initial state, not doing anything. + INIT, + /// Waiting for authorization to complete. + AUTHORIZING, + /// Making a connection to AVS. + CONNECTING, + /// Waiting for a timeout before retrying to connect to AVS. + WAITING_TO_RETRY_CONNECTING, + /// Performing operations that require a connection, but which must be done before the connection + /// becomes widely available. + POST_CONNECTING, + /// Connectsed to AVS and available for general use. + CONNECTED, + /// Handling the server disconnecting. + SERVER_SIDE_DISCONNECT, + /// Tearing down the connection. Possibly waiting for some streams to complete. + DISCONNECTING, + /// The connection is completely shut down. + SHUTDOWN + }; + + // Friend to allow access to enum class State. + friend std::ostream& operator<<(std::ostream& stream, HTTP2Transport::State state); + /** * HTTP2Transport Constructor. * * @param authDelegate The AuthDelegate implementation. * @param avsEndpoint The URL for the AVS endpoint of this object. + * @param http2Connection Instance of HTTP2ConnectionInterface with which to perform HTTP2 operations. * @param messageConsumer The MessageConsumerInterface to pass messages to. * @param attachmentManager The attachment manager that manages the attachments. - * @param observer The observer to this class. + * @param transportObserver The observer of the new instance of TransportInterface. + * @param postConnect The object used to create PostConnectInterface instances. + * @param configuration The HTTP2/2 connection settings. */ HTTP2Transport( std::shared_ptr authDelegate, const std::string& avsEndpoint, - std::shared_ptr messageConsumerInterface, + std::shared_ptr http2Connection, + std::shared_ptr messageConsumer, std::shared_ptr attachmentManager, - std::shared_ptr postConnectObject, - std::shared_ptr observer); + std::shared_ptr transportObserver, + std::shared_ptr postConnectFactory, + Configuration configuration); /** - * Notify registered observers on a transport disconnect. + * Main loop for servicing the various states. */ - void notifyObserversOnServerSideDisconnect(); - - /** - * Notify registered observers on a server side disconnect. - */ - void notifyObserversOnDisconnect(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason); + void mainLoop(); /** - * Notify registered observers on a transport connect. - */ - void notifyObserversOnConnected(); - - void doShutdown() override; - - /// Type defining an entry in the Event queue - typedef std::pair> ActiveTransferEntry; - - /** - * Sets up the downchannel stream. If a downchannel stream already exists, it is torn down and reset. + * @c mainLoop() handler for the @c State::INIT. * - * @param[out] reason Pointer to receive the reason the operation failed, if it does. - * @return Whether the operation was successful. - */ - bool setupDownchannelStream(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason* reason); - - /** - * Main network loop. Will establish a connection then repeatedly call curl_multi_perform in order to - * receive data from the AVS backend. + * @return The current value of @c m_state. */ - void networkLoop(); + State handleInit(); /** - * Establishes a connection to AVS. + * @c mainLoop() handler for the @c State::AUTHORIZING. * - * @return Whether the connection was successful or if we're already connected. + * @return The current value of @c m_state. */ - bool establishConnection(); + State handleAuthorizing(); /** - * Checks if an active stream is finished and reports the response code the observer. - */ - void cleanupFinishedStreams(); - - /** - * Check for streams that have not progressed within their timeout and remove them. - */ - void cleanupStalledStreams(); - - /** - * Checks all the currently executing message requests to see if they have HTTP response codes. + * @c mainLoop() handler for the @c State::CONNECTING. * - * @return If all the currently executing message requests have HTTP response codes. - */ - bool canProcessOutgoingMessage(); - - /** - * Send the next @c MessageRequest if any are queued. + * @return The current value of @c m_state. */ - void processNextOutgoingMessage(); + State handleConnecting(); /** - * Attempts to create a stream that will send a ping to the backend. If a ping stream is in flight, we do not - * attempt to create a new one (returning true in this case). + * @c mainLoop() handler for the @c State::WAITING_TO_RETRY_CONNECTING. * - * @return Whether setting up the HTTP2Stream was successful. Returns true if there is a ping in flight already. + * @return The current value of @c m_state. */ - bool sendPing(); + State handleWaitingToRetryConnecting(); /** - * Cleans up the ping stream when the ping request is complete, also identifies if there was an error sending - * the ping. - */ - void handlePingResponse(); - - /** - * Set whether or not the network thread is stopping. If transitioning to true, this method wakes up the - * connection retry loop so that it can break out. + * @c mainLoop() handler for the @c State::POST_CONNECTING. * - * @param reason Reason why this transport is stopping. + * @return The current value of @c m_state. */ - void setIsStopping(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason); + State handlePostConnecting(); /** - * Set whether or not the network thread is stopping. If transitioning to true, this method wakes up the - * connection retry loop so that it can break out. - * @note This method must be called while @c m_mutex is acquired. + * @c mainLoop() handler for the @c State::CONNECTED. * - * @param reason Reason why this transport is stopping. + * @return The current value of @c m_state. */ - void setIsStoppingLocked(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason); + State handleConnected(); /** - * Get whether or not the @c m_networkLoop is stopping. + * @c mainLoop() handler for the @c State::SERVER_SIDE_DISCONNECT. * - * @return Whether or not the network thread stopping. - */ - bool isStopping(); - - /** - * Get whether or not a viable connection is available (returns @c false if @c m_isStopping to discourage - * doomed sends). - * @note This method must be called while @c m_mutex is acquired. - * @return Whether or not a viable connection is available. + * @return The current value of @c m_state. */ - bool isConnectedLocked() const; + State handleServerSideDisconnect(); /** - * Set the state to connected, unless already stopping. Notifies observer of any change to connection state. - */ - void setIsConnectedTrueUnlessStopping(); - - /** - * Set the state to not connected. Notifies observer of any change to connection state. + * @c mainLoop() handler for the @c State::DISCONNECTING. + * + * @return The current value of @c m_state. */ - void setIsConnectedFalse(); + State handleDisconnecting(); /** - * Queue a @c MessageRequest for processing (to the back of the queue). + * @c mainLoop() handler for the @c State::SHUTDOWN. * - * @param request The MessageRequest to queue for sending. - * @param ignoreConnectionStatus set to @c false to block messages to AVS, @c true - * when invoked through the @c PostConnectSendMessage interface to allow - * post-connect messages to AVS in the unconnected state. - * @return Whether the request was enqueued. + * @return The current value of @c m_state. */ - bool enqueueRequest(std::shared_ptr request, bool ignoreConnectionStatus = false); + State handleShutdown(); /** - * De-queue a @c MessageRequest from (the front of) the queue of @c MessageRequest instances to process. + * Enqueue a MessageRequest for sending. * - * @return The next @c MessageRequest to process (or @c nullptr). + * @param request The MessageRequest to enqueue. + * @param beforeConnected Whether or not to only allow enqueuing of messages before connected. */ - std::shared_ptr dequeueRequest(); + void enqueueRequest(std::shared_ptr request, bool beforeConnected); /** - * Clear the queue of @c MessageRequest instances, but first call @c onSendCompleted(NOT_CONNECTED) for any - * requests in the queue. + * Handle sending @c MessageRequests and pings while in @c State::POST_CONNECTING or @c State::CONNECTED. + * + * @param whileState Continue sending @c MessageRequests and pings while in this state. + * @return The current value of @c m_state. */ - void clearQueuedRequests(); + State sendMessagesAndPings(State whileState); /** - * Release the down channel stream. + * Set the state to a new state. + * + * @note Must *not* be called while @c m_mutex is held by the calling thread. * - * @param removeFromMulti Whether to remove the stream from @c m_multi as part of releasing the stream. - * @param[out] reason If the operation fails, returns reason value. + * @param newState the new state to transition to. + * @param changedReason The reason the connection status changed. * @return Whether the operation was successful. */ - bool releaseDownchannelStream( - bool removeFromMulti = true, - avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason* reason = nullptr); + bool setState( + State newState, + avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason changedReason); /** - * Release the ping stream. + * Set the state to a new state. * - * @param removeFromMulti Whether to remove the stream from @c m_multi as part of releasing the stream. - * @return Whether the operation was successful. + * @note Must be called while @c m_mutex is held by the calling thread. + * + * @param newState + * @param reason The reason the connection status changed. + * @return */ - bool releasePingStream(bool removeFromMulti = true); + bool setStateLocked( + State newState, + avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason); /** - * Release all the event streams. + * Notify observers that this @c HTTP2Transport has established a connection with AVS. */ - void releaseAllEventStreams(); + void notifyObserversOnConnected(); /** - * Release an event stream. + * Notify observers that this @c HTTP2Transport is not connected to AVS. * - * @param stream The event stream to release. - * @param removeFromMulti Whether to remove the stream from @c m_multi as part of releasing the stream. - * @return Whether the operation was successful. + * @param reason The reason the connection was lost. */ - bool releaseEventStream(std::shared_ptr stream, bool removeFromMulti = true); + void notifyObserversOnDisconnect(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason); /** - * Release a stream - * - * @param stream The stream to release. - * @param removeFromMulti Whether to remove the stream from @c m_multi as part of releasing the stream. - * @param name Name of the stream to release (for logging). - * @return Whether the operation was successful. + * Notify observers that this @c HTTP2Transport's connection was terminated by AVS. */ - bool releaseStream(std::shared_ptr stream, bool removeFromMulti, const std::string& name); + void notifyObserversOnServerSideDisconnect(); /** - * Return whether a specified stream is an 'event' stream. + * Get m_state in a thread-safe manner. * - * @param stream The stream to check. - * @return Whether a specified stream is an 'event' stream. + * @return The current value of m_state. */ - bool isEventStream(std::shared_ptr stream); + State getState(); - /// Mutex to protect access to the m_observers variable. - std::mutex m_observerMutex; + /// Mutex for accessing @c m_state and @c m_messageQueue + std::mutex m_mutex; - /// Observers of this class, to be notified on changes in connection and received attachments. - std::unordered_set> m_observers; + /// Condition variable use to wake the @c m_thread from various waits. + std::condition_variable m_wakeEvent; - /// Observer of this class, to be passed received messages from AVS. - std::shared_ptr m_messageConsumer; + /// The current state of this HTTP2Transport. + State m_state; /// Auth delegate implementation. std::shared_ptr m_authDelegate; @@ -328,50 +356,56 @@ class HTTP2Transport /// The URL of the AVS server we will connect to. std::string m_avsEndpoint; - /// Representation of the downchannel stream. - std::shared_ptr m_downchannelStream; + /// The HTTP2ConnectionInterface with which to perform HTTP2 operations. + std::shared_ptr m_http2Connection; + + /// Observer of this class, to be passed received messages from AVS. + std::shared_ptr m_messageConsumer; + + /// Object for creating and accessing attachments. + std::shared_ptr m_attachmentManager; - /// Representation of the ping stream. - std::shared_ptr m_pingStream; + /// Factory for creating @c PostConnectInterface instances. + std::shared_ptr m_postConnectFactory; - /// Represents a CURL multi handle. - std::unique_ptr m_multi; + /// Mutex to protect access to the m_observers variable. + std::mutex m_observerMutex; - /// The list of streams that either do not have HTTP response headers, or have outstanding response data. - std::map> m_activeStreams; + /// Observers of this class, to be notified on changes in connection and received attachments. + std::unordered_set> m_observers; - /// Main thread for this class. - std::thread m_networkThread; + /// Thread for servicing the network connection. + std::thread m_thread; - /// An abstracted HTTP/2 stream pool to ensure that we efficiently and correctly manage our active streams. - HTTP2StreamPool m_streamPool; + /// PostConnect object is used to perform activities required once a connection is established. + std::shared_ptr m_postConnect; - /// Serializes access to various members. - std::mutex m_mutex; + /// Queue of @c MessageRequest instances to send. Serialized by @c m_mutex. + std::deque> m_requestQueue; - /// Reason the connection was lost. Serialized by @c m_mutex. - avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason m_disconnectReason; + /// Number of times connecting has been retried. + int m_connectRetryCount; - /// Keeps track of whether the main network loop is running. Serialized by @c m_mutex. - bool m_isNetworkThreadRunning; + /// Is a message handler awaiting a response? + bool m_isMessageHandlerAwaitingResponse; - /// Keeps track of whether we're connected to AVS. Serialized by @c m_mutex. - bool m_isConnected; + /// The number of message handlers that are not finished with their request. + int m_countOfUnfinishedMessageHandlers; - /// Whether or not the @c networkLoop is stopping. Serialized by @c m_mutex. - bool m_isStopping; + /// The current ping handler (if any). + std::shared_ptr m_pingHandler; - /// Whether or not the onDisconnected() notification has been sent. Serialized by @c m_mutex. - bool m_disconnectedSent; + /// Time last activity on the connection was observed. + std::chrono::time_point m_timeOfLastActivity; - /// Queue of @c MessageRequest instances to send. Serialized by @c m_mutex. - std::deque> m_requestQueue; + /// A bool to specify whether a post connect message was already received + std::atomic m_postConnected; - /// Used to wake the main network thread in connection retry back-off situation. - std::condition_variable m_wakeRetryTrigger; + /// The runtime HTTP2/2 connection settings. + const Configuration m_configuration; - /// PostConnect object. - std::shared_ptr m_postConnectObject; + /// The reason for disconnecting. + avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason m_disconnectReason; }; } // namespace acl diff --git a/ACL/include/ACL/Transport/HTTP2TransportFactory.h b/ACL/include/ACL/Transport/HTTP2TransportFactory.h new file mode 100644 index 0000000000..6d89160180 --- /dev/null +++ b/ACL/include/ACL/Transport/HTTP2TransportFactory.h @@ -0,0 +1,75 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2TRANSPORTFACTORY_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2TRANSPORTFACTORY_H_ + +#include +#include + +#include +#include +#include + +#include "ACL/Transport/MessageConsumerInterface.h" +#include "ACL/Transport/PostConnectFactoryInterface.h" +#include "ACL/Transport/TransportFactoryInterface.h" +#include "ACL/Transport/TransportObserverInterface.h" + +namespace alexaClientSDK { +namespace acl { + +/** + * An HTTP2TransportFactory creates HTTP2Transport objects. + */ +class HTTP2TransportFactory : public TransportFactoryInterface { +public: + /** + * HTTP2TransportFactory constructor. + * + * @param connectionFactory Object used to create instances of HTTP2ConnectionInterface. + * @param postConnectFactory Object used to create instances of the PostConnectInterface. + */ + HTTP2TransportFactory( + std::shared_ptr connectionFactory, + std::shared_ptr postConnectFactory); + + /// @name TransportFactoryInterface methods. + /// @{ + std::shared_ptr createTransport( + std::shared_ptr authDelegate, + std::shared_ptr attachmentManager, + const std::string& avsEndpoint, + std::shared_ptr messageConsumerInterface, + std::shared_ptr transportObserverInterface) override; + /// @} + + /** + * Deleted default constructor. + */ + HTTP2TransportFactory() = delete; + +private: + /// Save a pointer to the object used to create instances of the HTTP2ConnectionInterface. + std::shared_ptr m_connectionFactory; + + /// Save a pointer to the object used to create instances of the PostConnectInterface. + std::shared_ptr m_postConnectFactory; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_HTTP2TRANSPORTFACTORY_H_ diff --git a/ACL/include/ACL/Transport/MessageRequestHandler.h b/ACL/include/ACL/Transport/MessageRequestHandler.h new file mode 100644 index 0000000000..b47fc45266 --- /dev/null +++ b/ACL/include/ACL/Transport/MessageRequestHandler.h @@ -0,0 +1,132 @@ +/* + * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MESSAGEREQUESTHANDLER_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MESSAGEREQUESTHANDLER_H_ + +#include + +#include +#include +#include + +#include "ACL/Transport/ExchangeHandler.h" +#include "ACL/Transport/MessageConsumerInterface.h" +#include "ACL/Transport/MimeResponseStatusHandlerInterface.h" + +namespace alexaClientSDK { +namespace acl { + +/** + * Handle an HTTP2 request and response for a specific @c MessageRequest. + */ +class MessageRequestHandler + : public ExchangeHandler + , public avsCommon::utils::http2::HTTP2MimeRequestSourceInterface + , public MimeResponseStatusHandlerInterface + , public std::enable_shared_from_this { +public: + /** + * Destructor. + */ + ~MessageRequestHandler() override; + + /** + * Create a MessageRequestHandler and send the message request. + * + * @param context The ExchangeContext in which this MessageRequest handler will operate. + * @param authToken The token to use to authorize the request. + * @param messageRequest The MessageRequest to send. + * @param messageConsumer Where to send messages. + * @param attachmentManager Where to get attachments to write to. + * @return A new MessageRequestHandler or nullptr if the operation fails. + */ + static std::shared_ptr create( + std::shared_ptr context, + const std::string& authToken, + std::shared_ptr messageRequest, + std::shared_ptr messageConsumer, + std::shared_ptr attachmentManager); + +private: + /** + * Constructor. + * + * @param context The ExchangeContext in which this MessageRequest handler will operate. + * @param authToken The token to use to authorize the request. + * @param messageRequest The MessageRequest to send. + */ + MessageRequestHandler( + std::shared_ptr context, + const std::string& authToken, + std::shared_ptr messageRequest); + + /** + * Notify the associated HTTP2Transport instance that the message request failed or was acknowledged by AVS. + */ + void reportMessageRequestAcknowledged(); + + /** + * Notify the associated HTTP2Transport instance that the message request exchange has finished. + */ + void reportMessageRequestFinished(); + + /// @name HTTP2MimeRequestSourceInterface methods + /// @{ + std::vector getRequestHeaderLines() override; + avsCommon::utils::http2::HTTP2GetMimeHeadersResult getMimePartHeaderLines() override; + avsCommon::utils::http2::HTTP2SendDataResult onSendMimePartData(char* bytes, size_t size) override; + /// @} + + /// @name MimeResponseStatusHandlerInterface + /// @{ + void onActivity() override; + bool onReceiveResponseCode(long responseCode) override; + void onResponseFinished(avsCommon::utils::http2::HTTP2ResponseFinishedStatus status, const std::string& nonMimeBody) + override; + /// @} + + /// The MessageRequest that this handler is servicing. + std::shared_ptr m_messageRequest; + + /// JSON payload of message to send. + std::string m_json; + + /// Next char in m_json to send. + const char* m_jsonNext; + + /// Number of bytes left unsent in m_json. + size_t m_countOfJsonBytesLeft; + + /// The number of parts that have been sent. + size_t m_countOfPartsSent; + + /// Reader for current attachment (if any). + std::shared_ptr m_namedReader; + + /// Whether acknowledge of the @c MessageRequest was reported. + bool m_wasMessageRequestAcknowledgeReported; + + /// Whether finish of the @c MessageRequest was reported. + bool m_wasMessageRequestFinishedReported; + + /// Response code received through @c onReciveResponseCode (or zero). + long m_responseCode; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MESSAGEREQUESTHANDLER_H_ diff --git a/ACL/include/ACL/Transport/MessageRouter.h b/ACL/include/ACL/Transport/MessageRouter.h index 07a838271f..db05946a14 100644 --- a/ACL/include/ACL/Transport/MessageRouter.h +++ b/ACL/include/ACL/Transport/MessageRouter.h @@ -27,11 +27,12 @@ #include #include "AVSCommon/SDKInterfaces/AuthDelegateInterface.h" +#include "ACL/Transport/MessageConsumerInterface.h" #include "ACL/Transport/MessageRouterInterface.h" #include "ACL/Transport/MessageRouterObserverInterface.h" +#include "ACL/Transport/TransportFactoryInterface.h" #include "ACL/Transport/TransportInterface.h" #include "ACL/Transport/TransportObserverInterface.h" -#include "ACL/Transport/MessageConsumerInterface.h" namespace alexaClientSDK { namespace acl { @@ -52,13 +53,15 @@ class MessageRouter * @param authDelegate An implementation of an AuthDelegate, which will provide valid access tokens with which * the MessageRouter can authorize the client to AVS. * @param attachmentManager The AttachmentManager, which allows ACL to write attachments received from AVS. + * @param transportFactory Factory used to create new transport objects. * @param avsEndpoint The endpoint to connect to AVS. If empty the "endpoint" value of the "acl" configuration * will be used. If there no such configuration value a default value will be used instead. */ MessageRouter( std::shared_ptr authDelegate, std::shared_ptr attachmentManager, - const std::string& avsEndpoint); + std::shared_ptr transportFactory, + const std::string& avsEndpoint = ""); void enable() override; @@ -66,7 +69,6 @@ class MessageRouter ConnectionStatus getConnectionStatus() override; - // TODO: ACSDK-421: Revert this to use send(). void sendMessage(std::shared_ptr request) override; void setAVSEndpoint(const std::string& avsEndpoint) override; @@ -86,24 +88,6 @@ class MessageRouter void doShutdown() override; private: - /** - * Creates a new MessageRouter. - * - * @param authDelegate The AuthDelegateInterface to use for authentication and authorization with AVS. - * @param avsEndpoint The URL for the AVS server we will connect to. - * @param messageConsumerInterface The object which should be notified on messages which arrive from AVS. - * @param transportObserverInterface A pointer to the transport observer the new transport should notify. - * @return A new MessageRouter object. - * - * TODO: ACSDK-99 Replace this with an injected transport factory. - */ - virtual std::shared_ptr createTransport( - std::shared_ptr authDelegate, - std::shared_ptr attachmentManager, - const std::string& avsEndpoint, - std::shared_ptr messageConsumerInterface, - std::shared_ptr transportObserverInterface) = 0; - /** * Set the connection state. If it changes, notify our observer. * @c m_connectionMutex must be locked to call this method. @@ -212,6 +196,9 @@ class MessageRouter /// The attachment manager. std::shared_ptr m_attachmentManager; + /// The transport factory. + std::shared_ptr m_transportFactory; + protected: /** * Executor to perform asynchronous operations: diff --git a/ACL/include/ACL/Transport/MessageRouterInterface.h b/ACL/include/ACL/Transport/MessageRouterInterface.h index 963a9c1a62..4a11d78d00 100644 --- a/ACL/include/ACL/Transport/MessageRouterInterface.h +++ b/ACL/include/ACL/Transport/MessageRouterInterface.h @@ -23,7 +23,6 @@ #include "AVSCommon/Utils/Threading/Executor.h" #include "AVSCommon/Utils/RequiresShutdown.h" #include "AVSCommon/AVS/MessageRequest.h" -// TODO: ACSDK-421: Revert this to implement send(). #include "AVSCommon/SDKInterfaces/MessageSenderInterface.h" #include "ACL/Transport/MessageRouterObserverInterface.h" @@ -38,7 +37,6 @@ namespace acl { * * Implementations of this class are required to be thread-safe. */ -// TODO: ACSDK-421: Remove the inheritance from MessageSenderInterface. class MessageRouterInterface : public avsCommon::sdkInterfaces::MessageSenderInterface , public avsCommon::utils::RequiresShutdown { @@ -88,6 +86,11 @@ class MessageRouterInterface * and when messages arrive from AVS. */ virtual void setObserver(std::shared_ptr observer) = 0; + + /** + * Destructor. + */ + virtual ~MessageRouterInterface() = default; }; inline MessageRouterInterface::MessageRouterInterface(const std::string& name) : RequiresShutdown(name) { diff --git a/ACL/include/ACL/Transport/MessageRouterObserverInterface.h b/ACL/include/ACL/Transport/MessageRouterObserverInterface.h index a39a5d53bf..3509a9985f 100644 --- a/ACL/include/ACL/Transport/MessageRouterObserverInterface.h +++ b/ACL/include/ACL/Transport/MessageRouterObserverInterface.h @@ -29,6 +29,12 @@ namespace acl { * or when a message arrives from AVS. */ class MessageRouterObserverInterface { +public: + /** + * Destructor. + */ + virtual ~MessageRouterObserverInterface() = default; + private: /** * This function will be called when the connection status changes. diff --git a/ACL/include/ACL/Transport/MimeParser.h b/ACL/include/ACL/Transport/MimeParser.h deleted file mode 100644 index 009e7f81df..0000000000 --- a/ACL/include/ACL/Transport/MimeParser.h +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file - */ -#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIMEPARSER_H_ -#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIMEPARSER_H_ - -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include "ACL/Transport/MessageConsumerInterface.h" - -namespace alexaClientSDK { -namespace acl { - -class MimeParser { -public: - /** - * Values that express the result of a @c feed() call. - */ - enum class DataParsedStatus { - /// The most recent chunk of data was parsed ok. - OK, - /// The most recent chunk of data was not fully processed. - INCOMPLETE, - /// There was a problem handling the most recent chunk of data. - ERROR - }; - - /** - * Constructor. - * - * @param messageConsumer The MessageConsumerInterface which should receive messages from AVS. - * @param attachmentManager The attachment manager that manages the attachment. - */ - MimeParser( - std::shared_ptr messageConsumer, - std::shared_ptr attachmentManager); - - /** - * Resets class for use in another transfer. - */ - void reset(); - - /** - * Feeds chunk of MIME multipart stream into the underlying MIME multipart parser. - * @param data pointer to chunk of data. - * @param length length of data to feed. - * @return A value expressing the final status of the read operation. - */ - DataParsedStatus feed(char* data, size_t length); - - /** - * Set the context ID to use when creating attachments. - * - * @param attachmentContextId The context ID to use when creating attachments. - */ - void setAttachmentContextId(const std::string& attachmentContextId); - - /** - * Sets the MIME multipart boundary string that the underlying mime multipart parser - * uses. - * @param boundaryString The MIME multipart boundary string - */ - void setBoundaryString(const std::string& boundaryString); - - /** - * Utility function to get the MessageConsumer object that the MimeParser is using. - * The returned parameter's lifetime is guaranteed to be valid for the lifetime of the MimeParser object. - * @return The MessageConsumer object being used by the MimeParser. - */ - std::shared_ptr getMessageConsumer(); - - /** - * A utility function to close the currently active attachment writer, if there is one. - */ - void closeActiveAttachmentWriter(); - -private: - enum ContentType { - /// The default value, indicating no data. - NONE, - - /// The content represents a JSON formatted string. - JSON, - - /// The content represents binary data. - ATTACHMENT - }; - - /** - * Callback that gets called when a multipart MIME part begins - * @param headers The MIME headers for the upcoming MIME part - * @param user A pointer to user set data (should always be an instance of this class) - */ - static void partBeginCallback(const MultipartHeaders& headers, void* userData); - - /** - * Callback that gets called when data from a MIME part is available - * @param buffer A pointer to the chunk of data provided - * @param size The size of the data provided - * @param user A pointer to user set data (should always be an instance of this class) - */ - static void partDataCallback(const char* buffer, size_t size, void* userData); - - /** - * Callback that gets called when a multipart MIME part ends - * @param user A pointer to user set data (should always be an instance of this class) - */ - static void partEndCallback(void* userData); - - /** - * Utility function to encapsulate the logic required to write data to an attachment. - * - * @param buffer The data to be written to the attachment. - * @param size The size of the data to be written to the attachment. - * @return A value expressing the final status of the write operation. - */ - MimeParser::DataParsedStatus writeDataToAttachment(const char* buffer, size_t size); - - /** - * Utility function to determine if the given number of bytes has been processed already by this mime parser - * in a previous iteration. This follows the idea that when processing n bytes, a mime parse may succeed - * breaking out the first mime part, but fail on the second. The caller may want to re-drive the same data in - * order to parse all the data, and so the first bytes should not be re-processed. This function clarifies if, - * given the current state of the parser, these bytes have been already processed. - * - * @param size The number of bytes that can be processed. - * @return Whether any of these bytes have yet to be processed. - */ - bool shouldProcessBytes(size_t size) const; - - /** - * Function to update the parser's state to capture that size bytes have been successfully processed. - * - * @param size The number of bytes that have been processed. - */ - void updateCurrentByteProgress(size_t size); - - /** - * Function to reset the tracking byte counters of the mime parser, which should be called after successfully - * parsing a chunk of data. - */ - void resetByteProgressCounters(); - - /** - * Remember if the attachment writer's buffer is full. - * - * @param isFull Whether the attachment writer's buffer is full. - **/ - void setAttachmentWriterBufferFull(bool isFull); - - /// Tracks whether we've received our first block of data in the stream. - bool m_receivedFirstChunk; - /// Tracks the Content-Type of the current MIME part. - ContentType m_currDataType; - /// Instance of a multipart MIME reader. - MultipartReader m_multipartReader; - /// The object to report back to when JSON MIME parts are received. - std::shared_ptr m_messageConsumer; - /// The attachment manager. - std::shared_ptr m_attachmentManager; - /// The contextId, needed for creating attachments. - std::string m_attachmentContextId; - /** - * The directive message being received from AVS by this stream. It may be built up over several calls if either - * the write quantums are small, or if the message is long. - */ - std::string m_directiveBeingReceived; - /** - * The attachment id of the attachment currently being processed. This variable is needed to prevent duplicate - * creation of @c Attachment objects when data is re-driven. - */ - std::string m_attachmentIdBeingReceived; - /// The current AttachmentWriter. - std::unique_ptr m_attachmentWriter; - /** - * The status of the last feed() call. This is required as a class data member because the callback functions - * this class provides to MultiPartReader do not allow for a return value of our choosing. - */ - DataParsedStatus m_dataParsedStatus; - /** - * In the context of pause & re-drive of the same set of data, this value reflects the current progress of the - * mime parser over that data. - */ - size_t m_currentByteProgress; - /** - * In the context of pause & re-drive of the same set of data, this value reflects how many of those bytes - * have been successfully processed by the parser on any iteration. On a re-drive of the same data, these bytes - * should not be re-processed. - */ - size_t m_totalSuccessfullyProcessedBytes; - /// Records whether the attachment writer's buffer appears to be full. - bool m_isAttachmentWriterBufferFull; -}; - -/** - * Write a @c DataParsedStatus value to an @c ostream as a string. - * - * @param stream The stream to write the value to. - * @param status The status value to write to the @c ostream as a string. - * @return The @c ostream that was passed in and written to. - */ -inline std::ostream& operator<<(std::ostream& stream, MimeParser::DataParsedStatus status) { - switch (status) { - case MimeParser::DataParsedStatus::OK: - stream << "OK"; - break; - case MimeParser::DataParsedStatus::INCOMPLETE: - stream << "INCOMPLETE"; - break; - case MimeParser::DataParsedStatus::ERROR: - stream << "ERROR"; - break; - } - return stream; -} - -} // namespace acl -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIMEPARSER_H_ diff --git a/ACL/include/ACL/Transport/MimeResponseSink.h b/ACL/include/ACL/Transport/MimeResponseSink.h new file mode 100644 index 0000000000..a0ea4a1ff7 --- /dev/null +++ b/ACL/include/ACL/Transport/MimeResponseSink.h @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIMERESPONSESINK_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIMERESPONSESINK_H_ + +#include + +#include +#include +#include + +#include "ACL/Transport/MessageConsumerInterface.h" +#include "ACL/Transport/MimeResponseStatusHandlerInterface.h" + +namespace alexaClientSDK { +namespace acl { + +/** + * Handle Mime encoded responses from AVS. + * + * This includes forwarding json payloads to an @c MessageConsumer, attachments to attachment writers, and + * capturing exceptions for non 2xx results. + */ +class MimeResponseSink : public avsCommon::utils::http2::HTTP2MimeResponseSinkInterface { +public: + /** + * Constructor. + * + * @param handler The object to forward status and result notifications to. + * @param messageConsumer Object to send decoded messages to. + * @param attachmentManager Object with which to get attachments to write to. + * @param attachmentContextId Id added to content IDs to assure global uniqueness. + */ + MimeResponseSink( + std::shared_ptr handler, + std::shared_ptr messageConsumer, + std::shared_ptr attachmentManager, + std::string attachmentContextId); + + /** + * Destructor. + */ + virtual ~MimeResponseSink() = default; + + /// @name HTTP2MimeResponseSinkInterface methods + /// @{ + bool onReceiveResponseCode(long responseCode) override; + bool onReceiveHeaderLine(const std::string& line) override; + bool onBeginMimePart(const std::multimap& headers) override; + avsCommon::utils::http2::HTTP2ReceiveDataStatus onReceiveMimeData(const char* bytes, size_t size) override; + bool onEndMimePart() override; + avsCommon::utils::http2::HTTP2ReceiveDataStatus onReceiveNonMimeData(const char* bytes, size_t size) override; + void onResponseFinished(avsCommon::utils::http2::HTTP2ResponseFinishedStatus status) override; + /// @} + +private: + /// Types of mime parts + enum ContentType { + /// The default value, indicating no data. + NONE, + /// The content represents a JSON formatted string. + JSON, + /// The content represents binary data. + ATTACHMENT + }; + + /** + * Write received data to the currently accumulating attachment. + * + * @param bytes Pointer to the bytes to write. + * @param size Number of bytes to write. + * @return Status of the operation. @see HTTP2ReceiveDataStatus. + */ + avsCommon::utils::http2::HTTP2ReceiveDataStatus writeToAttachment(const char* bytes, size_t size); + + /// The handler to forward status to. + std::shared_ptr m_handler; + + /// The object to send decoded messages to. + std::shared_ptr m_messageConsumer; + + /// The attachment manager. + std::shared_ptr m_attachmentManager; + + /// Type of content in the current part. + ContentType m_contentType; + + /// The contextId, needed for creating attachments. + std::string m_attachmentContextId; + + /** + * The directive message being received from AVS by this stream. It may be built up over several calls if either + * the write quantums are small, or if the message is long. + */ + std::string m_directiveBeingReceived; + + /** + * The attachment id of the attachment currently being processed. This variable is needed to prevent duplicate + * creation of @c Attachment objects when data is re-driven. + */ + std::string m_attachmentIdBeingReceived; + + /// The current AttachmentWriter. + std::unique_ptr m_attachmentWriter; + + /// Non-mime response body acculumulated for response codes other than HTTPResponseCode::SUCCESS_OK. + std::string m_nonMimeBody; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIMERESPONSESINK_H_ diff --git a/ACL/include/ACL/Transport/MimeResponseStatusHandlerInterface.h b/ACL/include/ACL/Transport/MimeResponseStatusHandlerInterface.h new file mode 100644 index 0000000000..8930a4920b --- /dev/null +++ b/ACL/include/ACL/Transport/MimeResponseStatusHandlerInterface.h @@ -0,0 +1,70 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIMERESPONSESTATUSHANDLERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIMERESPONSESTATUSHANDLERINTERFACE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace acl { + +/** + * Handle HTTP response codes and finished notifications for mime encoded responses from AVS. + */ +class MimeResponseStatusHandlerInterface { +public: + /** + * Destructor. + */ + virtual ~MimeResponseStatusHandlerInterface() = default; + + /** + * Notification of network activity between this client and AVS. + * (this is used to detect sustained inactivity requiring the send of a ping). + */ + virtual void onActivity() = 0; + + /** + * Notification that an HTTP response code was returned for the request. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param responseCode The response code received for the request. + * @return Whether receipt of the response should continue. + */ + virtual bool onReceiveResponseCode(long responseCode) = 0; + + /** + * Notification that the request/response cycle has finished and no further notifications will be provided. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param status The status included in the response. + * @param nonMimeBody The body of the reply (for non HTTPResponseCode::SUCCESS_OK responses) + */ + virtual void onResponseFinished( + avsCommon::utils::http2::HTTP2ResponseFinishedStatus status, + const std::string& nonMimeBody) = 0; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIMERESPONSESTATUSHANDLERINTERFACE_H_ diff --git a/ACL/include/ACL/Transport/PingHandler.h b/ACL/include/ACL/Transport/PingHandler.h new file mode 100644 index 0000000000..7bd33345b5 --- /dev/null +++ b/ACL/include/ACL/Transport/PingHandler.h @@ -0,0 +1,87 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_PINGHANDLER_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_PINGHANDLER_H_ + +#include + +#include +#include + +#include "ACL/Transport/ExchangeHandler.h" + +/** + * Handle HTTP2 request / response for a ping sent to AVS. + */ +namespace alexaClientSDK { +namespace acl { + +class PingHandler + : public acl::ExchangeHandler + , public avsCommon::utils::http2::HTTP2RequestSourceInterface + , public avsCommon::utils::http2::HTTP2ResponseSinkInterface + , public std::enable_shared_from_this { +public: + /** + * Create a PingHandler and send the ping request. + * + * @param context The ExchangeContext in which this ping handler will operate. + * @param authToken The token to use to authorize the request. + * @return A new PingHandler or nullptr if the operation fails. + */ + static std::shared_ptr create( + std::shared_ptr context, + const std::string& authToken); + +private: + /** + * Constructor. + * + * @param context The ExchangeContext in which this ping handler will operate. + * @param authToken The token to use to authorize the request. + */ + PingHandler(std::shared_ptr context, const std::string& authToken); + + /** + * Report that the ping was acknowledged. + */ + void reportPingAcknowledged(); + + /// @name HTTP2RequestSourceInterface methods + /// @{ + std::vector getRequestHeaderLines() override; + avsCommon::utils::http2::HTTP2SendDataResult onSendData(char* bytes, size_t size) override; + /// @} + + /// @name HTTP2ResponseSinkInterface methods + /// @{ + bool onReceiveResponseCode(long responseCode) override; + bool onReceiveHeaderLine(const std::string& line) override; + avsCommon::utils::http2::HTTP2ReceiveDataStatus onReceiveData(const char* bytes, size_t size) override; + void onResponseFinished(avsCommon::utils::http2::HTTP2ResponseFinishedStatus status) override; + /// @} + + /// Whether acknowledge of this ping request was reported. + bool m_wasPingAcknowledgedReported; + + /// Response code received through @c onReciveResponseCode (or zero). + long m_responseCode; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_PINGHANDLER_H_ diff --git a/ACL/include/ACL/Transport/PostConnectFactoryInterface.h b/ACL/include/ACL/Transport/PostConnectFactoryInterface.h new file mode 100644 index 0000000000..d5daeea5f0 --- /dev/null +++ b/ACL/include/ACL/Transport/PostConnectFactoryInterface.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTFACTORYINTERFACE_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTFACTORYINTERFACE_H_ + +#include + +#include "ACL/Transport/PostConnectInterface.h" + +namespace alexaClientSDK { +namespace acl { + +/** + * Interface for creating post-connect objects which should be used to perform activities after a + * connection is established. + */ +class PostConnectFactoryInterface { +public: + /** + * Destructor. + */ + virtual ~PostConnectFactoryInterface() = default; + + /** + * Create an instance of @c PostConnectInterface. + * + * @return An instance of @c PostConnectInterface. + */ + virtual std::shared_ptr createPostConnect() = 0; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTFACTORYINTERFACE_H_ diff --git a/ACL/include/ACL/Transport/PostConnectInterface.h b/ACL/include/ACL/Transport/PostConnectInterface.h new file mode 100644 index 0000000000..f893dc681d --- /dev/null +++ b/ACL/include/ACL/Transport/PostConnectInterface.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTINTERFACE_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTINTERFACE_H_ + +#include + +#include "ACL/Transport/PostConnectObserverInterface.h" +#include "ACL/Transport/PostConnectSendMessageInterface.h" + +namespace alexaClientSDK { +namespace acl { + +class HTTP2Transport; + +/** + * Interface for post-connect objects which should be used to perform activities after a connection is established. + */ +class PostConnectInterface { +public: + /** + * The main method which is responsible for doing the PostConnect action + * of the specific PostConnect object type. + * + * @param transport The transport to which the post connect is associated.. + * + * @return A boolean to indicate that the post connect process has been successfully initiated + */ + virtual bool doPostConnect(std::shared_ptr transport) = 0; + + /** + * Handle notification that the connection has been lost. + */ + virtual void onDisconnect() = 0; + + /** + * PostConnectInterface destructor + */ + virtual ~PostConnectInterface() = default; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTINTERFACE_H_ diff --git a/ACL/include/ACL/Transport/PostConnectObject.h b/ACL/include/ACL/Transport/PostConnectObject.h deleted file mode 100644 index ccbc92008e..0000000000 --- a/ACL/include/ACL/Transport/PostConnectObject.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTOBJECT_H_ -#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTOBJECT_H_ - -#include - -#include -#include -#include "ACL/Transport/PostConnectObserverInterface.h" -#include "ACL/Transport/PostConnectSendMessageInterface.h" -#include "ACL/Transport/TransportInterface.h" - -namespace alexaClientSDK { -namespace acl { - -class HTTP2Transport; - -/** - * Base class for post-connect objects. - */ -class PostConnectObject : public avsCommon::utils::RequiresShutdown { -public: - /** - * Static method to initialize the ContextManager. - * - * @param contextManager is the context manager to initialize with. - */ - static void init(std::shared_ptr contextManager); - - /** - * This method currently creates only objects of PostConnectSynchronizer. - * - * @return a shared pointer to PostConnectObject. - */ - static std::shared_ptr create(); - - /** - * The main method which is responsible for doing the PostConnect action - * of the specific PostConnect object type. - * - * @param transport The transport to which the post connect is associated.. - */ - virtual bool doPostConnect(std::shared_ptr transport) = 0; - - /** - * Method to add observers for PostConnectObserverInterface. - * - * @param observer The observer object to add. - */ - virtual void addObserver(std::shared_ptr observer) = 0; - - /** - * Method to remove observers for PostConnectObserverInterface. - * - * @param observer The observer object to remove. - */ - virtual void removeObserver(std::shared_ptr observer) = 0; - - /// static instance of the ContextManager set during intialization. - static std::shared_ptr m_contextManager; - -private: - /** - * Method to notify observers of PostConnectObserverInterface. - */ - virtual void notifyObservers() = 0; - -protected: - /** - * PostConnectObject Constructor. - */ - PostConnectObject(); -}; - -} // namespace acl -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTOBJECT_H_ diff --git a/ACL/include/ACL/Transport/PostConnectSynchronizer.h b/ACL/include/ACL/Transport/PostConnectSynchronizer.h index 8108ac052c..f1ad0a67f3 100644 --- a/ACL/include/ACL/Transport/PostConnectSynchronizer.h +++ b/ACL/include/ACL/Transport/PostConnectSynchronizer.h @@ -20,16 +20,16 @@ #include #include #include -#include -#include "ACL/Transport/PostConnectObject.h" -#include "ACL/Transport/PostConnectSendMessageInterface.h" -#include "ACL/Transport/TransportObserverInterface.h" #include #include #include #include +#include "ACL/Transport/PostConnectInterface.h" +#include "ACL/Transport/PostConnectSendMessageInterface.h" +#include "ACL/Transport/TransportObserverInterface.h" + namespace alexaClientSDK { namespace acl { @@ -39,91 +39,134 @@ class HTTP2Transport; * Class that posts the StateSynchronizer message to the AVS. */ class PostConnectSynchronizer - : public PostConnectObject + : public PostConnectInterface , public avsCommon::sdkInterfaces::ContextRequesterInterface , public avsCommon::sdkInterfaces::MessageRequestObserverInterface - , public TransportObserverInterface , public std::enable_shared_from_this { public: /** - * PostConnectSynchronizer Constructor. + * Creates a PostConnectSynchronizer object. + * + * @param The @c ContextManager which will provide the context for the @c SynchronizeState event. + * @return a shared pointer to PostConnectSynchronizer if all given parameters are valid, otherwise return nullptr. */ - PostConnectSynchronizer(); + static std::shared_ptr create( + std::shared_ptr contextManager); + ~PostConnectSynchronizer(); + + /// @name PostConnectInterface methods + /// @{ bool doPostConnect(std::shared_ptr transport) override; + void onDisconnect() override; + ///@} + /// @name ContextRequesterInterface methods + /// @{ + void onContextAvailable(const std::string& jsonContext) override; + void onContextFailure(const avsCommon::sdkInterfaces::ContextRequestError error) override; + /// @} + + /// @name MessageRequestObserverInterface methods + /// @{ void onSendCompleted(MessageRequestObserverInterface::Status status) override; void onExceptionReceived(const std::string& exceptionMessage) override; + /// @} - void onContextAvailable(const std::string& jsonContext) override; - void onContextFailure(const avsCommon::sdkInterfaces::ContextRequestError error) override; +private: + /** + * Enum specifying the various states in a @c PostConnectSychronizer's lifecycle. + */ + enum class State { + /// @c mainLoop() is not running. + IDLE, - void onServerSideDisconnect(std::shared_ptr transport) override; - void onConnected(std::shared_ptr transport) override; - void onDisconnected( - std::shared_ptr transport, - avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override; + /// @c mainLoop() is running, no @c GetContext() request or message send is in progress. + RUNNING, - void addObserver(std::shared_ptr observer) override; - void removeObserver(std::shared_ptr observer) override; + /// @c mainLoop() is running, and a @c getContext() request is in progress. + FETCHING, -private: - void doShutdown() override; + /// @c mainLoop() is running, and a message send is in progress. + SENDING, + + /// @c mainLoop() is running, but has been instructed to stop. + STOPPING, - void notifyObservers() override; + /// @ mainLoop has exited. + STOPPED + }; + + /// Friend to allow access to enum class @c State. + friend std::ostream& operator<<(std::ostream& stream, State state); /** - * Loop to fetch the context and send a post-connect message to AVS. + * PostConnectSynchronizer Constructor. + * + * @param contextManager The @c ContextManager which will provide the context for the @c SynchronizeState event. */ - void postConnectLoop(); + PostConnectSynchronizer(std::shared_ptr contextManager); /** - * Thread safe method which returns if the post-connect object. - * is being stopped. + * Set the next state of this @c PostConnectSynchrnizer. + * + * @param state The new state to transition to. + * @return Whether or not the transition was allowed. */ - bool isStopping(); + bool setState(State state); /** - * Thread safe method which returns if the post-connect object. - * is post-connected. + * Set the next state of this @c PostConnectSynchrnizer. + * @note m_mutex must be held when this method is called. + * + * @param state The new state to transition to. + * @return Whether or not the transition was allowed. */ - bool isPostConnected(); + bool setStateLocked(State state); /** - * Set when a context fetch is in progress and reset when the context fetch fails or when the send - * of SynchronizeState event fails needing another context fetch. + * Loop to fetch the context and send a post-connect message to AVS - or retry if either fails. */ - bool m_contextFetchInProgress; - - /// Set after the postConnection is completed. - bool m_isPostConnected; + void mainLoop(); /** - * Set when the post-connect object is stopped. This can happen for the following reasons tranport - * disconnects, transport receives a server side disconnect or the AVSConnectManageer is shutdown. + * Stop mainLoop(). This method blocks until mainLoop() exits or the operation fails. + * + * @return Whether the operation was successful. */ - bool m_isStopping; - - /// bool to identfy if the post-connect loop is still running. - bool m_postConnectThreadRunning; - - /// Thread which runs the state synchronization operation. - std::thread m_postConnectThread; + bool stop(); - /// Mutex to protect the m_observers variable. - std::mutex m_observerMutex; + /** + * Get @c m_transport in a thread-safe manner. + * + * @return The current value of m_transport. + */ + std::shared_ptr getTransport(); - /// Unordered set of registered observers. - std::unordered_set> m_observers; + /** + * Set the value of @c m_transport. + * + * @param transport The new value for @c m_transport. + */ + void setTransport(std::shared_ptr transport); /// Serializes access to members. std::mutex m_mutex; + /// The current state of this @c PostConnectSynchronizer. + State m_state; + /// Condition variable on which m_postConnectThread waits. - std::condition_variable m_wakeRetryTrigger; + std::condition_variable m_wakeTrigger; /// The HTTP2Transport object to which the StateSynchronizer message is sent. std::shared_ptr m_transport; + + /// The context manager that provides application context in the post connect loop. + std::shared_ptr m_contextManager; + + /// Thread upon which @c mainLoop() runs. + std::thread m_mainLoopThread; }; } // namespace acl diff --git a/ACL/include/ACL/Transport/PostConnectSynchronizerFactory.h b/ACL/include/ACL/Transport/PostConnectSynchronizerFactory.h new file mode 100644 index 0000000000..39c3d98370 --- /dev/null +++ b/ACL/include/ACL/Transport/PostConnectSynchronizerFactory.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTSYNCHRONIZERFACTORY_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTSYNCHRONIZERFACTORY_H_ + +#include + +#include + +#include "ACL/Transport/PostConnectFactoryInterface.h" + +namespace alexaClientSDK { +namespace acl { + +/** + * Factory for creating PostConnectSynchronizer instances. + */ +class PostConnectSynchronizerFactory : public PostConnectFactoryInterface { +public: + /** + * Create a PostConnectSynchronizerFactory instance. + * + * @param contextManager The @c ContextManager which will provide the context for @c SynchronizeState events. + * @return A PostConnectSynchronizer instance. + */ + static std::shared_ptr create( + std::shared_ptr contextManager); + + /// @name PostConnectFactoryInterface methods + /// @{ + std::shared_ptr createPostConnect() override; + /// @} + +private: + /** + * Constructor. + * + * @param contextManager The @c ContextManager which will provide the context for @c SynchronizeState events. + */ + PostConnectSynchronizerFactory(std::shared_ptr contextManager); + + /// The @c ContextManager which will provide the context for @c SynchronizeState events. + std::shared_ptr m_contextManager; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_POSTCONNECTSYNCHRONIZERFACTORY_H_ diff --git a/ACL/include/ACL/Transport/TransportFactoryInterface.h b/ACL/include/ACL/Transport/TransportFactoryInterface.h new file mode 100644 index 0000000000..d15979cdef --- /dev/null +++ b/ACL/include/ACL/Transport/TransportFactoryInterface.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_TRANSPORTFACTORYINTERFACE_H_ +#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_TRANSPORTFACTORYINTERFACE_H_ + +#include +#include + +#include +#include + +#include "ACL/Transport/TransportInterface.h" +#include "ACL/Transport/MessageConsumerInterface.h" +#include "ACL/Transport/TransportObserverInterface.h" + +namespace alexaClientSDK { +namespace acl { + +/** + * This is the interface for the transport factory + */ +class TransportFactoryInterface { +public: + /** + * Creates a new transport. + * + * @param authDelegate The AuthDelegateInterface to use for authentication and authorization with AVS. + * @param avsEndpoint The URL for the AVS server we will connect to. + * @param messageConsumerInterface The object which should be notified on messages which arrive from AVS. + * @param transportObserverInterface A pointer to the transport observer the new transport should notify. + * @return A new MessageRouter object. + */ + virtual std::shared_ptr createTransport( + std::shared_ptr authDelegate, + std::shared_ptr attachmentManager, + const std::string& avsEndpoint, + std::shared_ptr messageConsumerInterface, + std::shared_ptr transportObserverInterface) = 0; + + /** + * Destructor. + */ + virtual ~TransportFactoryInterface() = default; +}; + +} // namespace acl +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_TRANSPORTFACTORYINTERFACE_H_ diff --git a/ACL/include/ACL/Transport/TransportInterface.h b/ACL/include/ACL/Transport/TransportInterface.h index 04c7695ee7..7dd0ee64af 100644 --- a/ACL/include/ACL/Transport/TransportInterface.h +++ b/ACL/include/ACL/Transport/TransportInterface.h @@ -63,6 +63,25 @@ class TransportInterface : public avsCommon::utils::RequiresShutdown { * @param request The requested message. */ virtual void send(std::shared_ptr request) = 0; + + /** + * Deleted copy constructor + * + * @param rhs The 'right hand side' to not copy. + */ + TransportInterface(const TransportInterface& rhs) = delete; + + /** + * Deleted assignment operator + * + * @param rhs The 'right hand side' to not copy. + */ + TransportInterface& operator=(const TransportInterface& rhs) = delete; + + /** + * Destructor. + */ + virtual ~TransportInterface() = default; }; inline TransportInterface::TransportInterface() : RequiresShutdown{"TransportInterface"} { diff --git a/ACL/src/AVSConnectionManager.cpp b/ACL/src/AVSConnectionManager.cpp index 9eaa2664d7..ee14c568a8 100644 --- a/ACL/src/AVSConnectionManager.cpp +++ b/ACL/src/AVSConnectionManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -39,7 +39,8 @@ std::shared_ptr AVSConnectionManager::create( std::shared_ptr messageRouter, bool isEnabled, std::unordered_set> connectionStatusObservers, - std::unordered_set> messageObservers) { + std::unordered_set> messageObservers, + std::shared_ptr internetConnectionMonitor) { if (!avsCommon::avs::initialization::AlexaClientSDKInit::isInitialized()) { ACSDK_ERROR(LX("createFailed").d("reason", "uninitialziedAlexaClientSdk").d("return", "nullptr")); return nullptr; @@ -64,8 +65,8 @@ std::shared_ptr AVSConnectionManager::create( } } - auto connectionManager = std::shared_ptr( - new AVSConnectionManager(messageRouter, connectionStatusObservers, messageObservers)); + auto connectionManager = std::shared_ptr(new AVSConnectionManager( + messageRouter, connectionStatusObservers, messageObservers, internetConnectionMonitor)); messageRouter->setObserver(connectionManager); @@ -73,21 +74,32 @@ std::shared_ptr AVSConnectionManager::create( connectionManager->enable(); } + if (internetConnectionMonitor) { + ACSDK_DEBUG5(LX(__func__).m("Subscribing to InternetConnectionMonitor Callbacks")); + internetConnectionMonitor->addInternetConnectionObserver(connectionManager); + } + return connectionManager; } AVSConnectionManager::AVSConnectionManager( std::shared_ptr messageRouter, std::unordered_set> connectionStatusObservers, - std::unordered_set> messageObservers) : - AbstractConnection{connectionStatusObservers}, + std::unordered_set> messageObservers, + std::shared_ptr internetConnectionMonitor) : + AbstractAVSConnectionManager{connectionStatusObservers}, RequiresShutdown{"AVSConnectionManager"}, m_isEnabled{false}, m_messageObservers{messageObservers}, - m_messageRouter{messageRouter} { + m_messageRouter{messageRouter}, + m_internetConnectionMonitor{internetConnectionMonitor} { } void AVSConnectionManager::doShutdown() { + if (m_internetConnectionMonitor) { + m_internetConnectionMonitor->removeInternetConnectionObserver(shared_from_this()); + } + disable(); clearObservers(); { @@ -98,20 +110,27 @@ void AVSConnectionManager::doShutdown() { } void AVSConnectionManager::enable() { + std::lock_guard lock(m_isEnabledMutex); + ACSDK_DEBUG5(LX(__func__)); m_isEnabled = true; m_messageRouter->enable(); } void AVSConnectionManager::disable() { + std::lock_guard lock(m_isEnabledMutex); + ACSDK_DEBUG5(LX(__func__)); m_isEnabled = false; m_messageRouter->disable(); } bool AVSConnectionManager::isEnabled() { + std::lock_guard lock(m_isEnabledMutex); return m_isEnabled; } void AVSConnectionManager::reconnect() { + std::lock_guard lock(m_isEnabledMutex); + ACSDK_DEBUG5(LX(__func__).d("isEnabled", m_isEnabled)); if (m_isEnabled) { m_messageRouter->disable(); m_messageRouter->enable(); @@ -129,6 +148,12 @@ bool AVSConnectionManager::isConnected() const { void AVSConnectionManager::setAVSEndpoint(const std::string& avsEndpoint) { m_messageRouter->setAVSEndpoint(avsEndpoint); } +void AVSConnectionManager::onConnectionStatusChanged(bool connected) { + ACSDK_DEBUG5(LX(__func__).d("connected", connected)); + if (!connected) { + reconnect(); + } +} void AVSConnectionManager::addMessageObserver( std::shared_ptr observer) { diff --git a/ACL/src/Transport/DownchannelHandler.cpp b/ACL/src/Transport/DownchannelHandler.cpp new file mode 100644 index 0000000000..f49be637ae --- /dev/null +++ b/ACL/src/Transport/DownchannelHandler.cpp @@ -0,0 +1,138 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include + +#include "ACL/Transport/DownchannelHandler.h" +#include "ACL/Transport/HTTP2Transport.h" +#include "ACL/Transport/MimeResponseSink.h" + +namespace alexaClientSDK { +namespace acl { + +using namespace avsCommon::utils::http; +using namespace avsCommon::utils::http2; + +/// Downchannel URL +const static std::string AVS_DOWNCHANNEL_URL_PATH_EXTENSION = "/v20160207/directives"; + +/// Prefix for the ID of downchannel requests. +static const std::string DOWNCHANNEL_ID_PREFIX = "AVSDownChannel-"; + +/// How long to wait for a downchannel request to connect to AVS. +static const std::chrono::seconds ESTABLISH_CONNECTION_TIMEOUT = std::chrono::seconds(60); + +/// String to identify log entries originating from this file. +static const std::string TAG("DownchannelHandler"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +std::shared_ptr DownchannelHandler::create( + std::shared_ptr context, + const std::string& authToken, + std::shared_ptr messageConsumer, + std::shared_ptr attachmentManager) { + ACSDK_DEBUG9(LX(__func__).d("context", context.get())); + + if (!context) { + ACSDK_CRITICAL(LX("createFailed").d("reason", "nullHttp2Transport")); + return nullptr; + } + + if (authToken.empty()) { + ACSDK_DEBUG9(LX("createFailed").d("reason", "emptyAuthToken")); + return nullptr; + } + + std::shared_ptr handler(new DownchannelHandler(context, authToken)); + + HTTP2RequestConfig cfg{ + HTTP2RequestType::GET, context->getEndpoint() + AVS_DOWNCHANNEL_URL_PATH_EXTENSION, DOWNCHANNEL_ID_PREFIX}; + cfg.setRequestSource(handler); + cfg.setResponseSink(std::make_shared( + std::make_shared(handler, messageConsumer, attachmentManager, cfg.getId()))); + cfg.setConnectionTimeout(ESTABLISH_CONNECTION_TIMEOUT); + cfg.setIntermittentTransferExpected(); + + auto request = context->createAndSendRequest(cfg); + + if (!request) { + ACSDK_ERROR(LX("createFailed").d("reason", "createAndSendRequestFailed")); + return nullptr; + } + + return handler; +} + +std::vector DownchannelHandler::getRequestHeaderLines() { + ACSDK_DEBUG9(LX(__func__)); + return {m_authHeader}; +} + +HTTP2SendDataResult DownchannelHandler::onSendData(char* bytes, size_t size) { + ACSDK_DEBUG9(LX(__func__).d("size", size)); + return HTTP2SendDataResult::COMPLETE; +} + +DownchannelHandler::DownchannelHandler( + std::shared_ptr context, + const std::string& authToken) : + ExchangeHandler{context, authToken} { + ACSDK_DEBUG9(LX(__func__).d("context", context.get())); +} + +void DownchannelHandler::onActivity() { + m_context->onActivity(); +} + +bool DownchannelHandler::onReceiveResponseCode(long responseCode) { + ACSDK_DEBUG5(LX(__func__).d("responseCode", responseCode)); + switch (intToHTTPResponseCode(responseCode)) { + case HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED: + case HTTPResponseCode::SUCCESS_NO_CONTENT: + case HTTPResponseCode::SUCCESS_END_CODE: + case HTTPResponseCode::REDIRECTION_MULTIPLE_CHOICES: + case HTTPResponseCode::REDIRECTION_MOVED_PERMANENTLY: + case HTTPResponseCode::REDIRECTION_FOUND: + case HTTPResponseCode::REDIRECTION_SEE_ANOTHER: + case HTTPResponseCode::REDIRECTION_TEMPORARY_REDIRECT: + case HTTPResponseCode::REDIRECTION_PERMANENT_REDIRECT: + case HTTPResponseCode::CLIENT_ERROR_BAD_REQUEST: + case HTTPResponseCode::SERVER_ERROR_INTERNAL: + break; + case HTTPResponseCode::SUCCESS_OK: + m_context->onDownchannelConnected(); + break; + case HTTPResponseCode::CLIENT_ERROR_FORBIDDEN: + m_context->onForbidden(m_authToken); + break; + } + return true; +} + +void DownchannelHandler::onResponseFinished(HTTP2ResponseFinishedStatus status, const std::string& nonMimeBody) { + ACSDK_DEBUG5(LX(__func__).d("status", status).d("nonMimeBody", nonMimeBody)); + m_context->onDownchannelFinished(); +} + +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/src/Transport/ExchangeHandler.cpp b/ACL/src/Transport/ExchangeHandler.cpp new file mode 100644 index 0000000000..52f6643952 --- /dev/null +++ b/ACL/src/Transport/ExchangeHandler.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include + +#include "ACL/Transport/HTTP2Transport.h" +#include "ACL/Transport/ExchangeHandler.h" + +namespace alexaClientSDK { +namespace acl { + +/// HTTP Header key for the bearer token. +static const std::string AUTHORIZATION_HEADER = "Authorization: Bearer "; + +/// String to identify log entries originating from this file. +static const std::string TAG("ExchangeHandler"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +ExchangeHandler::ExchangeHandler( + std::shared_ptr context, + const std::string& authToken) : + m_context{context}, + m_authToken{authToken}, + m_authHeader{AUTHORIZATION_HEADER + authToken} { + ACSDK_DEBUG5(LX(__func__).d("context", context.get()).sensitive("authToken", authToken)); + if (m_authToken.empty()) { + ACSDK_ERROR(LX(__func__).m("emptyAuthToken")); + } +} + +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/src/Transport/HTTP2MessageRouter.cpp b/ACL/src/Transport/HTTP2MessageRouter.cpp deleted file mode 100644 index d403cfc85b..0000000000 --- a/ACL/src/Transport/HTTP2MessageRouter.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include - -#include "ACL/Transport/HTTP2MessageRouter.h" -#include "ACL/Transport/HTTP2Transport.h" - -namespace alexaClientSDK { -namespace acl { - -using namespace alexaClientSDK::avsCommon::utils; -using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::avs::attachment; - -HTTP2MessageRouter::HTTP2MessageRouter( - std::shared_ptr authDelegate, - std::shared_ptr attachmentManager, - const std::string& avsEndpoint) : - MessageRouter(authDelegate, attachmentManager, avsEndpoint) { -} - -HTTP2MessageRouter::~HTTP2MessageRouter() { -} - -std::shared_ptr HTTP2MessageRouter::createTransport( - std::shared_ptr authDelegate, - std::shared_ptr attachmentManager, - const std::string& avsEndpoint, - std::shared_ptr messageConsumerInterface, - std::shared_ptr transportObserverInterface) { - return HTTP2Transport::create( - authDelegate, avsEndpoint, messageConsumerInterface, attachmentManager, transportObserverInterface); -} - -} // namespace acl -} // namespace alexaClientSDK diff --git a/ACL/src/Transport/HTTP2Stream.cpp b/ACL/src/Transport/HTTP2Stream.cpp deleted file mode 100644 index a0d5533ce3..0000000000 --- a/ACL/src/Transport/HTTP2Stream.cpp +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include -#include - -#include -#include -#include -#include -#include - -#include "ACL/Transport/HTTP2Stream.h" -#include "ACL/Transport/HTTP2Transport.h" - -namespace alexaClientSDK { -namespace acl { - -using namespace alexaClientSDK::avsCommon::utils; -using namespace alexaClientSDK::avsCommon::utils::libcurlUtils; -using namespace avsCommon::avs; -using namespace avsCommon::avs::attachment; - -/// String to identify log entries originating from this file. -static const std::string TAG("HTTP2Stream"); - -/** - * Create a LogEntry using this file's TAG and the specified event string. - * - * @param The event string for this @c LogEntry. - */ -#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) - -/// MIME boundary string prefix in HTTP header. -static const std::string BOUNDARY_PREFIX = "boundary="; -/// Size in chars of the MIME boundary string prefix -static const int BOUNDARY_PREFIX_SIZE = BOUNDARY_PREFIX.size(); -/// MIME HTTP header value delimiter -static const std::string BOUNDARY_DELIMITER = ";"; -/// The HTTP header to pass the LWA token into -static const std::string AUTHORIZATION_HEADER = "Authorization: Bearer "; -/// The POST field name for an attachment -static const std::string ATTACHMENT_FIELD_NAME = "audio"; -/// The POST field name for message metadata -static const std::string METADATA_FIELD_NAME = "metadata"; -/// The prefix for a stream contextId. -static const std::string STREAM_CONTEXT_ID_PREFIX_STRING = "ACL_LOGICAL_HTTP2_STREAM_ID_"; -/// The prefix of request IDs passed back in the header of AVS replies. -static const std::string X_AMZN_REQUESTID_PREFIX = "x-amzn-requestid:"; -/// Key under root configuration node for ACL configuration values. -static const std::string ACL_CONFIGURATION_KEY("acl"); -/// Key under 'acl' configuration node for path/prefix of per-stream log file names. -static const std::string STREAM_LOG_PREFIX_KEY("streamLogPrefix"); -/// Prefix for per-stream log file names. -static const std::string STREAM_LOG_NAME_PREFIX("stream-"); -/// Suffix for per-stream log file names. -static const std::string STREAM_LOG_NAME_SUFFIX(".log"); -/// Suffix for per-stream dump of incoming data. -static const std::string STREAM_IN_DUMP_SUFFIX("-in.bin"); -/// Suffix for per-stream dump of outgoing data. -static const std::string STREAM_OUT_DUMP_SUFFIX("-out.bin"); - -#ifdef DEBUG -/// Carriage return -static const char CR = 0x0D; -#endif - -#ifdef ACSDK_EMIT_CURL_LOGS - -/** - * Macro to simplify building a switch that translates from enum values to strings. - * - * @param name The name of the enum value to translate. - */ -#define ACSDK_TYPE_CASE(name) \ - case name: \ - return #name; - -/** - * Return a string identifying a @c curl_infotype value. - * - * @param type The value to identify - * @return A string identifying the specified @c curl_infotype value. - */ -static const char* curlInfoTypeToString(curl_infotype type) { - switch (type) { - ACSDK_TYPE_CASE(CURLINFO_TEXT) - ACSDK_TYPE_CASE(CURLINFO_HEADER_OUT) - ACSDK_TYPE_CASE(CURLINFO_DATA_OUT) - ACSDK_TYPE_CASE(CURLINFO_SSL_DATA_OUT) - ACSDK_TYPE_CASE(CURLINFO_HEADER_IN) - ACSDK_TYPE_CASE(CURLINFO_DATA_IN) - ACSDK_TYPE_CASE(CURLINFO_SSL_DATA_IN) - ACSDK_TYPE_CASE(CURLINFO_END) - } - return ">>> unknown curl_infotype value <<<"; -} - -#undef ACSDK_TYPE_CASE - -/** - * Return a prefix suitable for the data associated with a @c curl_infotype value. - * - * @param type The type of data to prefix. - * @return The prefix to use for the specified typw of data. - */ -static const char* curlInfoTypeToPrefix(curl_infotype type) { - switch (type) { - case CURLINFO_TEXT: - return "* "; - case CURLINFO_HEADER_OUT: - case CURLINFO_DATA_OUT: - case CURLINFO_SSL_DATA_OUT: - return "> "; - case CURLINFO_HEADER_IN: - case CURLINFO_DATA_IN: - case CURLINFO_SSL_DATA_IN: - return "< "; - case CURLINFO_END: - return ""; - } - return ">>> unknown curl_infotype value <<<"; -} - -#endif // ACSDK_EMIT_CURL_LOGS - -/** - * Get @c std::chrono::steady_clock::now() in a form that can be wrapped in @c atomic. - * - * @return @c std::chrono::steady_clock::now() in a form that can be wrapped in @c atomic. - */ -static std::chrono::steady_clock::rep getNow() { - return std::chrono::steady_clock::now().time_since_epoch().count(); -} - -HTTP2Stream::HTTP2Stream( - std::shared_ptr messageConsumer, - std::shared_ptr attachmentManager) : - m_logicalStreamId{0}, - m_parser{messageConsumer, attachmentManager}, - m_isPaused{false}, - m_progressTimeout{std::chrono::steady_clock::duration::max().count()}, - m_timeOfLastTransfer{getNow()} { -} - -bool HTTP2Stream::reset() { - if (!m_transfer.reset()) { - ACSDK_ERROR(LX("resetFailed").d("reason", "resetHandleFailed")); - return false; - } - m_parser.reset(); - m_currentRequest.reset(); - m_isPaused = false; - m_exceptionBeingProcessed.clear(); - m_progressTimeout = std::chrono::steady_clock::duration::max().count(); - m_timeOfLastTransfer = getNow(); - return true; -} - -bool HTTP2Stream::setCommonOptions(const std::string& url, const std::string& authToken) { -#ifdef ACSDK_EMIT_CURL_LOGS - - if (!(setopt(CURLOPT_DEBUGDATA, "CURLOPT_DEBUGDATA", this) && - setopt(CURLOPT_DEBUGFUNCTION, "CURLOPT_DEBUGFUNCTION", debugFunction) && - setopt(CURLOPT_VERBOSE, "CURLOPT_VERBOSE", 1L))) { - return false; - } - -#endif - - if (!m_transfer.setURL(url)) { - ACSDK_ERROR(LX("setCommonOptionsFailed").d("reason", "setURLFailed").d("url", url)); - return false; - } - - std::ostringstream authHeader; - authHeader << AUTHORIZATION_HEADER << authToken; - if (!m_transfer.addHTTPHeader(authHeader.str())) { - ACSDK_ERROR( - LX("setCommonOptionsFailed").d("reason", "addHTTPHeaderFailed").sensitive("authHeader", authHeader.str())); - return false; - } - - if (!m_transfer.setWriteCallback(&HTTP2Stream::writeCallback, this)) { - ACSDK_ERROR(LX("setCommonOptionsFailed").d("reason", "setWriteCallbackFailed")); - return false; - } - - if (!m_transfer.setHeaderCallback(&HTTP2Stream::headerCallback, this)) { - ACSDK_ERROR(LX("setCommonOptionsFailed").d("reason", "setHeaderCallbackFailed")); - return false; - } - - return setopt(CURLOPT_TCP_KEEPALIVE, "CURLOPT_TCP_KEEPALIVE", 1); -} - -bool HTTP2Stream::initGet(const std::string& url, const std::string& authToken) { - reset(); - initStreamLog(); - - if (url.empty()) { - ACSDK_ERROR(LX("initGetFailed").d("reason", "emptyURL")); - return false; - } - - if (authToken.empty()) { - ACSDK_ERROR(LX("initGetFailed").d("reason", "emptyAuthToken")); - return false; - } - - if (!m_transfer.getCurlHandle()) { - ACSDK_ERROR(LX("initGetFailed").d("reason", "curlEasyHandleUninitialized")); - return false; - } - - if (!m_transfer.setTransferType(avsCommon::utils::libcurlUtils::CurlEasyHandleWrapper::TransferType::kGET)) { - return false; - } - - if (!setCommonOptions(url, authToken)) { - ACSDK_ERROR(LX("initGetFailed").d("reason", "setCommonOptionsFailed")); - return false; - } - - return true; -} - -bool HTTP2Stream::initPost( - const std::string& url, - const std::string& authToken, - std::shared_ptr request) { - reset(); - initStreamLog(); - - std::string requestPayload = request->getJsonContent(); - - if (url.empty()) { - ACSDK_ERROR(LX("initPostFailed").d("reason", "emptyURL")); - return false; - } - - if (authToken.empty()) { - ACSDK_ERROR(LX("initPostFailed").d("reason", "emptyAuthToken")); - return false; - } - - if (!request) { - ACSDK_ERROR(LX("initPostFailed").d("reason", "nullMessageRequest")); - return false; - } - - if (!m_transfer.getCurlHandle()) { - ACSDK_ERROR(LX("initPostFailed").d("reason", "curlEasyHandleUninitialized")); - return false; - } - - if (!m_transfer.setPostContent(METADATA_FIELD_NAME, requestPayload)) { - ACSDK_ERROR(LX("initPostFailed").d("reason", "setPostContentFailed")); - return false; - } - - if (!m_transfer.setReadCallback(HTTP2Stream::readCallback, this)) { - ACSDK_ERROR(LX("initPostFailed").d("reason", "setReadCallbackFailed")); - return false; - } - - if (request->getAttachmentReader()) { - if (!m_transfer.setPostStream(ATTACHMENT_FIELD_NAME, this)) { - ACSDK_ERROR(LX("initPostFailed").d("reason", "setPostStreamFailed")); - return false; - } - } - - if (!m_transfer.setTransferType(avsCommon::utils::libcurlUtils::CurlEasyHandleWrapper::TransferType::kPOST)) { - ACSDK_ERROR(LX("initPostFailed").d("reason", "setTransferTypeFailed")); - return false; - } - - if (!setCommonOptions(url, authToken)) { - ACSDK_ERROR(LX("initPostFailed").d("reason", "setCommonOptionsFailed")); - return false; - } - - m_currentRequest = request; - return true; -} - -size_t HTTP2Stream::writeCallback(char* data, size_t size, size_t nmemb, void* user) { - size_t numChars = size * nmemb; - HTTP2Stream* stream = static_cast(user); - stream->m_timeOfLastTransfer = getNow(); - - /** - * If we get an HTTP 200 response code then we're getting a MIME multipart - * payload. For all other response codes we're getting a JSON string without - * multipart headers. - */ - if (HTTPResponseCode::SUCCESS_OK == stream->getResponseCode()) { - MimeParser::DataParsedStatus status = stream->m_parser.feed(data, numChars); - - if (MimeParser::DataParsedStatus::OK == status) { - return numChars; - } else if (MimeParser::DataParsedStatus::INCOMPLETE == status) { - stream->m_isPaused = true; - return CURL_WRITEFUNC_PAUSE; - } else if (MimeParser::DataParsedStatus::ERROR == status) { - return CURL_READFUNC_ABORT; - } - } else { - stream->m_exceptionBeingProcessed.append(data, numChars); - } - return numChars; -} - -size_t HTTP2Stream::headerCallback(char* data, size_t size, size_t nmemb, void* user) { - if (!user) { - ACSDK_ERROR(LX("headerCallbackFailed").d("reason", "nullUser")); - return 0; - } - size_t headerLength = size * nmemb; - std::string header(data, headerLength); -#ifdef DEBUG - if (0 == header.find(X_AMZN_REQUESTID_PREFIX)) { - auto end = header.find(CR); - ACSDK_DEBUG(LX("receivedRequestId").d("value", header.substr(0, end))); - } -#endif - std::string boundary; - HTTP2Stream* stream = static_cast(user); - stream->m_timeOfLastTransfer = getNow(); - if (HTTPResponseCode::SUCCESS_OK == stream->getResponseCode()) { - if (header.find(BOUNDARY_PREFIX) != std::string::npos) { - boundary = header.substr(header.find(BOUNDARY_PREFIX)); - boundary = boundary.substr(BOUNDARY_PREFIX_SIZE, boundary.find(BOUNDARY_DELIMITER) - BOUNDARY_PREFIX_SIZE); - stream->m_parser.setBoundaryString(boundary); - } - } - return headerLength; -} - -size_t HTTP2Stream::readCallback(char* data, size_t size, size_t nmemb, void* userData) { - if (!userData) { - ACSDK_ERROR(LX("readCallbackFailed").d("reason", "nullUserData")); - return 0; - } - - HTTP2Stream* stream = static_cast(userData); - - stream->m_timeOfLastTransfer = getNow(); - auto attachmentReader = stream->m_currentRequest->getAttachmentReader(); - - // This is ok - it means there's no attachment to send. Return 0 so libcurl can complete the stream to AVS. - if (!attachmentReader) { - return 0; - } - - // Pass the data to libcurl. - const size_t maxBytesToRead = size * nmemb; - auto readStatus = AttachmentReader::ReadStatus::OK; - auto bytesRead = attachmentReader->read(data, maxBytesToRead, &readStatus); - - switch (readStatus) { - // The good cases. - case AttachmentReader::ReadStatus::OK: - case AttachmentReader::ReadStatus::OK_WOULDBLOCK: - case AttachmentReader::ReadStatus::OK_TIMEDOUT: - break; - - // No more data to send - close the stream. - case AttachmentReader::ReadStatus::CLOSED: - return 0; - - // Handle any attachment read errors. - case AttachmentReader::ReadStatus::ERROR_OVERRUN: - case AttachmentReader::ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE: - case AttachmentReader::ReadStatus::ERROR_INTERNAL: - return CURL_READFUNC_ABORT; - } - // The attachment has no more data right now, but is still readable. - if (0 == bytesRead) { - stream->m_isPaused = true; - return CURL_READFUNC_PAUSE; - } - - return bytesRead; -} - -long HTTP2Stream::getResponseCode() { - long responseCode = 0; - CURLcode ret = curl_easy_getinfo(m_transfer.getCurlHandle(), CURLINFO_RESPONSE_CODE, &responseCode); - if (ret != CURLE_OK) { - ACSDK_ERROR(LX("getResponseCodeFailed") - .d("reason", "curlFailure") - .d("method", "curl_easy_getinfo") - .d("info", "CURLINFO_RESPONSE_CODE") - .d("error", curl_easy_strerror(ret))); - return -1; - } - return responseCode; -} - -CURL* HTTP2Stream::getCurlHandle() { - return m_transfer.getCurlHandle(); -} - -void HTTP2Stream::notifyRequestObserver() { - if (m_exceptionBeingProcessed.length() > 0) { - m_currentRequest->exceptionReceived(m_exceptionBeingProcessed); - m_exceptionBeingProcessed = ""; - } - - long responseCode = getResponseCode(); - - switch (responseCode) { - case HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED: - m_currentRequest->sendCompleted( - avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::INTERNAL_ERROR); - break; - case HTTPResponseCode::SUCCESS_OK: - m_currentRequest->sendCompleted(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::SUCCESS); - break; - case HTTPResponseCode::SUCCESS_NO_CONTENT: - m_currentRequest->sendCompleted( - avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::SUCCESS_NO_CONTENT); - break; - case HTTPResponseCode::BAD_REQUEST: - m_currentRequest->sendCompleted( - avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::BAD_REQUEST); - break; - case HTTPResponseCode::SERVER_INTERNAL_ERROR: - m_currentRequest->sendCompleted( - avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::SERVER_INTERNAL_ERROR_V2); - break; - default: - m_currentRequest->sendCompleted( - avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::SERVER_OTHER_ERROR); - } -} - -void HTTP2Stream::notifyRequestObserver(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status status) { - m_currentRequest->sendCompleted(status); -} - -bool HTTP2Stream::setStreamTimeout(const long timeoutSeconds) { - if (!m_transfer.setTransferTimeout(timeoutSeconds)) { - return false; - } - return true; -} - -bool HTTP2Stream::setConnectionTimeout(const std::chrono::seconds timeoutSeconds) { - return m_transfer.setConnectionTimeout(timeoutSeconds); -} - -void HTTP2Stream::unPause() { - m_isPaused = false; - // Call curl_easy_pause() *after* resetting m_pendingBits because curl_easy_pause may call - // readCallback() and/or writeCallback() which can modify m_pendingBits. - curl_easy_pause(getCurlHandle(), CURLPAUSE_CONT); -} - -bool HTTP2Stream::isPaused() const { - return m_isPaused; -} - -void HTTP2Stream::setLogicalStreamId(int logicalStreamId) { - m_logicalStreamId = logicalStreamId; - m_parser.setAttachmentContextId(STREAM_CONTEXT_ID_PREFIX_STRING + std::to_string(m_logicalStreamId)); -} - -unsigned int HTTP2Stream::getLogicalStreamId() const { - return m_logicalStreamId; -} - -bool HTTP2Stream::hasProgressTimedOut() const { - return (getNow() - m_timeOfLastTransfer) > m_progressTimeout; -} - -const avsCommon::utils::logger::LogStringFormatter& HTTP2Stream::getLogFormatter() const { - return m_logFormatter; -} - -template -bool HTTP2Stream::setopt(CURLoption option, const char* optionName, ParamType value) { - auto result = curl_easy_setopt(m_transfer.getCurlHandle(), option, value); - if (result != CURLE_OK) { - ACSDK_ERROR(LX("setoptFailed") - .d("option", optionName) - .sensitive("value", value) - .d("error", curl_easy_strerror(result))); - return false; - } - return true; -} - -#ifdef ACSDK_EMIT_CURL_LOGS - -void HTTP2Stream::initStreamLog() { - std::string streamLogPrefix; - configuration::ConfigurationNode::getRoot()[ACL_CONFIGURATION_KEY].getString( - STREAM_LOG_PREFIX_KEY, &streamLogPrefix); - if (streamLogPrefix.empty()) { - return; - } - - if (m_streamLog) { - m_streamLog->close(); - m_streamLog.reset(); - } - - if (m_streamInDump) { - m_streamInDump->close(); - m_streamInDump.reset(); - } - - if (m_streamOutDump) { - m_streamOutDump->close(); - m_streamOutDump.reset(); - } - - // Include a 'session id' (just a time stamp) in the log file name to avoid overwriting previous sessions. - static std::string sessionId; - if (sessionId.empty()) { - sessionId = std::to_string(std::chrono::system_clock::now().time_since_epoch().count()); - ACSDK_INFO(LX("initStreamLog").d("sessionId", sessionId)); - } - - auto basePath = streamLogPrefix + STREAM_LOG_NAME_PREFIX + sessionId + "-" + std::to_string(m_logicalStreamId); - - auto streamLogPath = basePath + STREAM_LOG_NAME_SUFFIX; - m_streamLog.reset(new std::ofstream(streamLogPath)); - if (!m_streamLog->good()) { - m_streamLog.reset(); - ACSDK_ERROR(LX("initStreamLogFailed").d("reason", "fileOpenFailed").d("streamLogPath", streamLogPath)); - } - - auto streamInDumpPath = basePath + STREAM_IN_DUMP_SUFFIX; - m_streamInDump.reset(new std::ofstream(streamInDumpPath, std::ios_base::out | std::ios_base::binary)); - if (!m_streamInDump->good()) { - m_streamInDump.reset(); - ACSDK_ERROR(LX("initStreamLogFailed").d("reason", "fileOpenFailed").d("streamInDumpPath", streamInDumpPath)); - } - - auto streamOutDumpPath = basePath + STREAM_OUT_DUMP_SUFFIX; - m_streamOutDump.reset(new std::ofstream(streamOutDumpPath, std::ios_base::out | std::ios_base::binary)); - if (!m_streamOutDump->good()) { - m_streamOutDump.reset(); - ACSDK_ERROR(LX("initStreamLogFailed").d("reason", "fileOpenFailed").d("streamOutDumpPath", streamOutDumpPath)); - } -} - -int HTTP2Stream::debugFunction(CURL* handle, curl_infotype type, char* data, size_t size, void* user) { - HTTP2Stream* stream = static_cast(user); - if (!stream) { - return 0; - } - if (stream->m_streamLog) { - auto logFormatter = stream->getLogFormatter(); - (*stream->m_streamLog) << logFormatter.format( - logger::Level::INFO, - std::chrono::system_clock::now(), - logger::ThreadMoniker::getThisThreadMoniker().c_str(), - curlInfoTypeToString(type)) - << std::endl; - if (CURLINFO_TEXT == type) { - (*stream->m_streamLog) << curlInfoTypeToPrefix(type) << data; - } else { - logger::dumpBytesToStream( - *stream->m_streamLog, curlInfoTypeToPrefix(type), 0x20, reinterpret_cast(data), size); - } - stream->m_streamLog->flush(); - } - switch (type) { - case CURLINFO_TEXT: { - std::string text(data, size); - auto index = text.rfind("\n"); - if (index != std::string::npos) { - text.resize(index); - } - ACSDK_DEBUG0(LX("libcurl").d("streamId", stream->getLogicalStreamId()).sensitive("text", text)); - } break; - case CURLINFO_HEADER_IN: - case CURLINFO_DATA_IN: - if (stream->m_streamInDump) { - stream->m_streamInDump->write(data, size); - stream->m_streamInDump->flush(); - } - break; - case CURLINFO_HEADER_OUT: - case CURLINFO_DATA_OUT: - if (stream->m_streamOutDump) { - stream->m_streamOutDump->write(data, size); - stream->m_streamOutDump->flush(); - } - break; - default: - break; - } - - return 0; -} - -#else // ACSDK_EMIT_CURL_LOGS - -void HTTP2Stream::initStreamLog() { -} - -#endif // ACSDK_EMIT_CURL_LOGS - -} // namespace acl -} // namespace alexaClientSDK diff --git a/ACL/src/Transport/HTTP2StreamPool.cpp b/ACL/src/Transport/HTTP2StreamPool.cpp deleted file mode 100644 index 2dbf463180..0000000000 --- a/ACL/src/Transport/HTTP2StreamPool.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include -#include "ACL/Transport/HTTP2StreamPool.h" - -namespace alexaClientSDK { -namespace acl { - -/// String to identify log entries originating from this file. -static const std::string TAG("HTTP2StreamPool"); - -/** - * Create a LogEntry using this file's TAG and the specified event string. - * - * @param The event string for this @c LogEntry. - */ -#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) - -using namespace avsCommon::utils; - -unsigned int HTTP2StreamPool::m_nextStreamId = 1; - -HTTP2StreamPool::HTTP2StreamPool( - const int maxStreams, - std::shared_ptr attachmentManager) : - m_numAcquiredStreams{0}, - m_maxStreams{maxStreams}, - m_attachmentManager{attachmentManager} { -} - -std::shared_ptr HTTP2StreamPool::createGetStream( - const std::string& url, - const std::string& authToken, - std::shared_ptr messageConsumer) { - std::shared_ptr stream = getStream(messageConsumer); - if (!stream) { - ACSDK_ERROR(LX("createGetStreamFailed").d("reason", "getStreamFailed")); - return nullptr; - } - if (!stream->initGet(url, authToken)) { - ACSDK_ERROR(LX("createGetStreamFailed").d("reason", "initGetFailed")); - releaseStream(stream); - return nullptr; - } - return stream; -} - -std::shared_ptr HTTP2StreamPool::createPostStream( - const std::string& url, - const std::string& authToken, - std::shared_ptr request, - std::shared_ptr messageConsumer) { - std::shared_ptr stream = getStream(messageConsumer); - if (!request) { - ACSDK_ERROR(LX("createPostStreamFailed").d("reason", "nullMessageRequest")); - return nullptr; - } - if (!stream) { - ACSDK_ERROR(LX("createPostStreamFailed").d("reason", "getStreamFailed")); - request->sendCompleted(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::INTERNAL_ERROR); - return nullptr; - } - if (!stream->initPost(url, authToken, request)) { - ACSDK_ERROR(LX("createPostStreamFailed").d("reason", "initPostFailed")); - request->sendCompleted(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::INTERNAL_ERROR); - releaseStream(stream); - return nullptr; - } - return stream; -} - -std::shared_ptr HTTP2StreamPool::getStream(std::shared_ptr messageConsumer) { - if (!messageConsumer) { - ACSDK_ERROR(LX("getStreamFailed").d("reason", "nullptrMessageConsumer")); - return nullptr; - } - std::lock_guard lock(m_mutex); - if (m_numAcquiredStreams >= m_maxStreams) { - ACSDK_WARN(LX("getStreamFailed").d("reason", "maxStreamsAlreadyAcquired")); - return nullptr; - } - - std::shared_ptr result; - if (m_pool.empty()) { - result = std::make_shared(messageConsumer, m_attachmentManager); - } else { - result = m_pool.back(); - m_pool.pop_back(); - } - m_numAcquiredStreams++; - - result->setLogicalStreamId(m_nextStreamId); - // Increment by two so that these IDs tend to line up with the number at the end of x-amzn-requestId values. - m_nextStreamId += 2; - - ACSDK_DEBUG0( - LX("getStream").d("streamId", result->getLogicalStreamId()).d("numAcquiredStreams", m_numAcquiredStreams)); - return result; -} - -void HTTP2StreamPool::releaseStream(std::shared_ptr stream) { - if (!stream) { - return; - } - std::lock_guard lock(m_mutex); - - // Check to avoid releasing the same stream more than once accidentally - for (auto item : m_pool) { - if (item == stream) { - ACSDK_ERROR( - LX("releaseStreamFailed").d("reason", "alreadyReleased").d("streamId", stream->getLogicalStreamId())); - return; - } - } - - m_numAcquiredStreams--; - - ACSDK_DEBUG0( - LX("releaseStream").d("streamId", stream->getLogicalStreamId()).d("numAcquiredStreams", m_numAcquiredStreams)); - - if (stream->reset()) { - m_pool.push_back(stream); - } -} - -} // namespace acl -} // namespace alexaClientSDK diff --git a/ACL/src/Transport/HTTP2Transport.cpp b/ACL/src/Transport/HTTP2Transport.cpp index dd6b1f851a..e304c5a7fc 100644 --- a/ACL/src/Transport/HTTP2Transport.cpp +++ b/ACL/src/Transport/HTTP2Transport.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -17,22 +17,27 @@ #include #include -#include -#include +#include +#include +#include #include #include +#include +#include "ACL/Transport/DownchannelHandler.h" #include "ACL/Transport/HTTP2Transport.h" +#include "ACL/Transport/MessageRequestHandler.h" +#include "ACL/Transport/PingHandler.h" #include "ACL/Transport/TransportDefines.h" namespace alexaClientSDK { namespace acl { using namespace alexaClientSDK::avsCommon::utils; -using namespace alexaClientSDK::avsCommon::utils::libcurlUtils; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::avs; using namespace avsCommon::avs::attachment; +using namespace avsCommon::utils::http2; /// String to identify log entries originating from this file. static const std::string TAG("HTTP2Transport"); @@ -44,846 +49,792 @@ static const std::string TAG("HTTP2Transport"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) -/** - * The maximum number of streams we can have active at once. Please see here for more information: - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/docs/managing-an-http-2-connection - */ -const static int MAX_STREAMS = 10; -/// Default @c AVS endpoint to connect to. -const static std::string DEFAULT_AVS_ENDPOINT = "https://avs-alexa-na.amazon.com"; -/// Downchannel URL -const static std::string AVS_DOWNCHANNEL_URL_PATH_EXTENSION = "/v20160207/directives"; -/// URL to send events to -const static std::string AVS_EVENT_URL_PATH_EXTENSION = "/v20160207/events"; -/// URL to send pings to -const static std::string AVS_PING_URL_PATH_EXTENSION = "/ping"; -/// Timeout for curl_multi_wait -const static std::chrono::milliseconds WAIT_FOR_ACTIVITY_TIMEOUT(100); -/// Timeout for curl_multi_wait while all HTTP/2 event streams are paused. -const static std::chrono::milliseconds WAIT_FOR_ACTIVITY_WHILE_STREAMS_PAUSED_TIMEOUT(10); -/// Inactivity timeout before we send a ping -const static std::chrono::minutes INACTIVITY_TIMEOUT = std::chrono::minutes(5); -/// The maximum time a ping should take in seconds -const static long PING_RESPONSE_TIMEOUT_SEC = 30; -/// Connection timeout -static const std::chrono::seconds ESTABLISH_CONNECTION_TIMEOUT = std::chrono::seconds(60); -/// Timeout for transmission of data on a given stream -static const std::chrono::seconds STREAM_PROGRESS_TIMEOUT = std::chrono::seconds(30); -/// Key for the root node value containing configuration values for ACL. -static const std::string ACL_CONFIG_KEY = "acl"; -/// Key for the 'endpoint' value under the @c ACL_CONFIG_KEY configuration node. -static const std::string ENDPOINT_KEY = "endpoint"; - -#ifdef ACSDK_OPENSSL_MIN_VER_REQUIRED -/** - * This function checks the minimum version of OpenSSL required and prints a warning if the version is too old or - * the version string parsing failed. - */ -static void verifyOpenSslVersion(curl_version_info_data* data) { - // There are three numbers in OpenSSL version: major.minor.patch - static const int NUM_OF_VERSION_NUMBER = 3; +/// The maximum number of streams we can have active at once. Please see here for more information: +/// https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/docs/managing-an-http-2-connection +static const int MAX_STREAMS = 10; - unsigned int versionUsed[NUM_OF_VERSION_NUMBER]; - unsigned int minVersionRequired[NUM_OF_VERSION_NUMBER]; +/// Max number of message requests MAX_STREAMS - 2 (for the downchannel stream and the ping stream) +static const int MAX_MESSAGE_HANDLERS = MAX_STREAMS - 2; - if (!data) { - ACSDK_ERROR(LX("verifyOpenSslVersionFailed").d("reason", "nullData")); - return; - } else if (!data->ssl_version) { - ACSDK_ERROR(LX("verifyOpenSslVersionFailed").d("reason", "nullSslVersion")); - return; - } - - // parse ssl_version - int matchedVersionUsed = - sscanf(data->ssl_version, "OpenSSL/%u.%u.%u", &versionUsed[0], &versionUsed[1], &versionUsed[2]); - - // parse minimum OpenSSL version required - int matchedVersionRequired = sscanf( - ACSDK_STRINGIFY(ACSDK_OPENSSL_MIN_VER_REQUIRED), - "%u.%u.%u", - &minVersionRequired[0], - &minVersionRequired[1], - &minVersionRequired[2]); - - if (matchedVersionUsed == matchedVersionRequired && matchedVersionUsed == NUM_OF_VERSION_NUMBER) { - bool versionRequirementFailed = false; - - for (int i = 0; i < NUM_OF_VERSION_NUMBER; i++) { - if (versionUsed[i] < minVersionRequired[i]) { - versionRequirementFailed = true; - break; - } else if (versionUsed[i] > minVersionRequired[i]) { - break; - } - } - if (versionRequirementFailed) { - ACSDK_WARN(LX("OpenSSL minimum version requirement failed!") - .d("version", data->ssl_version) - .d("required", ACSDK_STRINGIFY(ACSDK_OPENSSL_MIN_VER_REQUIRED))); - } - } else { - ACSDK_WARN(LX("Unable to parse OpenSSL version!") - .d("version", data->ssl_version) - .d("required", ACSDK_STRINGIFY(ACSDK_OPENSSL_MIN_VER_REQUIRED))); - } -} -#endif +/// Timeout to send a ping to AVS if there has not been any other acitivity on the connection. +static std::chrono::minutes INACTIVITY_TIMEOUT{5}; /** - * This function logs a warning if the version of curl is not recent enough for use with the ACL. + * Write a @c HTTP2Transport::State value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param state The state to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. */ -static void printCurlDiagnostics() { - curl_version_info_data* data = curl_version_info(CURLVERSION_NOW); - if (data && !(data->features & CURL_VERSION_HTTP2)) { - ACSDK_CRITICAL(LX("libcurl not built with HTTP/2 support!")); - } - -#ifdef ACSDK_OPENSSL_MIN_VER_REQUIRED - verifyOpenSslVersion(data); -#endif +std::ostream& operator<<(std::ostream& stream, HTTP2Transport::State state) { + switch (state) { + case HTTP2Transport::State::INIT: + return stream << "INIT"; + case HTTP2Transport::State::AUTHORIZING: + return stream << "AUTHORIZING"; + case HTTP2Transport::State::CONNECTING: + return stream << "CONNECTING"; + case HTTP2Transport::State::WAITING_TO_RETRY_CONNECTING: + return stream << "WAITING_TO_RETRY_CONNECTING"; + case HTTP2Transport::State::POST_CONNECTING: + return stream << "POST_CONNECTING"; + case HTTP2Transport::State::CONNECTED: + return stream << "CONNECTED"; + case HTTP2Transport::State::SERVER_SIDE_DISCONNECT: + return stream << "SERVER_SIDE_DISCONNECT"; + case HTTP2Transport::State::DISCONNECTING: + return stream << "DISCONNECTING"; + case HTTP2Transport::State::SHUTDOWN: + return stream << "SHUTDOWN"; + } + return stream << ""; +} + +HTTP2Transport::Configuration::Configuration() : inactivityTimeout{INACTIVITY_TIMEOUT} { } std::shared_ptr HTTP2Transport::create( std::shared_ptr authDelegate, const std::string& avsEndpoint, - std::shared_ptr messageConsumerInterface, + std::shared_ptr http2Connection, + std::shared_ptr messageConsumer, std::shared_ptr attachmentManager, - std::shared_ptr observer) { - std::shared_ptr postConnectObject = PostConnectObject::create(); + std::shared_ptr transportObserver, + std::shared_ptr postConnectFactory, + Configuration configuration) { + ACSDK_DEBUG5(LX(__func__) + .d("authDelegate", authDelegate.get()) + .d("avsEndpoint", avsEndpoint) + .d("http2Connection", http2Connection.get()) + .d("messageConsumer", messageConsumer.get()) + .d("attachmentManager", attachmentManager.get()) + .d("transportObserver", transportObserver.get()) + .d("postConnectFactory", postConnectFactory.get())); + + if (!authDelegate) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullAuthDelegate")); + return nullptr; + } + + if (avsEndpoint.empty()) { + ACSDK_ERROR(LX("createFailed").d("reason", "emptyEndpoint")); + return nullptr; + } - if (!postConnectObject) { - ACSDK_ERROR(LX("HTTP2Transport::createFailed").d("reason", "nullPostConnectObject")); + if (!http2Connection) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullHTTP2ConnectionInterface")); return nullptr; } - return std::shared_ptr(new HTTP2Transport( - authDelegate, avsEndpoint, messageConsumerInterface, attachmentManager, postConnectObject, observer)); + if (!messageConsumer) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullMessageConsumer")); + return nullptr; + } + + if (!attachmentManager) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullAttachmentManager")); + return nullptr; + } + + if (!postConnectFactory) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullPostConnectFactory")); + return nullptr; + } + + auto transport = std::shared_ptr(new HTTP2Transport( + authDelegate, + avsEndpoint, + http2Connection, + messageConsumer, + attachmentManager, + transportObserver, + postConnectFactory, + configuration)); + + return transport; } HTTP2Transport::HTTP2Transport( std::shared_ptr authDelegate, const std::string& avsEndpoint, - std::shared_ptr messageConsumerInterface, + std::shared_ptr http2Connection, + std::shared_ptr messageConsumer, std::shared_ptr attachmentManager, - std::shared_ptr postConnectObject, - std::shared_ptr observer) : - m_messageConsumer{messageConsumerInterface}, + std::shared_ptr transportObserver, + std::shared_ptr postConnectFactory, + Configuration configuration) : + m_state{State::INIT}, m_authDelegate{authDelegate}, m_avsEndpoint{avsEndpoint}, - m_streamPool{MAX_STREAMS, attachmentManager}, - m_disconnectReason{ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR}, - m_isNetworkThreadRunning{false}, - m_isConnected{false}, - m_isStopping{false}, - m_disconnectedSent{false}, - m_postConnectObject{postConnectObject} { - m_observers.insert(observer); - - printCurlDiagnostics(); - - if (m_avsEndpoint.empty()) { - alexaClientSDK::avsCommon::utils::configuration::ConfigurationNode::getRoot()[ACL_CONFIG_KEY].getString( - ENDPOINT_KEY, &m_avsEndpoint, DEFAULT_AVS_ENDPOINT); + m_http2Connection{http2Connection}, + m_messageConsumer{messageConsumer}, + m_attachmentManager{attachmentManager}, + m_postConnectFactory{postConnectFactory}, + m_connectRetryCount{0}, + m_isMessageHandlerAwaitingResponse{false}, + m_countOfUnfinishedMessageHandlers{0}, + m_postConnected{false}, + m_configuration{configuration}, + m_disconnectReason{ConnectionStatusObserverInterface::ChangedReason::NONE} { + ACSDK_DEBUG5(LX(__func__) + .d("authDelegate", authDelegate.get()) + .d("avsEndpoint", avsEndpoint) + .d("http2Connection", http2Connection.get()) + .d("messageConsumer", messageConsumer.get()) + .d("attachmentManager", attachmentManager.get()) + .d("transportObserver", transportObserver.get()) + .d("postConnectFactory", postConnectFactory.get())); + m_observers.insert(transportObserver); +} + +void HTTP2Transport::addObserver(std::shared_ptr transportObserver) { + ACSDK_DEBUG5(LX(__func__).d("transportObserver", transportObserver.get())); + + if (!transportObserver) { + ACSDK_ERROR(LX("addObserverFailed").d("reason", "nullObserver")); + return; } -} -void HTTP2Transport::doShutdown() { - disconnect(); + std::lock_guard lock{m_observerMutex}; + m_observers.insert(transportObserver); } -bool HTTP2Transport::connect() { - std::lock_guard lock(m_mutex); +void HTTP2Transport::removeObserver(std::shared_ptr transportObserver) { + ACSDK_DEBUG5(LX(__func__).d("transportObserver", transportObserver.get())); - /* - * To handle cases were shutdown was called before the transport is connected. In this case we - * do not want to spawn a thread and create a post-connect object. - */ - if (m_isStopping) { - return false; + if (!transportObserver) { + ACSDK_ERROR(LX("removeObserverFailed").d("reason", "nullObserver")); + return; } - // This function spawns a worker thread, so let's ensure this function may only be called when - // the worker thread is not running. - if (m_isNetworkThreadRunning) { - ACSDK_ERROR(LX("connectFailed").d("reason", "networkThreadAlreadyRunning")); - return false; - } + std::lock_guard lock{m_observerMutex}; + m_observers.erase(transportObserver); +} - // The transport object registers itself as an observer to the PostConnect - // factory. On PostConnect completion the transport object moves to - // connected state and can queue messages to send. - m_postConnectObject->addObserver(shared_from_this()); +std::shared_ptr HTTP2Transport::getHTTP2Connection() { + return m_http2Connection; +} - m_multi = avsCommon::utils::libcurlUtils::CurlMultiHandleWrapper::create(); - if (!m_multi) { - ACSDK_ERROR(LX("connectFailed").d("reason", "curlMultiHandleWrapperCreateFailed")); - return false; - } - if (curl_multi_setopt(m_multi->getCurlHandle(), CURLMOPT_PIPELINING, 2L) != CURLM_OK) { - m_multi.reset(); - ACSDK_ERROR(LX("connectFailed").d("reason", "enableHTTP2PipeliningFailed")); - return false; - } +bool HTTP2Transport::connect() { + ACSDK_DEBUG5(LX(__func__)); - ConnectionStatusObserverInterface::ChangedReason reason = - ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR; - if (!setupDownchannelStream(&reason)) { - m_multi.reset(); - ACSDK_ERROR(LX("connectFailed").d("reason", "setupDownchannelStreamFailed").d("error", reason)); + if (!setState(State::AUTHORIZING, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST)) { + ACSDK_ERROR(LX("connectFailed").d("reason", "setStateFailed")); return false; } - /* - * Call the post-connect object to do the post-connect operations. - */ - if (!m_postConnectObject->doPostConnect(shared_from_this())) { - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - return false; - } + m_thread = std::thread(&HTTP2Transport::mainLoop, this); - m_isNetworkThreadRunning = true; - m_isStopping = false; - m_networkThread = std::thread(&HTTP2Transport::networkLoop, this); return true; } void HTTP2Transport::disconnect() { - std::thread localNetworkThread; - std::shared_ptr localPostConnectObject; + ACSDK_DEBUG5(LX(__func__)); + + std::thread localThread; { std::lock_guard lock(m_mutex); - setIsStoppingLocked(ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); - std::swap(m_postConnectObject, localPostConnectObject); - std::swap(m_networkThread, localNetworkThread); - } - - if (localPostConnectObject) { - localPostConnectObject->shutdown(); + if (State::SHUTDOWN != m_state) { + setStateLocked(State::DISCONNECTING, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); + } + std::swap(m_thread, localThread); } - if (localNetworkThread.joinable()) { - localNetworkThread.join(); - } - { - std::lock_guard lock(m_observerMutex); - m_observers.clear(); + if (localThread.joinable()) { + localThread.join(); } } bool HTTP2Transport::isConnected() { std::lock_guard lock(m_mutex); - return isConnectedLocked(); + return State::CONNECTED == m_state; } void HTTP2Transport::send(std::shared_ptr request) { - if (!request) { - ACSDK_ERROR(LX("sendFailed").d("reason", "nullRequest")); - } else if (!enqueueRequest(request, false)) { - request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); - } + ACSDK_DEBUG5(LX(__func__)); + enqueueRequest(request, false); } void HTTP2Transport::sendPostConnectMessage(std::shared_ptr request) { - if (!request) { - ACSDK_ERROR(LX("sendFailed").d("reason", "nullRequest")); - } + ACSDK_DEBUG5(LX(__func__)); enqueueRequest(request, true); } -bool HTTP2Transport::setupDownchannelStream(ConnectionStatusObserverInterface::ChangedReason* reason) { - if (!reason) { - ACSDK_CRITICAL(LX("setupDownchannelStreamFailed").d("reason", "nullReason")); - return false; - } +void HTTP2Transport::onPostConnected() { + ACSDK_DEBUG5(LX(__func__)); - if (m_downchannelStream && !releaseDownchannelStream(true, reason)) { - return false; - } + std::lock_guard lock(m_mutex); - std::string authToken = m_authDelegate->getAuthToken(); - if (authToken.empty()) { - ACSDK_ERROR(LX("setupDownchannelStreamFailed").d("reason", "getAuthTokenFailed")); - *reason = ConnectionStatusObserverInterface::ChangedReason::INVALID_AUTH; - return false; + m_postConnect.reset(); + switch (m_state) { + case State::INIT: + case State::AUTHORIZING: + case State::CONNECTING: + case State::WAITING_TO_RETRY_CONNECTING: + m_postConnected = true; + break; + case State::CONNECTED: + ACSDK_ERROR(LX("onPostConnectFailed").d("reason", "unexpectedState")); + break; + case State::POST_CONNECTING: + m_postConnected = true; + if (!setStateLocked(State::CONNECTED, ConnectionStatusObserverInterface::ChangedReason::SUCCESS)) { + ACSDK_ERROR(LX("onPostConnectFailed").d("reason", "setState(CONNECTED)Failed")); + } + break; + case State::SERVER_SIDE_DISCONNECT: + case State::DISCONNECTING: + case State::SHUTDOWN: + break; } +} - std::string url = m_avsEndpoint + AVS_DOWNCHANNEL_URL_PATH_EXTENSION; - ACSDK_DEBUG9(LX("setupDownchannelStream").d("url", url)); +void HTTP2Transport::onAuthStateChange( + avsCommon::sdkInterfaces::AuthObserverInterface::State newState, + avsCommon::sdkInterfaces::AuthObserverInterface::Error error) { + ACSDK_DEBUG5(LX(__func__).d("newState", newState).d("error", error)); - m_downchannelStream = m_streamPool.createGetStream(url, authToken, m_messageConsumer); - if (!m_downchannelStream) { - ACSDK_ERROR(LX("setupDownchannelStreamFailed").d("reason", "createGetStreamFailed")); - *reason = ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR; - return false; - } - // Since the downchannel is the first stream to be established, make sure it times out if - // a connection can't be established. - if (!m_downchannelStream->setConnectionTimeout(ESTABLISH_CONNECTION_TIMEOUT)) { - releaseDownchannelStream(false, nullptr); - ACSDK_ERROR(LX("setupDownchannelStreamFailed").d("reason", "setConnectionTimeoutFailed")); - *reason = ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR; - return false; - } + std::lock_guard lock(m_mutex); - auto result = m_multi->addHandle(m_downchannelStream->getCurlHandle()); - if (result != CURLM_OK) { - releaseDownchannelStream(false, nullptr); - ACSDK_ERROR(LX("setupDownchannelStreamFailed").d("reason", "addHandleFailed")); - *reason = ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR; - return false; + switch (newState) { + case AuthObserverInterface::State::UNINITIALIZED: + case AuthObserverInterface::State::EXPIRED: + if (State::WAITING_TO_RETRY_CONNECTING == m_state) { + ACSDK_DEBUG0(LX("revertToAuthorizing").d("reason", "authorizationExpiredBeforeConnected")); + setStateLocked(State::AUTHORIZING, ConnectionStatusObserverInterface::ChangedReason::INVALID_AUTH); + } + return; + + case AuthObserverInterface::State::REFRESHED: + if (State::AUTHORIZING == m_state) { + setStateLocked(State::CONNECTING, ConnectionStatusObserverInterface::ChangedReason::SUCCESS); + } + return; + + case AuthObserverInterface::State::UNRECOVERABLE_ERROR: + ACSDK_ERROR(LX("shuttingDown").d("reason", "unrecoverableAuthError")); + setStateLocked(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::UNRECOVERABLE_ERROR); + return; } - m_activeStreams.insert(ActiveTransferEntry(m_downchannelStream->getCurlHandle(), m_downchannelStream)); + ACSDK_ERROR(LX("shuttingDown").d("reason", "unknownAuthStatus").d("newState", static_cast(newState))); + setStateLocked(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::UNRECOVERABLE_ERROR); +} - return true; +void HTTP2Transport::doShutdown() { + ACSDK_DEBUG5(LX(__func__)); + setState(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); + disconnect(); + m_authDelegate->removeAuthObserver(shared_from_this()); + m_pingHandler.reset(); + m_http2Connection.reset(); + m_messageConsumer.reset(); + m_attachmentManager.reset(); + m_postConnectFactory.reset(); + m_postConnect.reset(); + m_observers.clear(); } -void HTTP2Transport::networkLoop() { - int retryCount = 0; - while (!establishConnection() && !isStopping()) { - std::chrono::milliseconds retryBackoff = TransportDefines::RETRY_TIMER.calculateTimeToRetry(retryCount); - ACSDK_ERROR(LX("networkLoopRetryingToConnect") - .d("reason", "establishConnectionFailed") - .d("retryCount", retryCount) - .d("retryBackoff", retryBackoff.count())); - retryCount++; - std::unique_lock lock(m_mutex); - m_wakeRetryTrigger.wait_for(lock, retryBackoff, [this] { return m_isStopping; }); - } - - /* - * Call perform repeatedly to transfer data on active streams. If all the event streams have HTTP2 - * response codes service the next outgoing message (if any). While the connection is alive we should have - * at least 1 transfer active (the downchannel). - */ - int numTransfersLeft = 1; - auto inactivityTimerStart = std::chrono::steady_clock::now(); - while (numTransfersLeft && !isStopping()) { - auto result = m_multi->perform(&numTransfersLeft); - if (CURLM_CALL_MULTI_PERFORM == result) { - continue; - } else if (result != CURLM_OK) { - ACSDK_ERROR(LX("networkLoopStopping").d("reason", "performFailed")); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - break; - } +void HTTP2Transport::onDownchannelConnected() { + ACSDK_DEBUG5(LX(__func__)); + setState(State::POST_CONNECTING, ConnectionStatusObserverInterface::ChangedReason::SUCCESS); +} - cleanupFinishedStreams(); - cleanupStalledStreams(); - if (isStopping()) { - break; - } +void HTTP2Transport::onDownchannelFinished() { + ACSDK_DEBUG5(LX(__func__)); - if (canProcessOutgoingMessage()) { - processNextOutgoingMessage(); - } + std::lock_guard lock(m_mutex); - auto multiWaitTimeout = WAIT_FOR_ACTIVITY_TIMEOUT; + switch (m_state) { + case State::INIT: + case State::AUTHORIZING: + case State::WAITING_TO_RETRY_CONNECTING: + ACSDK_ERROR(LX("onDownchannelFinishedFailed").d("reason", "unexpectedState")); + break; + case State::CONNECTING: + setStateLocked(State::WAITING_TO_RETRY_CONNECTING, ConnectionStatusObserverInterface::ChangedReason::NONE); + break; + case State::POST_CONNECTING: + case State::CONNECTED: + setStateLocked( + State::SERVER_SIDE_DISCONNECT, + ConnectionStatusObserverInterface::ChangedReason::SERVER_SIDE_DISCONNECT); + break; + case State::SERVER_SIDE_DISCONNECT: + case State::DISCONNECTING: + case State::SHUTDOWN: + break; + } +} - size_t numberEventStreams = 0; - size_t numberPausedStreams = 0; - for (auto entry : m_activeStreams) { - auto stream = entry.second; - if (isEventStream(stream)) { - numberEventStreams++; - if (entry.second->isPaused()) { - numberPausedStreams++; - } - } - } - bool paused = numberPausedStreams > 0 && (numberPausedStreams == numberEventStreams); +void HTTP2Transport::onMessageRequestSent() { + std::lock_guard lock(m_mutex); + m_isMessageHandlerAwaitingResponse = true; + m_countOfUnfinishedMessageHandlers++; + ACSDK_DEBUG5(LX(__func__).d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); +} - auto before = std::chrono::time_point::max(); - if (paused) { - multiWaitTimeout = WAIT_FOR_ACTIVITY_WHILE_STREAMS_PAUSED_TIMEOUT; - before = std::chrono::steady_clock::now(); - } +void HTTP2Transport::onMessageRequestTimeout() { + // If a message request times out, trigger a ping to test connectivity to AVS. + std::lock_guard lock(m_mutex); + if (!m_pingHandler) { + m_timeOfLastActivity = m_timeOfLastActivity.min(); + m_wakeEvent.notify_all(); + } +} - // TODO: ACSDK-69 replace timeout with signal fd +void HTTP2Transport::onMessageRequestAcknowledged() { + ACSDK_DEBUG5(LX(__func__)); + std::lock_guard lock(m_mutex); + m_isMessageHandlerAwaitingResponse = false; + m_wakeEvent.notify_all(); +} - int numTransfersUpdated = 0; - result = m_multi->wait(multiWaitTimeout, &numTransfersUpdated); - if (result != CURLM_OK) { - ACSDK_ERROR( - LX("networkLoopStopping").d("reason", "multiWaitFailed").d("error", curl_multi_strerror(result))); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - break; - } +void HTTP2Transport::onMessageRequestFinished() { + std::lock_guard lock(m_mutex); + --m_countOfUnfinishedMessageHandlers; + ACSDK_DEBUG5(LX(__func__).d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); + m_wakeEvent.notify_all(); +} - // TODO: ACSDK-69 replace this logic with a signal fd +void HTTP2Transport::onPingRequestAcknowledged(bool success) { + ACSDK_DEBUG5(LX(__func__).d("success", success)); + std::lock_guard lock(m_mutex); + m_pingHandler.reset(); + if (!success) { + setStateLocked( + State::SERVER_SIDE_DISCONNECT, ConnectionStatusObserverInterface::ChangedReason::SERVER_SIDE_DISCONNECT); + } + m_wakeEvent.notify_all(); +} - // @note curl_multi_wait will return immediately even if all streams are paused, because HTTP/2 streams - // are full-duplex - so activity may have occurred on the other side. Therefore, if our intent is - // to pause ACL to give attachment readers time to catch up with written data, we must perform a local - // sleep of our own. - if (paused) { - auto after = std::chrono::steady_clock::now(); - auto elapsed = after - before; - auto remaining = multiWaitTimeout - elapsed; +void HTTP2Transport::onPingTimeout() { + ACSDK_WARN(LX(__func__)); + std::lock_guard lock(m_mutex); + m_pingHandler.reset(); + setStateLocked(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::PING_TIMEDOUT); + m_wakeEvent.notify_all(); +} - // sanity check that remainingMs is valid before performing a sleep. - if (remaining.count() > 0 && remaining <= WAIT_FOR_ACTIVITY_WHILE_STREAMS_PAUSED_TIMEOUT) { - std::this_thread::sleep_for(remaining); - } - } +void HTTP2Transport::onActivity() { + ACSDK_DEBUG5(LX(__func__)); + std::lock_guard lock(m_mutex); + m_timeOfLastActivity = std::chrono::steady_clock::now(); +} - /** - * If some transfers were updated then reset the start of the inactivity timer to now. Otherwise, - * if the INACTIVITY_TIMEOUT has been reached send a ping to AVS so verify connectivity. - */ - auto now = std::chrono::steady_clock::now(); - if (numTransfersUpdated != 0) { - inactivityTimerStart = now; - } else { - auto elapsed = now - inactivityTimerStart; - if (elapsed >= INACTIVITY_TIMEOUT) { - if (!sendPing()) { - ACSDK_ERROR(LX("networkLoopStopping").d("reason", "sendPingFailed")); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - break; - } - inactivityTimerStart = now; - } - } +void HTTP2Transport::onForbidden(const std::string& authToken) { + ACSDK_DEBUG0(LX(__func__)); + m_authDelegate->onAuthFailure(authToken); +} - // un-pause the streams so that progress may be made in the next invocation of @c m_multi->perform(). - for (auto stream : m_activeStreams) { - stream.second->unPause(); - } - } +std::shared_ptr HTTP2Transport::createAndSendRequest(const HTTP2RequestConfig& cfg) { + ACSDK_DEBUG5(LX(__func__).d("type", cfg.getRequestType()).sensitive("url", cfg.getUrl())); + return m_http2Connection->createAndSendRequest(cfg); +} - // Catch-all. Reaching this point implies stopping. - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); +std::string HTTP2Transport::getEndpoint() { + return m_avsEndpoint; +} - releaseAllEventStreams(); - releasePingStream(); - releaseDownchannelStream(); - m_multi.reset(); - clearQueuedRequests(); - setIsConnectedFalse(); +void HTTP2Transport::mainLoop() { + ACSDK_DEBUG5(LX(__func__)); - { + m_postConnect = m_postConnectFactory->createPostConnect(); + if (!m_postConnect || !m_postConnect->doPostConnect(shared_from_this())) { + ACSDK_ERROR(LX("mainLoopFailed").d("reason", "createPostConnectFailed")); std::lock_guard lock(m_mutex); - m_isNetworkThreadRunning = false; - } -} - -bool HTTP2Transport::establishConnection() { - // Set numTransferLeft to 1 because the downchannel stream has been added already. - int numTransfersLeft = 1; - - /* - * Calls curl_multi_perform until down channel stream receives an HTTP2 response code. If the down channel stream - * Call perform() to further networking transfers until the down channel stream receives an HTTP2 response - * ends before receiving a response code (numTransfersLeft == 0), then there was an error and we must try again. - * code indicating success or failure to establish a connection. If the down channel stream ends before - * If we're told to shutdown the network loop (isStopping()) then return false since no connection was established. - * receiving a response code (numTransfersLeft == 0), then set up a new down channel stream in preparation - * for being called again to retry establishing a connection. - */ - while (numTransfersLeft && !isStopping()) { - auto result = m_multi->perform(&numTransfersLeft); - // curl asked us to call multiperform again immediately - if (CURLM_CALL_MULTI_PERFORM == result) { - continue; - } else if (result != CURLM_OK) { - ACSDK_ERROR(LX("establishConnectionFailed").d("reason", "performFailed")); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - } - long downchannelResponseCode = m_downchannelStream->getResponseCode(); - /* if the downchannel response code is > 0 then we have some response from the backend - * if its < 0 there was a problem getting the response code from the easy handle - * if its == 0 then keep looping since we have not yet received a response - */ - if (downchannelResponseCode > 0) { - /* - * Only break the loop if we are successful. If we aren't keep looping so that we download - * the full error message (for logging purposes) and then return false when we're done - */ - if (HTTPResponseCode::SUCCESS_OK == downchannelResponseCode) { - return true; - } - } else if (downchannelResponseCode < 0) { - ACSDK_ERROR(LX("establishConnectionFailed") - .d("reason", "negativeResponseCode") - .d("responseCode", downchannelResponseCode)); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - } - // wait for activity on the downchannel stream, kinda like poll() - int numTransfersUpdated = 0; - result = m_multi->wait(WAIT_FOR_ACTIVITY_TIMEOUT, &numTransfersUpdated); - if (result != CURLM_OK) { - ACSDK_ERROR( - LX("establishConnectionFailed").d("reason", "waitFailed").d("error", curl_multi_strerror(result))); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - } + setStateLocked(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); } - ConnectionStatusObserverInterface::ChangedReason reason = - ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR; - if (!setupDownchannelStream(&reason)) { - ACSDK_ERROR(LX("establishConnectionFailed").d("reason", "setupDownchannelStreamFailed").d("error", reason)); - setIsStopping(reason); - } - return false; -} + m_timeOfLastActivity = std::chrono::steady_clock::now(); + State nextState = getState(); -void HTTP2Transport::cleanupFinishedStreams() { - CURLMsg* message = nullptr; - do { - int messagesLeft = 0; - message = m_multi->infoRead(&messagesLeft); - if (message && CURLMSG_DONE == message->msg) { - if (m_downchannelStream && m_downchannelStream->getCurlHandle() == message->easy_handle) { - if (!isStopping()) { - notifyObserversOnServerSideDisconnect(); - } - releaseDownchannelStream(); - continue; - } + while (nextState != State::SHUTDOWN) { + switch (nextState) { + case State::INIT: + nextState = handleInit(); + break; + case State::AUTHORIZING: + nextState = handleAuthorizing(); + break; + case State::CONNECTING: + nextState = handleConnecting(); + break; + case State::WAITING_TO_RETRY_CONNECTING: + nextState = handleWaitingToRetryConnecting(); + break; + case State::POST_CONNECTING: + nextState = handlePostConnecting(); + break; + case State::CONNECTED: + nextState = handleConnected(); + break; + case State::SERVER_SIDE_DISCONNECT: + nextState = handleServerSideDisconnect(); + case State::DISCONNECTING: + nextState = handleDisconnecting(); + break; + case State::SHUTDOWN: + break; + } + } - if (m_pingStream && m_pingStream->getCurlHandle() == message->easy_handle) { - handlePingResponse(); - continue; - } + handleShutdown(); - auto it = m_activeStreams.find(message->easy_handle); - if (it != m_activeStreams.end()) { - it->second->notifyRequestObserver(); - ACSDK_DEBUG0(LX("cleanupFinishedStream") - .d("streamId", it->second->getLogicalStreamId()) - .d("result", it->second->getResponseCode())); - releaseEventStream(it->second); - } else { - ACSDK_ERROR( - LX("cleanupFinishedStreamError").d("reason", "streamNotFound").d("handle", message->easy_handle)); - } - } - } while (message); + ACSDK_DEBUG5(LX("mainLoopExiting")); } -void HTTP2Transport::cleanupStalledStreams() { - auto it = m_activeStreams.begin(); - while (it != m_activeStreams.end()) { - auto stream = (it++)->second; - if (isEventStream(stream) && stream->hasProgressTimedOut()) { - ACSDK_INFO(LX("streamProgressTimedOut").d("streamId", stream->getLogicalStreamId())); - stream->notifyRequestObserver(MessageRequestObserverInterface::Status::TIMEDOUT); - releaseEventStream(stream); - } - } +HTTP2Transport::State HTTP2Transport::handleInit() { + ACSDK_CRITICAL(LX(__func__).d("reason", "unexpectedState")); + std::lock_guard lock(m_mutex); + setStateLocked(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); + return m_state; } -bool HTTP2Transport::canProcessOutgoingMessage() { - for (auto entry : m_activeStreams) { - auto stream = entry.second; - // If we have an event that still hasn't received a response code then we cannot send another outgoing message. - if (isEventStream(stream) && (stream->getResponseCode() == 0)) { - return false; - } - } - // All outstanding streams (if any) have received a response, the next message can now be sent. - return true; -} +HTTP2Transport::State HTTP2Transport::handleAuthorizing() { + ACSDK_DEBUG5(LX(__func__)); -void HTTP2Transport::processNextOutgoingMessage() { - auto request = dequeueRequest(); - if (!request) { - return; - } - auto authToken = m_authDelegate->getAuthToken(); - if (authToken.empty()) { - ACSDK_DEBUG0(LX("processNextOutgoingMessageFailed") - .d("reason", "invalidAuth") - .sensitive("jsonContext", request->getJsonContent())); - request->sendCompleted(MessageRequestObserverInterface::Status::INVALID_AUTH); - return; - } - ACSDK_DEBUG0(LX("processNextOutgoingMessage").sensitive("jsonContent", request->getJsonContent())); - auto url = m_avsEndpoint + AVS_EVENT_URL_PATH_EXTENSION; - std::shared_ptr stream = m_streamPool.createPostStream(url, authToken, request, m_messageConsumer); - // note : if the stream is nullptr, the stream pool already called sendCompleted on the MessageRequest. - if (stream) { - stream->setProgressTimeout(STREAM_PROGRESS_TIMEOUT); - auto result = m_multi->addHandle(stream->getCurlHandle()); - if (result != CURLM_OK) { - ACSDK_ERROR(LX("processNextOutgoingMessageFailed") - .d("reason", "addHandleFailed") - .d("error", curl_multi_strerror(result)) - .d("streamId", stream->getLogicalStreamId())); - m_streamPool.releaseStream(stream); - stream->notifyRequestObserver(MessageRequestObserverInterface::Status::INTERNAL_ERROR); - } else { - ACSDK_DEBUG9(LX("insertActiveStream").d("handle", stream->getCurlHandle())); - m_activeStreams.insert(ActiveTransferEntry(stream->getCurlHandle(), stream)); - } - } + m_authDelegate->addAuthObserver(shared_from_this()); + + std::unique_lock lock(m_mutex); + m_wakeEvent.wait(lock, [this]() { return m_state != State::AUTHORIZING; }); + return m_state; } -bool HTTP2Transport::sendPing() { - ACSDK_DEBUG(LX("sendPing").d("pingStream", m_pingStream.get())); +HTTP2Transport::State HTTP2Transport::handleConnecting() { + ACSDK_DEBUG5(LX(__func__)); - if (m_pingStream) { - return true; - } + std::unique_lock lock(m_mutex); - std::string authToken = m_authDelegate->getAuthToken(); - if (authToken.empty()) { - ACSDK_ERROR(LX("sendPingFailed").d("reason", "getAuthTokenFailed")); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INVALID_AUTH); - return false; - } + while (State::CONNECTING == m_state) { + lock.unlock(); - std::string url = m_avsEndpoint + AVS_PING_URL_PATH_EXTENSION; + auto authToken = m_authDelegate->getAuthToken(); - m_pingStream = m_streamPool.createGetStream(url, authToken, m_messageConsumer); - if (!m_pingStream) { - ACSDK_ERROR(LX("sendPingFailed").d("reason", "createPingStreamFailed")); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - return false; - } + if (authToken.empty()) { + setState( + State::WAITING_TO_RETRY_CONNECTING, ConnectionStatusObserverInterface::ChangedReason::INVALID_AUTH); + break; + } - if (!m_pingStream->setStreamTimeout(PING_RESPONSE_TIMEOUT_SEC)) { - releasePingStream(false); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - return false; - } + auto downchannelHandler = + DownchannelHandler::create(shared_from_this(), authToken, m_messageConsumer, m_attachmentManager); + lock.lock(); - auto result = m_multi->addHandle(m_pingStream->getCurlHandle()); - if (result != CURLM_OK) { - ACSDK_ERROR(LX("sendPingFailed").d("reason", "addHandleFailed")); - releasePingStream(false); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - return false; + if (!downchannelHandler) { + ACSDK_ERROR(LX("handleConnectingFailed").d("reason", "createDownchannelHandlerFailed")); + setStateLocked( + State::WAITING_TO_RETRY_CONNECTING, ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); + return m_state; + } + + while (State::CONNECTING == m_state) { + m_wakeEvent.wait(lock); + } } - m_activeStreams.insert(ActiveTransferEntry(m_pingStream->getCurlHandle(), m_pingStream)); - return true; + return m_state; } -void HTTP2Transport::handlePingResponse() { - ACSDK_DEBUG(LX("handlePingResponse")); - if (HTTPResponseCode::SUCCESS_NO_CONTENT != m_pingStream->getResponseCode()) { - ACSDK_ERROR(LX("pingFailed").d("responseCode", m_pingStream->getResponseCode())); - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::SERVER_SIDE_DISCONNECT); +HTTP2Transport::State HTTP2Transport::handleWaitingToRetryConnecting() { + ACSDK_DEBUG5(LX(__func__)); + + std::chrono::milliseconds timeout = TransportDefines::RETRY_TIMER.calculateTimeToRetry(m_connectRetryCount); + ACSDK_DEBUG5( + LX("handleConnectingWaitingToRetry").d("connectRetryCount", m_connectRetryCount).d("timeout", timeout.count())); + m_connectRetryCount++; + std::unique_lock lock(m_mutex); + m_wakeEvent.wait_for(lock, timeout, [this] { return m_state != State::WAITING_TO_RETRY_CONNECTING; }); + if (State::WAITING_TO_RETRY_CONNECTING == m_state) { + setStateLocked(State::CONNECTING, ConnectionStatusObserverInterface::ChangedReason::NONE); } - releasePingStream(); + return m_state; } -void HTTP2Transport::onPostConnected() { - setIsConnectedTrueUnlessStopping(); +HTTP2Transport::State HTTP2Transport::handlePostConnecting() { + ACSDK_DEBUG5(LX(__func__)); + if (m_postConnected) { + setState(State::CONNECTED, ConnectionStatusObserverInterface::ChangedReason::SUCCESS); + return State::CONNECTED; + } + return sendMessagesAndPings(State::POST_CONNECTING); } -void HTTP2Transport::setIsStopping(ConnectionStatusObserverInterface::ChangedReason reason) { - std::lock_guard lock(m_mutex); - setIsStoppingLocked(reason); +HTTP2Transport::State HTTP2Transport::handleConnected() { + ACSDK_DEBUG5(LX(__func__)); + notifyObserversOnConnected(); + return sendMessagesAndPings(State::CONNECTED); } -void HTTP2Transport::setIsStoppingLocked(ConnectionStatusObserverInterface::ChangedReason reason) { - if (m_isStopping) { - return; - } - - m_disconnectReason = reason; - m_isStopping = true; - m_wakeRetryTrigger.notify_one(); +HTTP2Transport::State HTTP2Transport::handleServerSideDisconnect() { + ACSDK_DEBUG5(LX(__func__)); + notifyObserversOnServerSideDisconnect(); + return State::DISCONNECTING; } -bool HTTP2Transport::isStopping() { - std::lock_guard lock(m_mutex); - return m_isStopping; -} +HTTP2Transport::State HTTP2Transport::handleDisconnecting() { + ACSDK_DEBUG5(LX(__func__)); -bool HTTP2Transport::isConnectedLocked() const { - return m_isConnected && !m_isStopping; + std::unique_lock lock(m_mutex); + while (State::DISCONNECTING == m_state && m_countOfUnfinishedMessageHandlers > 0) { + m_wakeEvent.wait(lock); + } + setStateLocked(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::SUCCESS); + return m_state; } -void HTTP2Transport::setIsConnectedTrueUnlessStopping() { +HTTP2Transport::State HTTP2Transport::handleShutdown() { + ACSDK_DEBUG5(LX(__func__)); + { std::lock_guard lock(m_mutex); - if (m_isConnected || m_isStopping) { - return; + for (auto request : m_requestQueue) { + request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); } - m_isConnected = true; + m_requestQueue.clear(); } - notifyObserversOnConnected(); -} + m_http2Connection->disconnect(); -void HTTP2Transport::setIsConnectedFalse() { - auto disconnectReason = ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR; - { - std::lock_guard lock(m_mutex); - if (m_disconnectedSent) { - return; - } - m_disconnectedSent = true; - m_isConnected = false; - disconnectReason = m_disconnectReason; - } + notifyObserversOnDisconnect(m_disconnectReason); - notifyObserversOnDisconnect(disconnectReason); + return State::SHUTDOWN; } -bool HTTP2Transport::enqueueRequest(std::shared_ptr request, bool ignoreConnectState) { +void HTTP2Transport::enqueueRequest(std::shared_ptr request, bool beforeConnected) { + ACSDK_DEBUG5(LX(__func__).d("beforeConnected", beforeConnected)); + if (!request) { ACSDK_ERROR(LX("enqueueRequestFailed").d("reason", "nullRequest")); - return false; + } + std::unique_lock lock(m_mutex); + bool allowed = false; + switch (m_state) { + case State::INIT: + case State::AUTHORIZING: + case State::CONNECTING: + case State::WAITING_TO_RETRY_CONNECTING: + case State::POST_CONNECTING: + allowed = beforeConnected; + break; + case State::CONNECTED: + allowed = !beforeConnected; + break; + case State::SERVER_SIDE_DISCONNECT: + case State::DISCONNECTING: + case State::SHUTDOWN: + allowed = false; + break; } - std::lock_guard lock(m_mutex); - if (!m_isStopping) { - if (ignoreConnectState || m_isConnected) { - ACSDK_DEBUG9(LX("enqueueRequest").sensitive("jsonContent", request->getJsonContent())); - m_requestQueue.push_back(request); - return true; - } else { - ACSDK_ERROR(LX("enqueueRequestFailed").d("reason", "isNotConnected")); - } + if (allowed) { + m_requestQueue.push_back(request); + m_wakeEvent.notify_all(); } else { - ACSDK_ERROR(LX("enqueueRequestFailed").d("reason", "isStopping")); + lock.unlock(); + ACSDK_ERROR(LX("enqueueRequestFailed").d("reason", "notInAllowedState").d("m_state", m_state)); + request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); } - return false; } -std::shared_ptr HTTP2Transport::dequeueRequest() { - std::lock_guard lock(m_mutex); - if (m_isStopping || m_requestQueue.empty()) { - return nullptr; +HTTP2Transport::State HTTP2Transport::sendMessagesAndPings(alexaClientSDK::acl::HTTP2Transport::State whileState) { + ACSDK_DEBUG5(LX(__func__).d("whileState", whileState)); + + std::unique_lock lock(m_mutex); + + auto canSendMessage = [this] { + return ( + !m_isMessageHandlerAwaitingResponse && !m_requestQueue.empty() && + (m_countOfUnfinishedMessageHandlers < MAX_MESSAGE_HANDLERS)); + }; + + auto wakePredicate = [this, whileState, canSendMessage] { + return whileState != m_state || canSendMessage() || + (std::chrono::steady_clock::now() > m_timeOfLastActivity + m_configuration.inactivityTimeout); + }; + + auto pingWakePredicate = [this, whileState, canSendMessage] { + return whileState != m_state || !m_pingHandler || canSendMessage(); + }; + + while (true) { + if (m_pingHandler) { + m_wakeEvent.wait(lock, pingWakePredicate); + } else { + m_wakeEvent.wait_until(lock, m_timeOfLastActivity + m_configuration.inactivityTimeout, wakePredicate); + } + + if (m_state != whileState) { + break; + } + + if (canSendMessage()) { + auto messageRequest = m_requestQueue.front(); + m_requestQueue.pop_front(); + + lock.unlock(); + + auto authToken = m_authDelegate->getAuthToken(); + if (!authToken.empty()) { + auto handler = MessageRequestHandler::create( + shared_from_this(), authToken, messageRequest, m_messageConsumer, m_attachmentManager); + if (!handler) { + messageRequest->sendCompleted(MessageRequestObserverInterface::Status::INTERNAL_ERROR); + } + } else { + ACSDK_ERROR(LX("failedToCreateMessageHandler").d("reason", "invalidAuth")); + messageRequest->sendCompleted(MessageRequestObserverInterface::Status::INVALID_AUTH); + } + + lock.lock(); + + } else if (std::chrono::steady_clock::now() > m_timeOfLastActivity + m_configuration.inactivityTimeout) { + if (!m_pingHandler) { + lock.unlock(); + + auto authToken = m_authDelegate->getAuthToken(); + if (!authToken.empty()) { + m_pingHandler = PingHandler::create(shared_from_this(), authToken); + } else { + ACSDK_ERROR(LX("failedToCreatePingHandler").d("reason", "invalidAuth")); + } + if (!m_pingHandler) { + ACSDK_ERROR(LX("shutDown").d("reason", "failedToCreatePingHandler")); + setState(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::PING_TIMEDOUT); + } + + lock.lock(); + } else { + ACSDK_DEBUG5(LX("m_pingHandler != nullptr")); + } + } } - auto result = m_requestQueue.front(); - m_requestQueue.pop_front(); - return result; + + return m_state; } -void HTTP2Transport::clearQueuedRequests() { +bool HTTP2Transport::setState(State newState, ConnectionStatusObserverInterface::ChangedReason changedReason) { std::lock_guard lock(m_mutex); - for (auto request : m_requestQueue) { - request->sendCompleted(MessageRequestObserverInterface::Status::NOT_CONNECTED); - } - m_requestQueue.clear(); + return setStateLocked(newState, changedReason); } -void HTTP2Transport::addObserver(std::shared_ptr observer) { - if (!observer) { - ACSDK_ERROR(LX("addObserverFailed").d("reason", "nullObserver")); - return; +bool HTTP2Transport::setStateLocked(State newState, ConnectionStatusObserverInterface::ChangedReason changedReason) { + ACSDK_DEBUG5(LX(__func__).d("newState", newState).d("changedReason", changedReason)); + + if (newState == m_state) { + ACSDK_DEBUG5(LX("alreadyInNewState")); + return true; } - std::lock_guard lock{m_observerMutex}; - m_observers.insert(observer); -} + bool allowed = false; + switch (newState) { + case State::INIT: + allowed = false; + break; + case State::AUTHORIZING: + allowed = State::INIT == m_state || State::WAITING_TO_RETRY_CONNECTING == m_state; + break; + case State::CONNECTING: + allowed = State::AUTHORIZING == m_state || State::WAITING_TO_RETRY_CONNECTING == m_state; + break; + case State::WAITING_TO_RETRY_CONNECTING: + allowed = State::CONNECTING == m_state; + break; + case State::POST_CONNECTING: + allowed = State::CONNECTING == m_state; + break; + case State::CONNECTED: + allowed = State::POST_CONNECTING == m_state; + break; + case State::SERVER_SIDE_DISCONNECT: + allowed = m_state != State::DISCONNECTING && m_state != State::SHUTDOWN; + break; + case State::DISCONNECTING: + allowed = m_state != State::SHUTDOWN; + break; + case State::SHUTDOWN: + allowed = true; + break; + } -void HTTP2Transport::removeObserver(std::shared_ptr observer) { - if (!observer) { - ACSDK_ERROR(LX("removeObserverFailed").d("reason", "nullObserver")); - return; + if (!allowed) { + ACSDK_ERROR(LX("stateChangeNotAllowed").d("oldState", m_state).d("newState", newState)); + return false; } - std::lock_guard lock{m_observerMutex}; - m_observers.erase(observer); + switch (newState) { + case State::INIT: + case State::AUTHORIZING: + case State::CONNECTING: + case State::WAITING_TO_RETRY_CONNECTING: + case State::POST_CONNECTING: + case State::CONNECTED: + break; + + case State::SERVER_SIDE_DISCONNECT: + case State::DISCONNECTING: + case State::SHUTDOWN: + if (ConnectionStatusObserverInterface::ChangedReason::NONE == m_disconnectReason) { + m_disconnectReason = changedReason; + } + break; + } + + m_state = newState; + m_wakeEvent.notify_all(); + + return true; } -void HTTP2Transport::notifyObserversOnServerSideDisconnect() { +void HTTP2Transport::notifyObserversOnConnected() { + ACSDK_DEBUG5(LX(__func__)); + std::unique_lock lock{m_observerMutex}; auto observers = m_observers; lock.unlock(); - for (auto observer : observers) { - observer->onServerSideDisconnect(shared_from_this()); + for (const auto& observer : observers) { + observer->onConnected(shared_from_this()); } } void HTTP2Transport::notifyObserversOnDisconnect(ConnectionStatusObserverInterface::ChangedReason reason) { - std::unique_lock lock{m_observerMutex}; - auto observers = m_observers; - lock.unlock(); + ACSDK_DEBUG5(LX(__func__)); - for (auto observer : observers) { - observer->onDisconnected(shared_from_this(), reason); + if (m_postConnect) { + m_postConnect->onDisconnect(); + m_postConnect.reset(); } -} -void HTTP2Transport::notifyObserversOnConnected() { std::unique_lock lock{m_observerMutex}; auto observers = m_observers; lock.unlock(); - for (auto observer : observers) { - observer->onConnected(shared_from_this()); + for (const auto& observer : observers) { + observer->onDisconnected(shared_from_this(), reason); } } -bool HTTP2Transport::releaseDownchannelStream( - bool removeFromMulti, - ConnectionStatusObserverInterface::ChangedReason* reason) { - if (m_downchannelStream) { - if (!releaseStream(m_downchannelStream, removeFromMulti, "downchannel")) { - if (reason) { - *reason = ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR; - return false; - } - } - m_downchannelStream.reset(); - } - return true; -} - -bool HTTP2Transport::releasePingStream(bool removeFromMulti) { - if (m_pingStream) { - if (!releaseStream(m_pingStream, removeFromMulti, "ping")) { - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - return false; - } - m_pingStream.reset(); - } - return true; -} +void HTTP2Transport::notifyObserversOnServerSideDisconnect() { + ACSDK_DEBUG5(LX(__func__)); -void HTTP2Transport::releaseAllEventStreams() { - // Get a copy of the event streams to release. - std::vector> eventStreams; - for (auto entry : m_activeStreams) { - auto stream = entry.second; - if (isEventStream(stream)) { - eventStreams.push_back(stream); - } - } - for (auto stream : eventStreams) { - releaseEventStream(stream); + if (m_postConnect) { + m_postConnect->onDisconnect(); + m_postConnect.reset(); } -} -bool HTTP2Transport::releaseEventStream(std::shared_ptr stream, bool removeFromMulti) { - if (stream) { - if (!releaseStream(stream, removeFromMulti, "event")) { - setIsStopping(ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - return false; - } - } - return true; -} + std::unique_lock lock{m_observerMutex}; + auto observers = m_observers; + lock.unlock(); -bool HTTP2Transport::releaseStream(std::shared_ptr stream, bool removeFromMulti, const std::string& name) { - auto handle = stream->getCurlHandle(); - m_activeStreams.erase(handle); - if (removeFromMulti) { - auto result = m_multi->removeHandle(handle); - if (result != CURLM_OK) { - ACSDK_ERROR(LX("releaseStreamFailed") - .d("reason", "removeHandleFailed") - .d("streamId", stream->getLogicalStreamId()) - .d("name", name)); - return false; - } + for (const auto& observer : observers) { + observer->onServerSideDisconnect(shared_from_this()); } - m_streamPool.releaseStream(stream); - return true; } -bool HTTP2Transport::isEventStream(std::shared_ptr stream) { - return stream != m_downchannelStream && stream != m_pingStream; +HTTP2Transport::State HTTP2Transport::getState() { + std::lock_guard lock(m_mutex); + return m_state; } } // namespace acl diff --git a/ACL/src/Transport/HTTP2TransportFactory.cpp b/ACL/src/Transport/HTTP2TransportFactory.cpp new file mode 100644 index 0000000000..7402338bd2 --- /dev/null +++ b/ACL/src/Transport/HTTP2TransportFactory.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include + +#include "ACL/Transport/HTTP2TransportFactory.h" +#include "ACL/Transport/HTTP2Transport.h" +#include "ACL/Transport/PostConnectSynchronizer.h" + +namespace alexaClientSDK { +namespace acl { + +using namespace alexaClientSDK::avsCommon::utils; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::avs::attachment; + +/// Key for the root node value containing configuration values for ACL. +static const std::string ACL_CONFIG_KEY = "acl"; +/// Key for the 'endpoint' value under the @c ACL_CONFIG_KEY configuration node. +static const std::string ENDPOINT_KEY = "endpoint"; +/// Default @c AVS endpoint to connect to. +static const std::string DEFAULT_AVS_ENDPOINT = "https://alexa.na.gateway.devices.a2z.com"; + +std::shared_ptr HTTP2TransportFactory::createTransport( + std::shared_ptr authDelegate, + std::shared_ptr attachmentManager, + const std::string& avsEndpoint, + std::shared_ptr messageConsumerInterface, + std::shared_ptr transportObserverInterface) { + auto connection = m_connectionFactory->createHTTP2Connection(); + if (!connection) { + return nullptr; + } + + std::string configuredEndpoint = avsEndpoint; + if (configuredEndpoint.empty()) { + alexaClientSDK::avsCommon::utils::configuration::ConfigurationNode::getRoot()[ACL_CONFIG_KEY].getString( + ENDPOINT_KEY, &configuredEndpoint, DEFAULT_AVS_ENDPOINT); + } + return HTTP2Transport::create( + authDelegate, + configuredEndpoint, + connection, + messageConsumerInterface, + attachmentManager, + transportObserverInterface, + m_postConnectFactory); +} + +HTTP2TransportFactory::HTTP2TransportFactory( + std::shared_ptr connectionFactory, + std::shared_ptr postConnectFactory) : + m_connectionFactory{connectionFactory}, + m_postConnectFactory{postConnectFactory} { +} + +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/src/Transport/MessageRequestHandler.cpp b/ACL/src/Transport/MessageRequestHandler.cpp new file mode 100644 index 0000000000..49b9ff8fe1 --- /dev/null +++ b/ACL/src/Transport/MessageRequestHandler.cpp @@ -0,0 +1,317 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "ACL/Transport/HTTP2Transport.h" +#include "ACL/Transport/MimeResponseSink.h" +#include "ACL/Transport/MessageRequestHandler.h" + +namespace alexaClientSDK { +namespace acl { + +using namespace avsCommon::avs::attachment; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils::http; +using namespace avsCommon::utils::http2; + +/// URL to send events to +const static std::string AVS_EVENT_URL_PATH_EXTENSION = "/v20160207/events"; + +/// Boundary for mime encoded requests +const static std::string MIME_BOUNDARY = "WhooHooZeerOoonie!"; + +/// Timeout for transmission of data on a given stream +static const std::chrono::seconds STREAM_PROGRESS_TIMEOUT = std::chrono::seconds(30); + +/// Mime header strings for mime parts containing json payloads. +static const std::vector JSON_MIME_PART_HEADER_LINES = { + "Content-Disposition: form-data; name=\"metadata\"", + "Content-Type: application/json"}; + +/// Mime Content-Disposition line before name. +static const std::string CONTENT_DISPOSITION_PREFIX = "Content-Disposition: form-data; name=\""; + +/// Mime Content-Disposition line after name. +static const std::string CONTENT_DISPOSITION_SUFFIX = "\""; + +/// Mime Content-Type for attchments. +static const std::string ATTACHMENT_CONTENT_TYPE = "Content-Type: application/octet-stream"; + +/// Prefix for the ID of message requests. +static const std::string MESSAGEREQUEST_ID_PREFIX = "AVSEvent-"; + +/// String to identify log entries originating from this file. +static const std::string TAG("MessageRequestHandler"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +MessageRequestHandler::~MessageRequestHandler() { + reportMessageRequestAcknowledged(); + reportMessageRequestFinished(); +} + +std::shared_ptr MessageRequestHandler::create( + std::shared_ptr context, + const std::string& authToken, + std::shared_ptr messageRequest, + std::shared_ptr messageConsumer, + std::shared_ptr attachmentManager) { + ACSDK_DEBUG5(LX(__func__).d("context", context.get()).d("messageRequest", messageRequest.get())); + + if (!context) { + ACSDK_CRITICAL(LX("MessageRequestHandlerCreateFailed").d("reason", "nullHttp2Transport")); + return nullptr; + } + + if (authToken.empty()) { + ACSDK_DEBUG9(LX("createFailed").d("reason", "emptyAuthToken")); + return nullptr; + } + + std::shared_ptr handler(new MessageRequestHandler(context, authToken, messageRequest)); + + // Allow custom path extension, if provided by the sender of the MessageRequest + + auto url = context->getEndpoint(); + if (messageRequest->getUriPathExtension().empty()) { + url += AVS_EVENT_URL_PATH_EXTENSION; + } else { + url += messageRequest->getUriPathExtension(); + } + + HTTP2RequestConfig cfg{HTTP2RequestType::POST, url, MESSAGEREQUEST_ID_PREFIX}; + cfg.setRequestSource(std::make_shared(MIME_BOUNDARY, handler)); + cfg.setResponseSink(std::make_shared( + std::make_shared(handler, messageConsumer, attachmentManager, cfg.getId()))); + cfg.setActivityTimeout(STREAM_PROGRESS_TIMEOUT); + + context->onMessageRequestSent(); + auto request = context->createAndSendRequest(cfg); + + if (!request) { + handler->reportMessageRequestAcknowledged(); + handler->reportMessageRequestFinished(); + ACSDK_ERROR(LX("MessageRequestHandlerCreateFailed").d("reason", "createAndSendRequestFailed")); + return nullptr; + } + + return handler; +} + +MessageRequestHandler::MessageRequestHandler( + std::shared_ptr context, + const std::string& authToken, + std::shared_ptr messageRequest) : + ExchangeHandler{context, authToken}, + m_messageRequest{messageRequest}, + m_json{messageRequest->getJsonContent()}, + m_jsonNext{m_json.c_str()}, + m_countOfJsonBytesLeft{m_json.size()}, + m_countOfPartsSent{0}, + m_wasMessageRequestAcknowledgeReported{false}, + m_wasMessageRequestFinishedReported{false}, + m_responseCode{0} { + ACSDK_DEBUG5(LX(__func__).d("context", context.get()).d("messageRequest", messageRequest.get())); +} + +void MessageRequestHandler::reportMessageRequestAcknowledged() { + ACSDK_DEBUG5(LX(__func__)); + if (!m_wasMessageRequestAcknowledgeReported) { + m_wasMessageRequestAcknowledgeReported = true; + m_context->onMessageRequestAcknowledged(); + } +} + +void MessageRequestHandler::reportMessageRequestFinished() { + ACSDK_DEBUG5(LX(__func__)); + if (!m_wasMessageRequestFinishedReported) { + m_wasMessageRequestFinishedReported = true; + m_context->onMessageRequestFinished(); + } +} + +std::vector MessageRequestHandler::getRequestHeaderLines() { + ACSDK_DEBUG5(LX(__func__)); + + m_context->onActivity(); + + return {m_authHeader}; +} + +HTTP2GetMimeHeadersResult MessageRequestHandler::getMimePartHeaderLines() { + ACSDK_DEBUG5(LX(__func__)); + + m_context->onActivity(); + + if (0 == m_countOfPartsSent) { + return HTTP2GetMimeHeadersResult(JSON_MIME_PART_HEADER_LINES); + } else if (static_cast(m_countOfPartsSent) <= m_messageRequest->attachmentReadersCount()) { + m_namedReader = m_messageRequest->getAttachmentReader(m_countOfPartsSent - 1); + if (m_namedReader) { + return HTTP2GetMimeHeadersResult( + {CONTENT_DISPOSITION_PREFIX + m_namedReader->name + CONTENT_DISPOSITION_SUFFIX, + ATTACHMENT_CONTENT_TYPE}); + } else { + ACSDK_ERROR(LX("getMimePartHeaderLinesFailed").d("reason", "nullReader").d("index", m_countOfPartsSent)); + return HTTP2GetMimeHeadersResult::ABORT; + } + } else { + return HTTP2GetMimeHeadersResult::COMPLETE; + } +} + +HTTP2SendDataResult MessageRequestHandler::onSendMimePartData(char* bytes, size_t size) { + ACSDK_DEBUG5(LX(__func__).d("size", size)); + + m_context->onActivity(); + + if (0 == m_countOfPartsSent) { + if (m_countOfJsonBytesLeft != 0) { + size_t countToCopy = (m_countOfJsonBytesLeft <= size) ? m_countOfJsonBytesLeft : size; + std::copy(m_jsonNext, m_jsonNext + countToCopy, bytes); + m_jsonNext += countToCopy; + m_countOfJsonBytesLeft -= countToCopy; + return HTTP2SendDataResult(countToCopy); + } else { + m_countOfPartsSent++; + return HTTP2SendDataResult::COMPLETE; + } + } else if (m_namedReader) { + auto readStatus = AttachmentReader::ReadStatus::OK; + auto bytesRead = m_namedReader->reader->read(bytes, size, &readStatus); + ACSDK_DEBUG5(LX("attachmentRead").d("readStatus", (int)readStatus).d("bytesRead", bytesRead)); + switch (readStatus) { + // The good cases. + case AttachmentReader::ReadStatus::OK: + case AttachmentReader::ReadStatus::OK_WOULDBLOCK: + case AttachmentReader::ReadStatus::OK_TIMEDOUT: + return bytesRead != 0 ? HTTP2SendDataResult(bytesRead) : HTTP2SendDataResult::PAUSE; + + case AttachmentReader::ReadStatus::OK_OVERRUN_RESET: + return HTTP2SendDataResult::ABORT; + + case AttachmentReader::ReadStatus::CLOSED: + // Stream consumed. Move on to next part. + m_namedReader.reset(); + m_countOfPartsSent++; + return HTTP2SendDataResult::COMPLETE; + + // Handle any attachment read errors. + case AttachmentReader::ReadStatus::ERROR_OVERRUN: + case AttachmentReader::ReadStatus::ERROR_INTERNAL: + // Stream failure. Abort sending the request. + return HTTP2SendDataResult::ABORT; + + case AttachmentReader::ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE: + return HTTP2SendDataResult::PAUSE; + } + } + + ACSDK_ERROR(LX("onSendMimePartDataFailed").d("reason", "noMoreAttachments")); + return HTTP2SendDataResult::ABORT; +} + +void MessageRequestHandler::onActivity() { + m_context->onActivity(); +} + +bool MessageRequestHandler::onReceiveResponseCode(long responseCode) { + ACSDK_DEBUG5(LX(__func__).d("responseCode", responseCode)); + + // TODO ACSDK-1839: Provide MessageRequestObserverInterface immediate notification of receipt of response code. + + reportMessageRequestAcknowledged(); + + if (HTTPResponseCode::CLIENT_ERROR_FORBIDDEN == intToHTTPResponseCode(responseCode)) { + m_context->onForbidden(m_authToken); + } + + m_responseCode = responseCode; + return true; +} + +void MessageRequestHandler::onResponseFinished(HTTP2ResponseFinishedStatus status, const std::string& nonMimeBody) { + ACSDK_DEBUG5(LX(__func__).d("status", status).d("responseCode", m_responseCode)); + + if (HTTP2ResponseFinishedStatus::TIMEOUT == status) { + m_context->onMessageRequestTimeout(); + } + + reportMessageRequestAcknowledged(); + reportMessageRequestFinished(); + + if ((intToHTTPResponseCode(m_responseCode) != HTTPResponseCode::SUCCESS_OK) && !nonMimeBody.empty()) { + m_messageRequest->exceptionReceived(nonMimeBody); + } + + // Hash to allow use of HTTP2ResponseFinishedStatus as the key in an unordered_map. + struct statusHash { + size_t operator()(const HTTP2ResponseFinishedStatus& key) const { + return static_cast(key); + } + }; + + // Mapping HTTP2ResponseFinishedStatus to a MessageRequestObserverInterface::Status. Note that no mapping is + // provided from the COMPLETE status so that the logic below falls through to map the HTTPResponseCode value + // from the completed requests to the appropriate MessageRequestObserverInterface value. + static const std::unordered_map + statusToResult = { + {HTTP2ResponseFinishedStatus::INTERNAL_ERROR, MessageRequestObserverInterface::Status::INTERNAL_ERROR}, + {HTTP2ResponseFinishedStatus::CANCELLED, MessageRequestObserverInterface::Status::CANCELED}, + {HTTP2ResponseFinishedStatus::TIMEOUT, MessageRequestObserverInterface::Status::TIMEDOUT}}; + + // Map HTTPResponseCode values to MessageRequestObserverInterface::Status values. + static const std::unordered_map responseToResult = { + {HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED, MessageRequestObserverInterface::Status::INTERNAL_ERROR}, + {HTTPResponseCode::SUCCESS_OK, MessageRequestObserverInterface::Status::SUCCESS}, + {HTTPResponseCode::SUCCESS_NO_CONTENT, MessageRequestObserverInterface::Status::SUCCESS_NO_CONTENT}, + {HTTPResponseCode::CLIENT_ERROR_BAD_REQUEST, MessageRequestObserverInterface::Status::BAD_REQUEST}, + {HTTPResponseCode::CLIENT_ERROR_FORBIDDEN, MessageRequestObserverInterface::Status::INVALID_AUTH}, + {HTTPResponseCode::SERVER_ERROR_INTERNAL, MessageRequestObserverInterface::Status::SERVER_INTERNAL_ERROR_V2}}; + + auto result = MessageRequestObserverInterface::Status::INTERNAL_ERROR; + + if (HTTP2ResponseFinishedStatus::COMPLETE == status) { + auto responseIterator = responseToResult.find(m_responseCode); + if (responseIterator != responseToResult.end()) { + result = responseIterator->second; + } else { + result = MessageRequestObserverInterface::Status::SERVER_OTHER_ERROR; + } + } else { + auto statusIterator = statusToResult.find(status); + if (statusIterator != statusToResult.end()) { + result = statusIterator->second; + } + } + + m_messageRequest->sendCompleted(result); +} + +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/src/Transport/MessageRouter.cpp b/ACL/src/Transport/MessageRouter.cpp index 93293cbe36..05eee03547 100644 --- a/ACL/src/Transport/MessageRouter.cpp +++ b/ACL/src/Transport/MessageRouter.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ static const std::string TAG("MessageRouter"); MessageRouter::MessageRouter( std::shared_ptr authDelegate, std::shared_ptr attachmentManager, + std::shared_ptr transportFactory, const std::string& avsEndpoint) : MessageRouterInterface{"MessageRouter"}, m_avsEndpoint{avsEndpoint}, @@ -50,7 +51,8 @@ MessageRouter::MessageRouter( m_connectionStatus{ConnectionStatusObserverInterface::Status::DISCONNECTED}, m_connectionReason{ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST}, m_isEnabled{false}, - m_attachmentManager{attachmentManager} { + m_attachmentManager{attachmentManager}, + m_transportFactory{transportFactory} { } MessageRouterInterface::ConnectionStatus MessageRouter::getConnectionStatus() { @@ -60,13 +62,22 @@ MessageRouterInterface::ConnectionStatus MessageRouter::getConnectionStatus() { void MessageRouter::enable() { std::lock_guard lock{m_connectionMutex}; - m_isEnabled = true; - if (!m_activeTransport || !m_activeTransport->isConnected()) { - setConnectionStatusLocked( - ConnectionStatusObserverInterface::Status::PENDING, - ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); - createActiveTransportLocked(); + + if (m_isEnabled) { + ACSDK_DEBUG0(LX(__func__).m("already enabled")); + return; } + + if (m_activeTransport != nullptr) { + ACSDK_ERROR(LX("enableFailed").d("reason", "activeTransportNotNull")); + return; + } + + m_isEnabled = true; + setConnectionStatusLocked( + ConnectionStatusObserverInterface::Status::PENDING, + ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); + createActiveTransportLocked(); } void MessageRouter::doShutdown() { @@ -80,7 +91,6 @@ void MessageRouter::disable() { disconnectAllTransportsLocked(lock, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); } -// TODO: ACSDK-421: Revert this to use send(). void MessageRouter::sendMessage(std::shared_ptr request) { if (!request) { ACSDK_ERROR(LX("sendFailed").d("reason", "nullRequest")); @@ -112,11 +122,24 @@ void MessageRouter::setAVSEndpoint(const std::string& avsEndpoint) { void MessageRouter::onConnected(std::shared_ptr transport) { std::unique_lock lock{m_connectionMutex}; - if (m_isEnabled) { - setConnectionStatusLocked( - ConnectionStatusObserverInterface::Status::CONNECTED, - ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); + + /* + * Transport shutdown might be asynchronous,so the following scenarios are valid, + * but we shouldn't update the connection status. + */ + if (!m_isEnabled) { + ACSDK_DEBUG0(LX("onConnectedWhenDisabled")); + return; } + + if (transport != m_activeTransport) { + ACSDK_DEBUG0(LX("onInactiveTransportConnected")); + return; + } + + setConnectionStatusLocked( + ConnectionStatusObserverInterface::Status::CONNECTED, + ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); } void MessageRouter::onDisconnected( @@ -134,15 +157,22 @@ void MessageRouter::onDisconnected( if (transport == m_activeTransport) { m_activeTransport.reset(); - // Update status. If transitioning to PENDING, also initiate the reconnect. - if (ConnectionStatusObserverInterface::Status::CONNECTED == m_connectionStatus) { - if (m_isEnabled) { - setConnectionStatusLocked(ConnectionStatusObserverInterface::Status::PENDING, reason); - createActiveTransportLocked(); - } else if (m_transports.empty()) { - setConnectionStatusLocked(ConnectionStatusObserverInterface::Status::DISCONNECTED, reason); - } + switch (m_connectionStatus) { + case ConnectionStatusObserverInterface::Status::PENDING: + case ConnectionStatusObserverInterface::Status::CONNECTED: + if (m_isEnabled && reason != ConnectionStatusObserverInterface::ChangedReason::UNRECOVERABLE_ERROR) { + setConnectionStatusLocked(ConnectionStatusObserverInterface::Status::PENDING, reason); + createActiveTransportLocked(); + } else if (m_transports.empty()) { + setConnectionStatusLocked(ConnectionStatusObserverInterface::Status::DISCONNECTED, reason); + } + return; + + case ConnectionStatusObserverInterface::Status::DISCONNECTED: + return; } + + ACSDK_ERROR(LX("unhandledConnectionStatus").d("connectionStatus", static_cast(m_connectionStatus))); } } @@ -203,8 +233,8 @@ void MessageRouter::notifyObserverOnReceive(const std::string& contextId, const } void MessageRouter::createActiveTransportLocked() { - auto transport = - createTransport(m_authDelegate, m_attachmentManager, m_avsEndpoint, shared_from_this(), shared_from_this()); + auto transport = m_transportFactory->createTransport( + m_authDelegate, m_attachmentManager, m_avsEndpoint, shared_from_this(), shared_from_this()); if (transport && transport->connect()) { m_transports.push_back(transport); m_activeTransport = transport; diff --git a/ACL/src/Transport/MimeParser.cpp b/ACL/src/Transport/MimeParser.cpp deleted file mode 100644 index 32adc08d40..0000000000 --- a/ACL/src/Transport/MimeParser.cpp +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include -#include "ACL/Transport/MimeParser.h" -#include - -namespace alexaClientSDK { -namespace acl { - -using namespace avsCommon::utils; -using namespace avsCommon::avs::attachment; - -/// String to identify log entries originating from this file. -static const std::string TAG("MimeParser"); - -/** - * Create a LogEntry using this file's TAG and the specified event string. - * - * @param The event string for this @c LogEntry. - */ -#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) - -/// MIME field name for a part's MIME type -static const std::string MIME_CONTENT_TYPE_FIELD_NAME = "Content-Type"; -/// MIME field name for a part's reference id -static const std::string MIME_CONTENT_ID_FIELD_NAME = "Content-ID"; -/// MIME type for JSON payloads -static const std::string MIME_JSON_CONTENT_TYPE = "application/json"; -/// MIME type for binary streams -static const std::string MIME_OCTET_STREAM_CONTENT_TYPE = "application/octet-stream"; -/// Size of CLRF in chars -static const int LEADING_CRLF_CHAR_SIZE = 2; -/// ASCII value of CR -static const char CARRIAGE_RETURN_ASCII = 13; -/// ASCII value of LF -static const char LINE_FEED_ASCII = 10; - -/** - * Sanitize the Content-ID field in MIME header. - * - * This function is necessary per RFC-2392: A "cid" URL is converted to the corresponding Content-ID message header - * MIME by removing the "cid:" prefix, and enclosing the remaining parts with an angle bracket pair, "<" and ">". - * For example, "cid:foo4%25foo1@bar.net" corresponds to Content-ID: - * - * @param mimeContentId The raw content ID value in MIME header. - * @return The sanitized content ID. - */ -std::string sanitizeContentId(const std::string& mimeContentId) { - std::string sanitizedContentId; - if (mimeContentId.empty()) { - ACSDK_ERROR(LX("sanitizeContentIdFailed").d("reason", "emptyMimeContentId")); - } else if (('<' == mimeContentId.front()) && ('>' == mimeContentId.back())) { - // Getting attachment ID within angle bracket <>. - sanitizedContentId = mimeContentId.substr(1, mimeContentId.size() - 2); - } else { - sanitizedContentId = mimeContentId; - } - return sanitizedContentId; -} - -MimeParser::MimeParser( - std::shared_ptr messageConsumer, - std::shared_ptr attachmentManager) : - m_receivedFirstChunk{false}, - m_currDataType{ContentType::NONE}, - m_messageConsumer{messageConsumer}, - m_attachmentManager{attachmentManager}, - m_dataParsedStatus{DataParsedStatus::OK}, - m_currentByteProgress{0}, - m_totalSuccessfullyProcessedBytes{0}, - m_isAttachmentWriterBufferFull{false} { - m_multipartReader.onPartBegin = MimeParser::partBeginCallback; - m_multipartReader.onPartData = MimeParser::partDataCallback; - m_multipartReader.onPartEnd = MimeParser::partEndCallback; - m_multipartReader.userData = this; -} - -void MimeParser::partBeginCallback(const MultipartHeaders& headers, void* userData) { - MimeParser* parser = static_cast(userData); - - if (parser->m_dataParsedStatus != MimeParser::DataParsedStatus::OK) { - ACSDK_ERROR( - LX("partBeginCallbackFailed").d("reason", "mimeParsingFailed").d("status", parser->m_dataParsedStatus)); - return; - } - - std::string contentType = headers[MIME_CONTENT_TYPE_FIELD_NAME]; - if (contentType.find(MIME_JSON_CONTENT_TYPE) != std::string::npos) { - parser->m_currDataType = MimeParser::ContentType::JSON; - } else if (contentType.find(MIME_OCTET_STREAM_CONTENT_TYPE) != std::string::npos) { - if (1 == headers.count(MIME_CONTENT_ID_FIELD_NAME)) { - auto contentId = sanitizeContentId(headers[MIME_CONTENT_ID_FIELD_NAME]); - auto attachmentId = - parser->m_attachmentManager->generateAttachmentId(parser->m_attachmentContextId, contentId); - - if (!parser->m_attachmentWriter && attachmentId != parser->m_attachmentIdBeingReceived) { - parser->m_attachmentWriter = parser->m_attachmentManager->createWriter(attachmentId); - if (!parser->m_attachmentWriter) { - ACSDK_ERROR(LX("partBeginCallbackFailed") - .d("reason", "createWriterFailed") - .d("attachmentId", attachmentId)); - } - } - } - parser->m_currDataType = MimeParser::ContentType::ATTACHMENT; - } -} - -MimeParser::DataParsedStatus MimeParser::writeDataToAttachment(const char* buffer, size_t size) { - // Error case. We can't process the attachment. - if (!m_attachmentWriter) { - ACSDK_ERROR(LX("writeDataToAttachmentFailed").d("reason", "nullAttachmentWriter")); - return MimeParser::DataParsedStatus::ERROR; - } - - auto writeStatus = AttachmentWriter::WriteStatus::OK; - auto numWritten = m_attachmentWriter->write(const_cast(buffer), size, &writeStatus); - - // The underlying memory was closed elsewhere. - if (AttachmentWriter::WriteStatus::CLOSED == writeStatus) { - ACSDK_WARN(LX("writeDataToAttachmentFailed").d("reason", "attachmentWriterIsClosed")); - return MimeParser::DataParsedStatus::ERROR; - } - - // A low-level error with the Attachment occurred. - if (AttachmentWriter::WriteStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE == writeStatus || - AttachmentWriter::WriteStatus::ERROR_INTERNAL == writeStatus) { - ACSDK_ERROR(LX("writeDataToAttachmentFailed").d("reason", "attachmentWriterInternalError")); - return MimeParser::DataParsedStatus::ERROR; - } - - // We're blocked on a slow reader. - if (AttachmentWriter::WriteStatus::OK_BUFFER_FULL == writeStatus) { - setAttachmentWriterBufferFull(true); - return MimeParser::DataParsedStatus::INCOMPLETE; - } - - // A final sanity check to ensure we wrote the data we intended to. - if (AttachmentWriter::WriteStatus::OK == writeStatus && numWritten != size) { - ACSDK_ERROR(LX("writeDataToAttachmentFailed").d("reason", "writeTruncated")); - return MimeParser::DataParsedStatus::ERROR; - } - - setAttachmentWriterBufferFull(false); - return MimeParser::DataParsedStatus::OK; -} - -void MimeParser::partDataCallback(const char* buffer, size_t size, void* userData) { - MimeParser* parser = static_cast(userData); - - if (MimeParser::DataParsedStatus::INCOMPLETE == parser->m_dataParsedStatus) { - ACSDK_DEBUG9(LX("partDataCallbackIgnored").d("reason", "attachmentWriterFullBuffer")); - return; - } - - if (parser->m_dataParsedStatus != MimeParser::DataParsedStatus::OK) { - ACSDK_ERROR( - LX("partDataCallbackFailed").d("reason", "mimeParsingError").d("status", parser->m_dataParsedStatus)); - return; - } - - // If we've already processed any of this part in a previous incomplete iteration, let's not process it twice. - if (!parser->shouldProcessBytes(size)) { - ACSDK_DEBUG9(LX("partDataCallbackSkipped").d("reason", "bytesAlreadyProcessed")); - parser->updateCurrentByteProgress(size); - parser->m_dataParsedStatus = MimeParser::DataParsedStatus::OK; - return; - } - - // Ok, there is data in this part we've not processed yet. - - // Let's do the math so we only process bytes within this part that have not been processed before. - auto bytesAlreadyProcessed = parser->m_totalSuccessfullyProcessedBytes - parser->m_currentByteProgress; - auto bytesToProcess = size - bytesAlreadyProcessed; - - // Sanity check that we actually have correctly bounded work to do. - if (0 == bytesToProcess || bytesToProcess > size) { - ACSDK_ERROR(LX("partDataCallbackFailed") - .d("reason", "invalidBytesToProcess") - .d("bytesToProcess", bytesToProcess) - .d("totalSize", size)); - parser->m_dataParsedStatus = MimeParser::DataParsedStatus::ERROR; - return; - } - - // Find the correct offset in the data to process. - const char* dataProcessingPoint = &(buffer[bytesAlreadyProcessed]); - - switch (parser->m_currDataType) { - case MimeParser::ContentType::JSON: - parser->m_directiveBeingReceived.append(dataProcessingPoint, bytesToProcess); - parser->updateCurrentByteProgress(bytesToProcess); - break; - case MimeParser::ContentType::ATTACHMENT: - parser->m_dataParsedStatus = parser->writeDataToAttachment(dataProcessingPoint, bytesToProcess); - if (MimeParser::DataParsedStatus::OK == parser->m_dataParsedStatus) { - parser->updateCurrentByteProgress(bytesToProcess); - } - break; - default: - ACSDK_ERROR(LX("partDataCallbackFailed").d("reason", "unsupportedContentType")); - parser->m_dataParsedStatus = MimeParser::DataParsedStatus::ERROR; - } -} - -void MimeParser::partEndCallback(void* userData) { - MimeParser* parser = static_cast(userData); - - if (parser->m_dataParsedStatus != MimeParser::DataParsedStatus::OK) { - ACSDK_ERROR( - LX("partEndCallbackFailed").d("reason", "mimeParsingError").d("status", parser->m_dataParsedStatus)); - return; - } - - switch (parser->m_currDataType) { - case MimeParser::ContentType::JSON: - if (!parser->m_messageConsumer) { - ACSDK_ERROR(LX("partEndCallbackFailed") - .d("reason", "nullMessageConsumer") - .d("status", parser->m_dataParsedStatus)); - break; - } - // Check there's data to send out, because in a re-drive we may skip a directive that's been seen before. - if (parser->m_directiveBeingReceived != "") { - parser->m_messageConsumer->consumeMessage( - parser->m_attachmentContextId, parser->m_directiveBeingReceived); - parser->m_directiveBeingReceived = ""; - } - break; - - case MimeParser::ContentType::ATTACHMENT: - parser->closeActiveAttachmentWriter(); - break; - - default: - ACSDK_ERROR(LX("partEndCallbackFailed").d("reason", "unsupportedContentType")); - } -} - -void MimeParser::reset() { - m_currDataType = ContentType::NONE; - m_receivedFirstChunk = false; - m_multipartReader.reset(); - m_dataParsedStatus = DataParsedStatus::OK; - closeActiveAttachmentWriter(); - m_isAttachmentWriterBufferFull = false; -} - -void MimeParser::setAttachmentContextId(const std::string& attachmentContextId) { - m_attachmentContextId = attachmentContextId; -} - -void MimeParser::setBoundaryString(const std::string& boundaryString) { - m_multipartReader.setBoundary(boundaryString); -} - -/* - * This function is designed to allow the processing of MIME multipart data in chunks. If a chunk of data cannot - * be fully processed, this class allows that chunk to be re-driven until it returns @c DataParsedStatus::OK. - * - * Each invocation of of this function may result any number of directives and attachments being parsed out, - * and then routed out to observers. - * - * As a brief example of how a parse might fail, and what the internal logic needs to do, let's imagine a multipart - * data chunk arranged as follows (let's say it's chunk x > 0 of the data): - * - * [ part of directive 2 | attachment 2 | part of directive 3 ] - * ^ - * | - * - * If the chunk fails while processing attachment 2 (per the arrow above), then the logic here needs to be careful to - * ensure that a re-drive is possible, without confusing the underlying MultiPartReader object, which is stateful. - * - * The solution is to capture the state of the MultiPartReader object at the start of the function, and if the parse - * is not successful, restore the object to its initial state, allowing a re-drive. Otherwise it is left in its - * resulting state for subsequent data chunks. - */ -MimeParser::DataParsedStatus MimeParser::feed(char* data, size_t length) { - // Capture old state in case the complete parse does not succeed (see function comments). - auto oldReader = m_multipartReader; - auto oldReceivedFirstChunk = m_receivedFirstChunk; - auto oldDataType = m_currDataType; - - /** - * Our parser expects no leading CRLF in the data stream. Additionally downchannel streams - * include this CRLF but event streams do not. So just remove the CRLF in the first chunk of the stream - * if it exists. - */ - if (!m_receivedFirstChunk) { - if (length >= LEADING_CRLF_CHAR_SIZE && CARRIAGE_RETURN_ASCII == data[0] && LINE_FEED_ASCII == data[1]) { - data += LEADING_CRLF_CHAR_SIZE; - length -= LEADING_CRLF_CHAR_SIZE; - m_receivedFirstChunk = true; - } - } - - // Initialize this before all the feed() callbacks happen (since this persists from previous call). - m_currentByteProgress = 0; - m_dataParsedStatus = DataParsedStatus::OK; - - m_multipartReader.feed(data, length); - - if (DataParsedStatus::OK == m_dataParsedStatus) { - // We parsed all the data ok - reset our counters for the next potential feed of data. - resetByteProgressCounters(); - } else { - // There was a problem parsing the data - we need to reset the previous mime parser state for re-drive. - m_multipartReader = oldReader; - m_receivedFirstChunk = oldReceivedFirstChunk; - m_currDataType = oldDataType; - } - - return m_dataParsedStatus; -} - -std::shared_ptr MimeParser::getMessageConsumer() { - return m_messageConsumer; -} - -void MimeParser::closeActiveAttachmentWriter() { - m_attachmentIdBeingReceived = ""; - m_attachmentWriter.reset(); -} - -bool MimeParser::shouldProcessBytes(size_t size) const { - return (m_currentByteProgress + size) > m_totalSuccessfullyProcessedBytes; -} - -void MimeParser::updateCurrentByteProgress(size_t size) { - m_currentByteProgress += size; - if (m_currentByteProgress > m_totalSuccessfullyProcessedBytes) { - m_totalSuccessfullyProcessedBytes = m_currentByteProgress; - } -} - -void MimeParser::resetByteProgressCounters() { - m_totalSuccessfullyProcessedBytes = 0; - m_currentByteProgress = 0; -} - -void MimeParser::setAttachmentWriterBufferFull(bool isFull) { - if (isFull == m_isAttachmentWriterBufferFull) { - return; - } - ACSDK_DEBUG9(LX("setAttachmentWriterBufferFull").d("full", isFull)); - m_isAttachmentWriterBufferFull = isFull; -} - -} // namespace acl -} // namespace alexaClientSDK diff --git a/ACL/src/Transport/MimeResponseSink.cpp b/ACL/src/Transport/MimeResponseSink.cpp new file mode 100644 index 0000000000..0ef9bcdd05 --- /dev/null +++ b/ACL/src/Transport/MimeResponseSink.cpp @@ -0,0 +1,284 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include "ACL/Transport/MimeResponseSink.h" + +namespace alexaClientSDK { +namespace acl { + +using namespace avsCommon::avs::attachment; +using namespace avsCommon::utils::http2; + +#ifdef DEBUG +/// Carriage return +static const char CR = 0x0D; +#endif + +/// The prefix of request IDs passed back in the header of AVS replies. +static const std::string X_AMZN_REQUESTID_PREFIX = "x-amzn-requestid:"; + +/// MIME field name for a part's MIME type +static const std::string MIME_CONTENT_TYPE_FIELD_NAME = "Content-Type"; + +/// MIME field name for a part's reference id +static const std::string MIME_CONTENT_ID_FIELD_NAME = "Content-ID"; + +/// MIME type for JSON payloads +static const std::string MIME_JSON_CONTENT_TYPE = "application/json"; + +/// MIME type for binary streams +static const std::string MIME_OCTET_STREAM_CONTENT_TYPE = "application/octet-stream"; + +/// Maximum size of non-mime body to accumulate. +static const size_t NON_MIME_BODY_MAX_SIZE = 4096; + +/// String to identify log entries originating from this file. +static const std::string TAG("MimeResponseSink"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/** + * Sanitize the Content-ID field in MIME header. + * + * This function is necessary per RFC-2392: A "cid" URL is converted to the corresponding Content-ID message header + * MIME by removing the "cid:" prefix, and enclosing the remaining parts with an angle bracket pair, "<" and ">". + * For example, "cid:foo4%25foo1@bar.net" corresponds to Content-ID: + * + * @param mimeContentId The raw content ID value in MIME header. + * @return The sanitized content ID. + */ +static std::string sanitizeContentId(const std::string& mimeContentId) { + std::string sanitizedContentId; + if (mimeContentId.empty()) { + ACSDK_ERROR(LX("sanitizeContentIdFailed").d("reason", "emptyMimeContentId")); + } else if (('<' == mimeContentId.front()) && ('>' == mimeContentId.back())) { + // Getting attachment ID within angle bracket <>. + sanitizedContentId = mimeContentId.substr(1, mimeContentId.size() - 2); + } else { + sanitizedContentId = mimeContentId; + } + return sanitizedContentId; +} + +MimeResponseSink::MimeResponseSink( + std::shared_ptr handler, + std::shared_ptr messageConsumer, + std::shared_ptr attachmentManager, + std::string attachmentContextId) : + m_handler{handler}, + m_messageConsumer{messageConsumer}, + m_attachmentManager{attachmentManager}, + m_attachmentContextId{std::move(attachmentContextId)} { + ACSDK_DEBUG5(LX(__func__).d("handler", handler.get())); +} + +bool MimeResponseSink::onReceiveResponseCode(long responseCode) { + ACSDK_DEBUG5(LX(__func__).d("responseCode", responseCode)); + + if (m_handler) { + m_handler->onActivity(); + return m_handler->onReceiveResponseCode(responseCode); + } + return false; +} + +bool MimeResponseSink::onReceiveHeaderLine(const std::string& line) { + ACSDK_DEBUG5(LX(__func__).d("line", line)); + + if (m_handler) { + m_handler->onActivity(); + } + +#ifdef DEBUG + if (0 == line.find(X_AMZN_REQUESTID_PREFIX)) { + auto end = line.find(CR); + ACSDK_DEBUG0(LX("receivedRequestId").d("value", line.substr(0, end))); + } +#endif + return true; +} + +bool MimeResponseSink::onBeginMimePart(const std::multimap& headers) { + ACSDK_DEBUG5(LX(__func__)); + + if (m_handler) { + m_handler->onActivity(); + } + + auto it = headers.find(MIME_CONTENT_TYPE_FIELD_NAME); + if (headers.end() == it) { + ACSDK_WARN(LX("noContent-Type")); + return true; + } + + auto contentType = it->second; + if (contentType.find(MIME_JSON_CONTENT_TYPE) != std::string::npos) { + m_contentType = ContentType::JSON; + ACSDK_DEBUG9(LX("JsonContentDetected")); + } else if ( + m_attachmentManager && contentType.find(MIME_OCTET_STREAM_CONTENT_TYPE) != std::string::npos && + 1 == headers.count(MIME_CONTENT_ID_FIELD_NAME)) { + auto iy = headers.find(MIME_CONTENT_ID_FIELD_NAME); + auto contentId = sanitizeContentId(iy->second); + auto attachmentId = m_attachmentManager->generateAttachmentId(m_attachmentContextId, contentId); + if (!m_attachmentWriter && attachmentId != m_attachmentIdBeingReceived) { + m_attachmentWriter = m_attachmentManager->createWriter(attachmentId); + if (!m_attachmentWriter) { + ACSDK_ERROR( + LX("onBeginMimePartFailed").d("reason", "createWriterFailed").d("attachmentId", attachmentId)); + return false; + } + ACSDK_DEBUG9(LX("attachmentContentDetected").d("contentId", contentId)); + } + m_contentType = ContentType::ATTACHMENT; + } else { + ACSDK_WARN(LX("unhandledContent-Type").d("Content-Type", contentType)); + m_contentType = ContentType::NONE; + } + return true; +} + +HTTP2ReceiveDataStatus MimeResponseSink::onReceiveMimeData(const char* bytes, size_t size) { + ACSDK_DEBUG5(LX(__func__).d("size", size)); + + if (m_handler) { + m_handler->onActivity(); + } + + switch (m_contentType) { + case ContentType::JSON: + m_directiveBeingReceived.append(bytes, size); + return HTTP2ReceiveDataStatus::SUCCESS; + case ContentType::ATTACHMENT: + return writeToAttachment(bytes, size); + case ContentType::NONE: + break; + } + return HTTP2ReceiveDataStatus::SUCCESS; +} + +bool MimeResponseSink::onEndMimePart() { + ACSDK_DEBUG5(LX(__func__)); + + if (m_handler) { + m_handler->onActivity(); + } + + switch (m_contentType) { + case ContentType::JSON: + if (!m_messageConsumer) { + ACSDK_ERROR(LX("onEndMimePartFailed").d("reason", "nullMessageConsumer")); + break; + } + // Check there's data to send out, because in a re-drive we may skip a directive that's been seen before. + if (!m_directiveBeingReceived.empty()) { + m_messageConsumer->consumeMessage(m_attachmentContextId, m_directiveBeingReceived); + m_directiveBeingReceived.clear(); + } + break; + case ContentType::ATTACHMENT: + m_attachmentIdBeingReceived.clear(); + m_attachmentWriter.reset(); + m_contentType = ContentType::NONE; + break; + default: + ACSDK_ERROR(LX("partEndCallbackFailed").d("reason", "unsupportedContentType")); + break; + } + return true; +} + +HTTP2ReceiveDataStatus MimeResponseSink::onReceiveNonMimeData(const char* bytes, size_t size) { + ACSDK_DEBUG5(LX(__func__).d("size", size)); + + if (m_handler) { + m_handler->onActivity(); + } + + auto total = m_nonMimeBody.size() + size; + if (total <= NON_MIME_BODY_MAX_SIZE) { + m_nonMimeBody.append(bytes, size); + } else { + // Only append up to the maximum allowed. + auto spaceLeft = NON_MIME_BODY_MAX_SIZE - m_nonMimeBody.size(); + m_nonMimeBody.append(bytes, spaceLeft); + ACSDK_ERROR(LX("nonMimeBodyTruncated").d("total", total).d("maxSize", NON_MIME_BODY_MAX_SIZE)); + } + + return HTTP2ReceiveDataStatus(size); +} + +void MimeResponseSink::onResponseFinished(HTTP2ResponseFinishedStatus status) { + ACSDK_DEBUG5(LX(__func__).d("status", status)); + + if (m_handler) { + m_handler->onResponseFinished(status, m_nonMimeBody); + } +} + +HTTP2ReceiveDataStatus MimeResponseSink::writeToAttachment(const char* bytes, size_t size) { + // Error case. We can't process the attachment. + if (!m_attachmentWriter) { + ACSDK_ERROR(LX("writeToAttachmentFailed").d("reason", "nullAttachmentWriter")); + return HTTP2ReceiveDataStatus::ABORT; + } + + auto writeStatus = AttachmentWriter::WriteStatus::OK; + auto numWritten = m_attachmentWriter->write(const_cast(bytes), size, &writeStatus); + + switch (writeStatus) { + case AttachmentWriter::WriteStatus::OK: + if (numWritten != size) { + ACSDK_ERROR(LX("writeDataToAttachmentFailed").d("reason", "writeTruncated")); + return HTTP2ReceiveDataStatus::ABORT; + } + return HTTP2ReceiveDataStatus::SUCCESS; + + case AttachmentWriter::WriteStatus::OK_BUFFER_FULL: + // We're blocked on a slow reader. + ACSDK_DEBUG9(LX("writeToAttachmentReturningPAUSE")); + return HTTP2ReceiveDataStatus::PAUSE; + + case AttachmentWriter::WriteStatus::CLOSED: + // The underlying memory was closed elsewhere. + ACSDK_WARN(LX("writeDataToAttachmentFailed").d("reason", "attachmentWriterIsClosed")); + return HTTP2ReceiveDataStatus::ABORT; + + case AttachmentWriter::WriteStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE: + case AttachmentWriter::WriteStatus::ERROR_INTERNAL: + // A low-level error with the Attachment occurred. + ACSDK_ERROR(LX("writeDataToAttachmentFailed").d("reason", "attachmentWriterInternalError")); + return HTTP2ReceiveDataStatus::ABORT; + + case AttachmentWriter::WriteStatus::TIMEDOUT: + // Unexpected status (this attachment writer should be non blocking). + ACSDK_ERROR(LX("writeDataToAttachmentFailed").d("reason", "unexpectedTimedoutStatus")); + return HTTP2ReceiveDataStatus::ABORT; + } + // Unreachable unless a garbage status was returned. + ACSDK_ERROR( + LX("writeDataToAttachmentFailed").d("reason", "unhandledStatus").d("status", static_cast(writeStatus))); + return HTTP2ReceiveDataStatus::ABORT; +} + +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/src/Transport/PingHandler.cpp b/ACL/src/Transport/PingHandler.cpp new file mode 100644 index 0000000000..ffbb5d30db --- /dev/null +++ b/ACL/src/Transport/PingHandler.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include + +#include "ACL/Transport/HTTP2Transport.h" +#include "ACL/Transport/PingHandler.h" + +namespace alexaClientSDK { +namespace acl { + +using namespace avsCommon::utils::http; +using namespace avsCommon::utils::http2; + +/// URL to send pings to +const static std::string AVS_PING_URL_PATH_EXTENSION = "/ping"; + +/// Max time to wait for a ping reply. +static const std::chrono::milliseconds PING_TRANSFER_TIMEOUT(30000); + +/// Priority for ping requests (high, default is 16). +static const uint8_t PING_PRIORITY = 200; + +/// Prefix for the ID of ping requests. +static const std::string PING_ID_PREFIX = "AVSPing-"; + +/// String to identify log entries originating from this file. +static const std::string TAG("PingHandler"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +std::shared_ptr PingHandler::create( + std::shared_ptr context, + const std::string& authToken) { + ACSDK_DEBUG5(LX(__func__).d("context", context.get())); + + if (!context) { + ACSDK_CRITICAL(LX("createFailed").d("reason", "nullContext")); + return nullptr; + } + + if (authToken.empty()) { + ACSDK_DEBUG9(LX("createFailed").d("reason", "emptyAuthToken")); + return nullptr; + } + + std::shared_ptr handler(new PingHandler(context, authToken)); + + HTTP2RequestConfig cfg{HTTP2RequestType::GET, context->getEndpoint() + AVS_PING_URL_PATH_EXTENSION, PING_ID_PREFIX}; + cfg.setRequestSource(handler); + cfg.setResponseSink(handler); + cfg.setTransferTimeout(PING_TRANSFER_TIMEOUT); + cfg.setPriority(PING_PRIORITY); + + auto request = context->createAndSendRequest(cfg); + + if (!request) { + ACSDK_ERROR(LX("createFailed").d("reason", "createAndSendRequestFailed")); + return nullptr; + } + + return handler; +} + +PingHandler::PingHandler(std::shared_ptr context, const std::string& authToken) : + ExchangeHandler{context, authToken}, + m_wasPingAcknowledgedReported{false}, + m_responseCode{0} { + ACSDK_DEBUG5(LX(__func__).d("context", context.get())); +} + +void PingHandler::reportPingAcknowledged() { + ACSDK_DEBUG5(LX(__func__)); + if (!m_wasPingAcknowledgedReported) { + m_wasPingAcknowledgedReported = true; + m_context->onPingRequestAcknowledged( + HTTPResponseCode::SUCCESS_NO_CONTENT == intToHTTPResponseCode(m_responseCode)); + } +} + +std::vector PingHandler::getRequestHeaderLines() { + ACSDK_DEBUG5(LX(__func__)); + return {m_authHeader}; +} + +HTTP2SendDataResult PingHandler::onSendData(char* bytes, size_t size) { + ACSDK_DEBUG5(LX(__func__).d("size", size)); + return HTTP2SendDataResult::COMPLETE; +} + +bool PingHandler::onReceiveResponseCode(long responseCode) { + ACSDK_DEBUG5(LX(__func__).d("responseCode", responseCode)); + + if (HTTPResponseCode::CLIENT_ERROR_FORBIDDEN == intToHTTPResponseCode(responseCode)) { + m_context->onForbidden(m_authToken); + } + + m_context->onActivity(); + m_responseCode = responseCode; + reportPingAcknowledged(); + return true; +} + +bool PingHandler::onReceiveHeaderLine(const std::string& line) { + ACSDK_DEBUG5(LX(__func__).d("line", line)); + m_context->onActivity(); + return true; +} + +HTTP2ReceiveDataStatus PingHandler::onReceiveData(const char* bytes, size_t size) { + ACSDK_DEBUG5(LX(__func__).d("size", size)); + m_context->onActivity(); + return HTTP2ReceiveDataStatus::SUCCESS; +} + +void PingHandler::onResponseFinished(HTTP2ResponseFinishedStatus status) { + ACSDK_DEBUG5(LX(__func__).d("status", status)); + switch (status) { + case HTTP2ResponseFinishedStatus::COMPLETE: + reportPingAcknowledged(); + return; + case HTTP2ResponseFinishedStatus::TIMEOUT: + case HTTP2ResponseFinishedStatus::INTERNAL_ERROR: + m_context->onPingTimeout(); + return; + case HTTP2ResponseFinishedStatus::CANCELLED: + ACSDK_WARN(LX("onResponseFinishedWithCancelledStatus")); + return; + } + + ACSDK_ERROR(LX("onResponseFinishedWithUnhandledStatus").d("status", static_cast(status))); +} + +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/src/Transport/PostConnectObject.cpp b/ACL/src/Transport/PostConnectObject.cpp deleted file mode 100644 index ad6bcd7eff..0000000000 --- a/ACL/src/Transport/PostConnectObject.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include "ACL/Transport/PostConnectObject.h" -#include "ACL/Transport/PostConnectSynchronizer.h" -#include - -namespace alexaClientSDK { -namespace acl { - -using namespace avsCommon::sdkInterfaces; - -/// Class static definition of context-manager. -std::shared_ptr PostConnectObject::m_contextManager = nullptr; - -/// String to identify log entries originating from this file. -static const std::string TAG("PostConnect"); - -/** - * Create a LogEntry using this file's TAG and the specified event string. - * - * @param event The event string for this @c LogEntry. - */ -#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) - -/* - * Static method to initialize the context manager. - * - * @param contextManager The contextManager instance to initialize with. - */ -void PostConnectObject::init(std::shared_ptr contextManager) { - m_contextManager = contextManager; -} - -/** - * Method that creates post-connect object. - */ -std::shared_ptr PostConnectObject::create() { - if (!m_contextManager) { - ACSDK_ERROR(LX("postCreateFailed").d("reason", "contextManagerNullData")); - return nullptr; - } - - return std::shared_ptr(new PostConnectSynchronizer()); -} - -PostConnectObject::PostConnectObject() : RequiresShutdown{"PostConnectObject"} { -} - -} // namespace acl -} // namespace alexaClientSDK diff --git a/ACL/src/Transport/PostConnectSynchronizer.cpp b/ACL/src/Transport/PostConnectSynchronizer.cpp index 5877dc7129..c89dadfa20 100644 --- a/ACL/src/Transport/PostConnectSynchronizer.cpp +++ b/ACL/src/Transport/PostConnectSynchronizer.cpp @@ -13,19 +13,23 @@ * permissions and limitations under the License. */ -#include "ACL/Transport/HTTP2Transport.h" -#include "ACL/Transport/TransportDefines.h" -#include "ACL/Transport/PostConnectSynchronizer.h" +#include +#include + #include #include +#include "ACL/Transport/HTTP2Transport.h" +#include "ACL/Transport/PostConnectSynchronizer.h" +#include "ACL/Transport/TransportDefines.h" + namespace alexaClientSDK { namespace acl { using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("PostConnectSynchronize"); +static const std::string TAG("PostConnectSynchronizer"); /** * Create a LogEntry using this file's TAG and the specified event string. @@ -40,39 +44,27 @@ static const std::string STATE_SYNCHRONIZER_NAMESPACE = "System"; /// String to identify the AVS name of the event we send. static const std::string STATE_SYNCHRONIZER_NAME = "SynchronizeState"; -PostConnectSynchronizer::PostConnectSynchronizer() : - m_contextFetchInProgress{false}, - m_isPostConnected{false}, - m_isStopping{false}, - m_postConnectThreadRunning{false} { -} +/** + * Method that creates post-connect object. + */ +std::shared_ptr PostConnectSynchronizer::create( + std::shared_ptr contextManager) { + if (!contextManager) { + ACSDK_ERROR(LX("createFailed").d("reason", "contextManagerNullData")); + return nullptr; + } -void PostConnectSynchronizer::doShutdown() { - ACSDK_DEBUG(LX("PostConnectSynchronizer::doShutdown().")); - std::thread localPostConnectThread; - { - std::lock_guard lock{m_mutex}; - if (m_isStopping) { - return; - } - m_isStopping = true; - m_wakeRetryTrigger.notify_one(); - m_transport.reset(); + return std::shared_ptr{new PostConnectSynchronizer(contextManager)}; +} - std::swap(m_postConnectThread, localPostConnectThread); - } - { - if (localPostConnectThread.joinable()) { - localPostConnectThread.join(); - } - } - { - std::lock_guard lock{m_observerMutex}; - m_observers.clear(); - } +PostConnectSynchronizer::~PostConnectSynchronizer() { + ACSDK_DEBUG0(LX("~PostConnectSynchronizer")); + stop(); } bool PostConnectSynchronizer::doPostConnect(std::shared_ptr transport) { + ACSDK_DEBUG0(LX("doPostConnect")); + if (!transport) { ACSDK_ERROR(LX("createFailed").d("reason", "nullTransport")); return false; @@ -80,188 +72,210 @@ bool PostConnectSynchronizer::doPostConnect(std::shared_ptr tran std::lock_guard lock{m_mutex}; - /* - * To handle cases where shutdown was invoked before the post-connect object is - * called. We do not want to spawn the post-connect thread again which ends up - * waiting forever. - */ - if (m_isStopping) { + if (m_state != State::IDLE) { + ACSDK_ERROR(LX("doPostConnectFailed").d("reason", "notIdle")); return false; } - // This function spawns a worker thread, so ensure this function is called - // only when the the worker thread is not running. - if (m_postConnectThreadRunning) { - ACSDK_ERROR(LX("postConnectFailed").d("reason", "postConnectThreadAlreadyRunning")); + if (!setStateLocked(State::RUNNING)) { + ACSDK_ERROR(LX("doPostConnectFailed").d("reason", "setStateRunningFailed")); return false; } m_transport = transport; - // Register the post-connect object as a listener to the HTTP m_transport - // observer interface to stop the thread when the m_transport is - // disconnected. - m_transport->addObserver(shared_from_this()); - - m_postConnectThreadRunning = true; - - m_postConnectThread = std::thread(&PostConnectSynchronizer::postConnectLoop, this); + m_mainLoopThread = std::thread(&PostConnectSynchronizer::mainLoop, this); return true; } -void PostConnectSynchronizer::postConnectLoop() { - int retryCount = 0; - - ACSDK_DEBUG9(LX("Entering postConnectLoop thread")); - - while (!isStopping() && !isPostConnected()) { - { - std::unique_lock lock(m_mutex); - if (!m_contextFetchInProgress) { - PostConnectObject::m_contextManager->getContext(shared_from_this()); - m_contextFetchInProgress = true; - } - } - - auto retryBackoff = TransportDefines::RETRY_TIMER.calculateTimeToRetry(retryCount); - retryCount++; - std::unique_lock lock(m_mutex); - m_wakeRetryTrigger.wait_for(lock, retryBackoff, [this] { return m_isPostConnected || m_isStopping; }); - } - - ACSDK_DEBUG9(LX("Exiting postConnectLoop thread")); - - { - std::lock_guard lock{m_mutex}; - m_postConnectThreadRunning = false; - } +void PostConnectSynchronizer::onDisconnect() { + ACSDK_DEBUG0(LX("onDisconnect")); + stop(); } void PostConnectSynchronizer::onContextAvailable(const std::string& jsonContext) { - if (isPostConnected() || isStopping()) { - ACSDK_DEBUG(LX("onContextAvailableIgnored") - .d("reason", "PostConnectSynchronizerDone") - .d("m_isPostConnected", m_isPostConnected) - .d("m_isStopping", m_isStopping)); + ACSDK_DEBUG5(LX("onContextAvailable").sensitive("context", jsonContext)); + + if (!setState(State::SENDING)) { + // This is expected if m_state is STOPPING. + ACSDK_DEBUG5(LX("onContextAvailableIgnored").d("reason", "setStateSendingFailed")); return; } auto msgIdAndJsonEvent = avsCommon::avs::buildJsonEventString( STATE_SYNCHRONIZER_NAMESPACE, STATE_SYNCHRONIZER_NAME, "", "{}", jsonContext); - auto postConnectMessage = std::make_shared(msgIdAndJsonEvent.second); postConnectMessage->addObserver(shared_from_this()); - - /* - * If the transport pointer held by the post-connect is still valid - not - * shutdown yet then we send the message through the transport. - */ - std::shared_ptr localTransport; - { - std::lock_guard lock{m_mutex}; - swap(m_transport, localTransport); - } - - if (localTransport) { - ACSDK_DEBUG(LX("onContextAvailable : Send PostConnectMessage to transport")); - localTransport->sendPostConnectMessage(postConnectMessage); + auto transport = getTransport(); + if (transport) { + transport->sendPostConnectMessage(postConnectMessage); } } void PostConnectSynchronizer::onContextFailure(const ContextRequestError error) { - if (isPostConnected() || isStopping()) { - ACSDK_DEBUG(LX("onContextFailureIgnored") - .d("reason", "PostConnectSynchronizerDone") - .d("m_isPostConnected", m_isPostConnected) - .d("m_isStopping", m_isStopping)); - return; - } + ACSDK_ERROR(LX("onContextFailure").d("error", error)); - { - std::lock_guard lock{m_mutex}; - m_contextFetchInProgress = false; + if (!setState(State::RUNNING)) { + // This is expected if m_state is STOPPING. + ACSDK_DEBUG5(LX("onContextFailureIgnored").d("reason", "setStateRunningFailed")); } - - ACSDK_ERROR(LX("contextRetrievalFailed").d("reason", "contextRequestErrorOccurred").d("error", error)); } void PostConnectSynchronizer::onSendCompleted(MessageRequestObserverInterface::Status status) { - ACSDK_DEBUG(LX("onSendCompleted").d("status", status)); + ACSDK_DEBUG5(LX("onSendCompleted").d("status", status)); + if (status == MessageRequestObserverInterface::Status::SUCCESS || status == MessageRequestObserverInterface::Status::SUCCESS_NO_CONTENT) { - { - std::lock_guard lock{m_mutex}; - m_isPostConnected = true; - m_wakeRetryTrigger.notify_one(); + // Capture transport before stop(), which resets m_transport. + auto transport = getTransport(); + if (stop()) { + if (transport) { + transport->onPostConnected(); + } + } else { + // This is expected if m_state is STOPPING. + ACSDK_DEBUG5(LX("onSendCompletedSuccessIgnored").d("reason", "stopFailed")); } - notifyObservers(); } else { - std::lock_guard lock{m_mutex}; - m_contextFetchInProgress = false; + if (!setState(State::RUNNING)) { + ACSDK_ERROR(LX("onSendCompletedFailureIgnored").d("reason", "setStateRunningFailed")); + } } } void PostConnectSynchronizer::onExceptionReceived(const std::string& exceptionMessage) { - ACSDK_ERROR(LX("onExceptionReceived").d("exception", exceptionMessage)); - std::lock_guard lock{m_mutex}; - m_contextFetchInProgress = false; + // Exceptions are ignored here because the @c onSendCompleted() notification will also + // be received with a failure status. If both are processed, the first will trigger + // a return to the RUNNING and then FETCHING states and the second will try and + // abort the FETCHING state + ACSDK_ERROR(LX("onExceptionReceivedIgnored").d("exception", exceptionMessage)); } -void PostConnectSynchronizer::addObserver(std::shared_ptr observer) { - if (!observer) { - ACSDK_ERROR(LX("addObserverFailed").d("reason", "nullObserver")); - return; +PostConnectSynchronizer::PostConnectSynchronizer( + std::shared_ptr contextManager) : + m_state{State::IDLE}, + m_contextManager{contextManager} { +} + +bool PostConnectSynchronizer::setState(State state) { + std::lock_guard lock(m_mutex); + return setStateLocked(state); +} + +bool PostConnectSynchronizer::setStateLocked(State state) { + static std::set> allowsStateTransitions = {{State::IDLE, State::RUNNING}, + {State::RUNNING, State::FETCHING}, + {State::RUNNING, State::STOPPING}, + {State::FETCHING, State::RUNNING}, + {State::FETCHING, State::SENDING}, + {State::FETCHING, State::STOPPING}, + {State::SENDING, State::RUNNING}, + {State::SENDING, State::STOPPING}, + {State::STOPPING, State::STOPPED}}; + + if (allowsStateTransitions.count({m_state, state}) == 0) { + ACSDK_ERROR(LX("stateTransitionNotAllowed").d("from", m_state).d("to", state)); + return false; } - std::lock_guard lock{m_observerMutex}; - m_observers.insert(observer); + ACSDK_DEBUG5(LX("setState").d("from", m_state).d("to", state)); + m_state = state; + if (State::RUNNING == m_state || State::STOPPING == m_state) { + m_wakeTrigger.notify_all(); + } + return true; } -void PostConnectSynchronizer::removeObserver(std::shared_ptr observer) { - if (!observer) { - ACSDK_ERROR(LX("removeObserverFailed").d("reason", "nullObserver")); - return; +void PostConnectSynchronizer::mainLoop() { + ACSDK_DEBUG5(LX("mainLoop")); + + int tryCount = -1; + auto timeout = std::chrono::milliseconds(0); + + std::unique_lock lock(m_mutex); + + while (State::RUNNING == m_state) { + if (tryCount++ > 0) { + timeout = TransportDefines::RETRY_TIMER.calculateTimeToRetry(tryCount - 1); + ACSDK_WARN(LX("waitingToRetryPostConnectOperation").d("timeoutMs", timeout.count())); + if (m_wakeTrigger.wait_for(lock, timeout, [this] { return State::STOPPING == m_state; })) { + break; + } + } + + setStateLocked(State::FETCHING); + m_contextManager->getContext(shared_from_this()); + + m_wakeTrigger.wait(lock, [this] { return State::RUNNING == m_state || State::STOPPING == m_state; }); } - std::lock_guard lock{m_observerMutex}; - m_observers.erase(observer); + ACSDK_DEBUG5(LX("mainLoopReturning")); } -void PostConnectSynchronizer::notifyObservers() { - std::unique_lock lock{m_observerMutex}; - auto observers = m_observers; +bool PostConnectSynchronizer::stop() { + ACSDK_DEBUG5(LX("stop")); + + std::unique_lock lock(m_mutex); + + if (State::STOPPED == m_state) { + ACSDK_DEBUG5(LX("stopIgnored").d("reason", "alreadyStopped")); + return true; + } + if (State::STOPPING == m_state) { + ACSDK_DEBUG5(LX("stopAlreadyInProgress").d("state", m_state)); + m_wakeTrigger.wait(lock, [this]() { return State::STOPPED == m_state; }); + return true; + } + if (!setStateLocked(State::STOPPING)) { + ACSDK_ERROR(LX("stopFailed").d("reason", "setStateStoppingFailed")); + return false; + } + lock.unlock(); - for (auto observer : observers) { - observer->onPostConnected(); + if (m_mainLoopThread.joinable()) { + m_mainLoopThread.join(); } -} -void PostConnectSynchronizer::onServerSideDisconnect(std::shared_ptr transport) { - ACSDK_DEBUG(LX("onServerSideDisconnect()")); - doShutdown(); -} + setTransport(nullptr); -void PostConnectSynchronizer::onConnected(std::shared_ptr transport) { - ACSDK_DEBUG(LX("onConnected()")); + if (!setState(State::STOPPED)) { + ACSDK_ERROR(LX("stopFailed").d("reason", "setStateStoppedFailed")); + return false; + } + + m_wakeTrigger.notify_all(); + + return true; } -void PostConnectSynchronizer::onDisconnected( - std::shared_ptr transport, - ConnectionStatusObserverInterface::ChangedReason reason) { - ACSDK_DEBUG(LX("onDisconnected()")); - doShutdown(); +std::shared_ptr PostConnectSynchronizer::getTransport() { + std::lock_guard lock(m_mutex); + return m_transport; } -bool PostConnectSynchronizer::isStopping() { - std::lock_guard lock{m_mutex}; - return m_isStopping; +void PostConnectSynchronizer::setTransport(std::shared_ptr transport) { + std::lock_guard lock(m_mutex); + m_transport = transport; } -bool PostConnectSynchronizer::isPostConnected() { - std::lock_guard lock{m_mutex}; - return m_isPostConnected; +std::ostream& operator<<(std::ostream& stream, const PostConnectSynchronizer::State state) { + switch (state) { + case PostConnectSynchronizer::State::IDLE: + return stream << "IDLE"; + case PostConnectSynchronizer::State::RUNNING: + return stream << "RUNNING"; + case PostConnectSynchronizer::State::FETCHING: + return stream << "FETCHING"; + case PostConnectSynchronizer::State::SENDING: + return stream << "SENDING"; + case PostConnectSynchronizer::State::STOPPING: + return stream << "STOPPING"; + case PostConnectSynchronizer::State::STOPPED: + return stream << "STOPPED"; + } + return stream << "Unknown State: " << static_cast(state); } } // namespace acl diff --git a/ACL/src/Transport/PostConnectSynchronizerFactory.cpp b/ACL/src/Transport/PostConnectSynchronizerFactory.cpp new file mode 100644 index 0000000000..16df4ec7f4 --- /dev/null +++ b/ACL/src/Transport/PostConnectSynchronizerFactory.cpp @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include "AVSCommon/Utils/Logger/Logger.h" + +#include "ACL/Transport/PostConnectSynchronizer.h" +#include "ACL/Transport/PostConnectSynchronizerFactory.h" + +namespace alexaClientSDK { +namespace acl { + +/// String to identify log entries originating from this file. +static const std::string TAG("PostConnectSynchronizerFactory"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +std::shared_ptr PostConnectSynchronizerFactory::create( + std::shared_ptr contextManager) { + if (!contextManager) { + ACSDK_ERROR(LX("createFactoryFailed").d("reason", "nullContextManager")); + return nullptr; + } + + return std::shared_ptr(new PostConnectSynchronizerFactory(contextManager)); +} + +std::shared_ptr PostConnectSynchronizerFactory::createPostConnect() { + return PostConnectSynchronizer::create(m_contextManager); +} + +PostConnectSynchronizerFactory::PostConnectSynchronizerFactory( + std::shared_ptr contextManager) : + m_contextManager{contextManager} { +} + +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/test/AVSConnectionManagerTest.cpp b/ACL/test/AVSConnectionManagerTest.cpp index 6fe3d61542..6a4ba6b0aa 100644 --- a/ACL/test/AVSConnectionManagerTest.cpp +++ b/ACL/test/AVSConnectionManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -15,9 +15,11 @@ /// @file AVSConnectionManagerTest.cpp -#include #include +#include + #include +#include #include "ACL/AVSConnectionManager.h" namespace alexaClientSDK { @@ -27,6 +29,7 @@ namespace test { using namespace ::testing; using namespace alexaClientSDK::avsCommon::avs::initialization; using namespace alexaClientSDK::avsCommon::sdkInterfaces; +using namespace avsCommon::utils::network::test; /// This class allows us to test MessageObserver interaction class MockMessageObserver : public MessageObserverInterface { @@ -60,7 +63,6 @@ class MockMessageRouter : public MessageRouterInterface { MOCK_METHOD0(disable, void()); MOCK_METHOD0(doShutdown, void()); MOCK_METHOD0(getConnectionStatus, MessageRouterInterface::ConnectionStatus()); - // TODO: ACSDK-421: Revert this to use send(). MOCK_METHOD1(sendMessage, void(std::shared_ptr request)); MOCK_METHOD1(setAVSEndpoint, void(const std::string& avsEndpoint)); MOCK_METHOD1(setObserver, void(std::shared_ptr observer)); @@ -76,18 +78,23 @@ class AVSConnectionManagerTest : public ::testing::Test { std::shared_ptr m_messageRouter; std::shared_ptr m_observer; std::shared_ptr m_messageObserver; + std::shared_ptr m_mockConnectionMonitor; }; void AVSConnectionManagerTest::SetUp() { - AlexaClientSDKInit::initialize(std::vector()); + AlexaClientSDKInit::initialize(std::vector>()); m_messageRouter = std::make_shared(); m_observer = std::make_shared(); m_messageObserver = std::make_shared(); + m_mockConnectionMonitor = std::make_shared(); m_avsConnectionManager = AVSConnectionManager::create( m_messageRouter, true, std::unordered_set>(), - std::unordered_set>()); + std::unordered_set>(), + m_mockConnectionMonitor); + + EXPECT_THAT(m_avsConnectionManager, NotNull()); } void AVSConnectionManagerTest::TearDown() { @@ -97,19 +104,27 @@ void AVSConnectionManagerTest::TearDown() { /** * Test @c create with valid messageRouter, ConnectionStatusObserver, MessageObservers */ -TEST_F(AVSConnectionManagerTest, createTest) { +TEST_F(AVSConnectionManagerTest, test_create) { EXPECT_CALL(*m_messageRouter, setObserver(_)).Times(1); EXPECT_CALL(*m_messageRouter, enable()).Times(1); ASSERT_NE(nullptr, m_avsConnectionManager->create(m_messageRouter, true, {m_observer}, {m_messageObserver})); } /** - * Test @c create with different combinations of messageRouter, ConnectionStatusObserver, MessageObservers + * Test @c create with different combinations of messageRouter, ConnectionStatusObserver, MessageObservers, + * InternetConnectionMonitor. */ -TEST_F(AVSConnectionManagerTest, createWithNullMessageRouterAndObservers) { +TEST_F(AVSConnectionManagerTest, test_createWithNullMessageRouterAndObservers) { ASSERT_EQ(nullptr, m_avsConnectionManager->create(nullptr, true, {m_observer}, {m_messageObserver})); ASSERT_EQ(nullptr, m_avsConnectionManager->create(m_messageRouter, true, {nullptr}, {m_messageObserver})); ASSERT_EQ(nullptr, m_avsConnectionManager->create(m_messageRouter, true, {m_observer}, {nullptr})); + ASSERT_NE( + nullptr, m_avsConnectionManager->create(m_messageRouter, true, {m_observer}, {m_messageObserver}, nullptr)); + ASSERT_NE( + nullptr, + m_avsConnectionManager->create( + m_messageRouter, true, {m_observer}, {m_messageObserver}, m_mockConnectionMonitor)); + std::shared_ptr validConnectionStatusObserver; validConnectionStatusObserver = std::make_shared(); ASSERT_EQ( @@ -148,7 +163,7 @@ TEST_F(AVSConnectionManagerTest, createWithNullMessageRouterAndObservers) { /** * Test addConnectionStatusObserver with a @c nullptr observer, expecting no errors. */ -TEST_F(AVSConnectionManagerTest, addConnectionStatusObserverNull) { +TEST_F(AVSConnectionManagerTest, test_addConnectionStatusObserverNull) { EXPECT_CALL(*m_messageRouter, getConnectionStatus()).Times(0); m_avsConnectionManager->addConnectionStatusObserver(nullptr); } @@ -156,7 +171,7 @@ TEST_F(AVSConnectionManagerTest, addConnectionStatusObserverNull) { /** * Test with addConnectionStatusObserver with MockConnectionStatusObserver. */ -TEST_F(AVSConnectionManagerTest, addConnectionStatusObserverValid) { +TEST_F(AVSConnectionManagerTest, test_addConnectionStatusObserverValid) { EXPECT_CALL(*m_observer, onConnectionStatusChanged(_, _)).Times(1); m_avsConnectionManager->addConnectionStatusObserver(m_observer); } @@ -164,28 +179,28 @@ TEST_F(AVSConnectionManagerTest, addConnectionStatusObserverValid) { /** * Test removeConnectionStatusObserver with a @c nullptr observer, expecting no errors. */ -TEST_F(AVSConnectionManagerTest, removeConnectionStatusObserverNull) { +TEST_F(AVSConnectionManagerTest, test_removeConnectionStatusObserverNull) { m_avsConnectionManager->removeConnectionStatusObserver(nullptr); } /** * Test addMessageObserver with a @c nullptr observer, expecting no errors. */ -TEST_F(AVSConnectionManagerTest, addMessageObserverNull) { +TEST_F(AVSConnectionManagerTest, test_addMessageObserverNull) { m_avsConnectionManager->addMessageObserver(nullptr); } /** * Test removeMessageObserver with a @c nullptr observer, expecting no errors. */ -TEST_F(AVSConnectionManagerTest, removeMessageObserverNull) { +TEST_F(AVSConnectionManagerTest, test_removeMessageObserverNull) { m_avsConnectionManager->removeMessageObserver(nullptr); } /** * Test enable and disable function of AVSConnectionManager */ -TEST_F(AVSConnectionManagerTest, enableAndDisableFunction) { +TEST_F(AVSConnectionManagerTest, test_enableAndDisableFunction) { EXPECT_CALL(*m_messageRouter, enable()).Times(1); m_avsConnectionManager->enable(); ASSERT_TRUE(m_avsConnectionManager->isEnabled()); @@ -197,24 +212,95 @@ TEST_F(AVSConnectionManagerTest, enableAndDisableFunction) { /** * Tests sendMessage with a @c nullptr request, expecting no errors. */ -TEST_F(AVSConnectionManagerTest, sendMessageRequestTest) { - // TODO: ACSDK-421: Revert this to use send(). +TEST_F(AVSConnectionManagerTest, test_sendMessageRequest) { EXPECT_CALL(*m_messageRouter, sendMessage(_)).Times(1); m_avsConnectionManager->sendMessage(nullptr); - // TODO: ACSDK-421: Revert this to use send(). EXPECT_CALL(*m_messageRouter, sendMessage(_)).Times(1); std::shared_ptr messageRequest; - messageRequest = std::make_shared("Test message", nullptr); + messageRequest = std::make_shared("Test message"); m_avsConnectionManager->sendMessage(messageRequest); } /** * Test setAVSEndpoint and expect a call to messageRouter's setAVSEndpoint. */ -TEST_F(AVSConnectionManagerTest, setAVSEndpointTest) { +TEST_F(AVSConnectionManagerTest, test_setAVSEndpoint) { EXPECT_CALL(*m_messageRouter, setAVSEndpoint(_)).Times(1); m_avsConnectionManager->setAVSEndpoint("AVSEndpoint"); } + +/** + * Test that onConnectionStatusChanged(false) results in a reconnect attempt when enabled. + */ +TEST_F(AVSConnectionManagerTest, test_enabledOnConnectStatusChangedToFalse) { + // Create a new MessageRouter so we don't get residual calls to m_messageRouter from SetUp(). + auto messageRouter = std::make_shared(); + + { + InSequence dummy; + EXPECT_CALL(*messageRouter, enable()); + EXPECT_CALL(*messageRouter, disable()); + EXPECT_CALL(*messageRouter, enable()); + } + + m_avsConnectionManager = AVSConnectionManager::create( + messageRouter, + true, + std::unordered_set>(), + std::unordered_set>()); + m_avsConnectionManager->onConnectionStatusChanged(false); + // Explicitly reset so we control when destructor is called and can set expectations accordingly. + m_avsConnectionManager.reset(); +} + +/** + * Test that onConnectionStatusChanged(true) results in a no-op when enabled. + */ +TEST_F(AVSConnectionManagerTest, test_enabledOnConnectStatusChangedToTrue) { + // Create a new MessageRouter so we don't get residual calls to m_messageRouter from SetUp(). + auto messageRouter = std::make_shared(); + + { + InSequence dummy; + EXPECT_CALL(*messageRouter, enable()).Times(1); + EXPECT_CALL(*messageRouter, disable()).Times(0); + EXPECT_CALL(*messageRouter, enable()).Times(0); + } + + m_avsConnectionManager = AVSConnectionManager::create( + messageRouter, + true, + std::unordered_set>(), + std::unordered_set>()); + m_avsConnectionManager->onConnectionStatusChanged(true); + // Explicitly reset so we control when destructor is called and can set expectations accordingly. + m_avsConnectionManager.reset(); +} + +/** + * Test that onConnectionStatusChanged() results in no reconnect attempts when disabled. + */ +TEST_F(AVSConnectionManagerTest, test_disabledOnConnectStatusChanged) { + // Create a new MessageRouter so we don't get residual calls to m_messageRouter from SetUp(). + auto messageRouter = std::make_shared(); + + { + InSequence dummy; + EXPECT_CALL(*messageRouter, enable()).Times(0); + EXPECT_CALL(*messageRouter, disable()).Times(0); + } + + m_avsConnectionManager = AVSConnectionManager::create( + messageRouter, + false, + std::unordered_set>(), + std::unordered_set>()); + m_avsConnectionManager->onConnectionStatusChanged(true); + m_avsConnectionManager->onConnectionStatusChanged(false); + // Explicitly reset so we control when destructor is called and can set expectations accordingly. + m_avsConnectionManager.reset(); +} + } // namespace test } // namespace acl } // namespace alexaClientSDK diff --git a/ACL/test/CMakeLists.txt b/ACL/test/CMakeLists.txt index 9bd7f94c96..29bc15b774 100644 --- a/ACL/test/CMakeLists.txt +++ b/ACL/test/CMakeLists.txt @@ -1,5 +1,9 @@ add_subdirectory("Transport") -set(LIBRARIES ACL ACLTransportCommonTestLib ${CMAKE_THREAD_LIBS_INIT}) -set(INCLUDE_PATH ${AVSCommon_INCLUDE_DIRS} "${ACL_SOURCE_DIR}/include") +set(LIBRARIES ACL ${CMAKE_THREAD_LIBS_INIT} ACLTransportCommonTestLib) +set(INCLUDE_PATH + ${AVSCommon_INCLUDE_DIRS} + "${ACL_SOURCE_DIR}/include" + "${AVSCommon_SOURCE_DIR}/AVS/test" + "${AVSCommon_SOURCE_DIR}/Utils/test") discover_unit_tests( "${INCLUDE_PATH}" "${LIBRARIES}") diff --git a/ACL/test/Transport/Common/CMakeLists.txt b/ACL/test/Transport/Common/CMakeLists.txt index 52efda12f4..5290a2b486 100644 --- a/ACL/test/Transport/Common/CMakeLists.txt +++ b/ACL/test/Transport/Common/CMakeLists.txt @@ -1,13 +1,11 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_library(ACLTransportCommonTestLib - Common.cpp - MimeUtils.cpp - TestableAttachmentManager.cpp - TestableAttachmentWriter.cpp - TestableMessageObserver.cpp) + MockHTTP2Connection.cpp + MockHTTP2Request.cpp + MockMimeResponseSink.cpp) target_include_directories(ACLTransportCommonTestLib PUBLIC - "${ACL_SOURCE_DIR}/include") + "${ACL_SOURCE_DIR}/include" "${ACL_SOURCE_DIR}/test/Transport") target_link_libraries(ACLTransportCommonTestLib AVSCommon gtest_main diff --git a/ACL/test/Transport/Common/MockHTTP2Connection.cpp b/ACL/test/Transport/Common/MockHTTP2Connection.cpp new file mode 100644 index 0000000000..4d134242e0 --- /dev/null +++ b/ACL/test/Transport/Common/MockHTTP2Connection.cpp @@ -0,0 +1,258 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "MockHTTP2Connection.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { +namespace test { + +using namespace avsCommon::utils::http; + +MockHTTP2Connection::MockHTTP2Connection(std::string dURL, std::string pingURL) : + m_downchannelURL{dURL}, + m_pingURL{pingURL}, + m_postResponseCode{HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED}, + m_maxPostRequestsEnqueued{0} { +} + +std::shared_ptr MockHTTP2Connection::createAndSendRequest(const HTTP2RequestConfig& config) { + std::lock_guard lock(m_requestMutex); + // Create HTTP2 request from config. + auto request = std::make_shared(config); + + // Add request to queue. + m_requestQueue.push_back(request); + + // Notify any listeners that are waiting for a header match. + if (m_headerMatch.length() > 0) { + for (auto header : request->getSource()->getRequestHeaderLines()) { + if (header.find(m_headerMatch) != std::string::npos) { + m_requestHeaderCv.notify_one(); + } + } + } + + if (request->getRequestType() == HTTP2RequestType::POST) { + // Parse POST HTTP2 Requests. + std::lock_guard lock(m_postRequestMutex); + m_postRequestQueue.push_back(request); + if (m_postResponseCode != HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED) { + request->getSink()->onReceiveResponseCode(responseCodeToInt(m_postResponseCode)); + } + if (m_postRequestQueue.size() > m_maxPostRequestsEnqueued) { + m_maxPostRequestsEnqueued = m_postRequestQueue.size(); + } + m_requestPostCv.notify_one(); + } else if (m_downchannelURL == request->getUrl()) { + // Push downchannel requests to its queue. + std::lock_guard lock(m_downchannelRequestMutex); + m_downchannelRequestQueue.push_back(request); + m_downchannelRequestCv.notify_all(); + } else if (m_pingURL == request->getUrl()) { + // Push ping requests to its queue. + std::lock_guard lock(m_pingRequestMutex); + m_pingRequestQueue.push_back(request); + m_pingRequestCv.notify_one(); + } + + m_requestCv.notify_one(); + return request; +} + +bool MockHTTP2Connection::isRequestQueueEmpty() { + std::lock_guard lock(m_requestMutex); + return m_requestQueue.empty(); +} + +std::shared_ptr MockHTTP2Connection::waitForRequest( + std::chrono::milliseconds timeout, + unsigned requestNum) { + std::unique_lock lock(m_requestMutex); + if (!m_requestCv.wait_for(lock, timeout, [this, requestNum] { + return !m_requestQueue.empty() && m_requestQueue.size() >= requestNum; + })) { + return nullptr; + } + return m_requestQueue.front(); +} + +std::shared_ptr MockHTTP2Connection::dequeRequest() { + std::lock_guard lock(m_requestMutex); + auto req = m_requestQueue.front(); + m_requestQueue.pop_front(); + return req; +} + +void MockHTTP2Connection::setWaitRequestHeader(const std::string& matchString) { + std::lock_guard lock(m_requestMutex); + m_headerMatch = matchString; +} + +bool MockHTTP2Connection::waitForRequestWithHeader(std::chrono::milliseconds timeout) { + if (waitForRequest(timeout)) { + std::unique_lock lock(m_requestMutex); + return m_requestHeaderCv.wait_for(lock, timeout, [this] { return !m_requestQueue.empty(); }); + } + + return false; +} + +std::shared_ptr MockHTTP2Connection::waitForPostRequest(const std::chrono::milliseconds timeout) { + std::unique_lock lock(m_postRequestMutex); + + if (!m_requestPostCv.wait_for(lock, timeout, [this] { return !m_postRequestQueue.empty(); })) { + return nullptr; + } + + auto request = m_postRequestQueue.back(); + + // Need to send 200 to MIME decoder in order for it parse the message. + request->getMimeDecoder()->onReceiveResponseCode( + static_cast::type>(HTTPResponseCode::SUCCESS_OK)); + + // Feed the header lines to the MIME decoder. + for (auto headerLine : request->getSource()->getRequestHeaderLines()) { + request->getMimeDecoder()->onReceiveHeaderLine(headerLine); + } + + // Feed the data to the MIME decoder. + char buf[READ_DATA_BUF_SIZE] = {'\0'}; + bool stop = false; + do { + auto res = request->getSource()->onSendData(buf, READ_DATA_BUF_SIZE); + switch (res.status) { + case HTTP2SendStatus::COMPLETE: + case HTTP2SendStatus::ABORT: + stop = true; + break; + case HTTP2SendStatus::PAUSE: + if (!isPauseOnSendReceived()) { + m_receivedPauseOnSend.setValue(); + } + // fall-through + case HTTP2SendStatus::CONTINUE: + break; + } + if (stop) { + break; + } + + request->getMimeDecoder()->onReceiveData(buf, res.size); + + } while (true); + + return request; +} + +std::shared_ptr MockHTTP2Connection::waitForPingRequest(const std::chrono::milliseconds timeout) { + std::unique_lock lock(m_pingRequestMutex); + if (!m_pingRequestCv.wait_for(lock, timeout, [this] { return !m_pingRequestQueue.empty(); })) { + return nullptr; + } + + return m_pingRequestQueue.back(); +} + +bool MockHTTP2Connection::respondToDownchannelRequests( + long responseCode, + bool sendResponseFinished, + const std::chrono::milliseconds timeout) { + std::unique_lock lock(m_downchannelRequestMutex); + auto ret = m_downchannelRequestCv.wait_for(lock, timeout, [this] { return !m_downchannelRequestQueue.empty(); }); + + for (auto request : m_downchannelRequestQueue) { + request->getSink()->onReceiveResponseCode(responseCode); + if (sendResponseFinished) { + request->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::COMPLETE); + } + } + return ret; +} + +void MockHTTP2Connection::setResponseToPOSTRequests(HTTPResponseCode responseCode) { + std::lock_guard lock(m_postRequestMutex); + m_postResponseCode = responseCode; +} + +std::shared_ptr MockHTTP2Connection::getDownchannelRequest(std::chrono::milliseconds timeout) { + std::unique_lock lock(m_downchannelRequestMutex); + m_downchannelRequestCv.wait_for(lock, timeout); + if (m_downchannelRequestQueue.empty()) { + return nullptr; + } + return m_downchannelRequestQueue.back(); +} + +bool MockHTTP2Connection::isPauseOnSendReceived(std::chrono::milliseconds timeout) { + return m_receivedPauseOnSend.waitFor(timeout); +} + +std::size_t MockHTTP2Connection::getPostRequestsNum() { + return m_postRequestQueue.size(); +} + +std::size_t MockHTTP2Connection::getRequestsNum() { + return m_requestQueue.size(); +} + +std::size_t MockHTTP2Connection::getDownchannelRequestsNum() { + return m_downchannelRequestQueue.size(); +} + +std::shared_ptr MockHTTP2Connection::dequePostRequest() { + std::lock_guard lock(m_postRequestMutex); + if (m_postRequestQueue.empty()) { + return nullptr; + } + auto req = m_postRequestQueue.front(); + m_postRequestQueue.pop_front(); + return req; +} + +std::shared_ptr MockHTTP2Connection::dequePostRequest(const std::chrono::milliseconds timeout) { + auto req = dequePostRequest(); + + if (!req) { + if (waitForPostRequest(timeout) == nullptr) { + return nullptr; + } + req = dequePostRequest(); + } + + return req; +} + +std::shared_ptr MockHTTP2Connection::dequePingRequest() { + std::lock_guard lock(m_pingRequestMutex); + if (m_pingRequestQueue.empty()) { + return nullptr; + } + auto req = m_pingRequestQueue.front(); + m_pingRequestQueue.pop_front(); + return req; +} + +std::size_t MockHTTP2Connection::getMaxPostRequestsEnqueud() { + return m_maxPostRequestsEnqueued; +} + +} // namespace test +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/ACL/test/Transport/Common/MockHTTP2Request.cpp b/ACL/test/Transport/Common/MockHTTP2Request.cpp new file mode 100644 index 0000000000..97a5754885 --- /dev/null +++ b/ACL/test/Transport/Common/MockHTTP2Request.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "MockHTTP2Request.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { +namespace test { + +MockHTTP2Request::MockHTTP2Request(const alexaClientSDK::avsCommon::utils::http2::HTTP2RequestConfig& config) : + m_url{config.getUrl()}, + m_source{config.getSource()}, + m_sink{config.getSink()}, + m_type{config.getRequestType()} { + m_mimeResponseSink = std::make_shared(); + m_mimeDecoder = std::make_shared(m_mimeResponseSink); +} + +const std::string MockHTTP2Request::getUrl() { + return m_url; +} + +std::shared_ptr MockHTTP2Request::getSource() { + return m_source; +} + +std::shared_ptr MockHTTP2Request::getSink() { + return m_sink; +} + +HTTP2RequestType MockHTTP2Request::getRequestType() { + return m_type; +} + +std::shared_ptr MockHTTP2Request::getMimeResponseSink() { + return m_mimeResponseSink; +} + +std::shared_ptr MockHTTP2Request::getMimeDecoder() { + return m_mimeDecoder; +} + +} // namespace test +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/ACL/test/Transport/Common/MockMimeResponseSink.cpp b/ACL/test/Transport/Common/MockMimeResponseSink.cpp new file mode 100644 index 0000000000..ecbed564ad --- /dev/null +++ b/ACL/test/Transport/Common/MockMimeResponseSink.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "MockMimeResponseSink.h" + +namespace alexaClientSDK { +namespace acl { +namespace test { + +MockMimeResponseSink::MockMimeResponseSink(){ + +}; + +bool MockMimeResponseSink::onReceiveResponseCode(long responseCode) { + return true; +} + +bool MockMimeResponseSink::onReceiveHeaderLine(const std::string& line) { + return true; +} + +bool MockMimeResponseSink::onBeginMimePart(const std::multimap& headers) { + m_mimeCurrentContent.clear(); + return true; +} + +avsCommon::utils::http2::HTTP2ReceiveDataStatus MockMimeResponseSink::onReceiveMimeData( + const char* bytes, + size_t size) { + for (unsigned i = 0; i < size; i++) { + m_mimeCurrentContent.push_back(bytes[i]); + } + return avsCommon::utils::http2::HTTP2ReceiveDataStatus::SUCCESS; +} + +bool MockMimeResponseSink::onEndMimePart() { + m_mimeContents.push_back(m_mimeCurrentContent); + return true; +} + +avsCommon::utils::http2::HTTP2ReceiveDataStatus MockMimeResponseSink::onReceiveNonMimeData( + const char* bytes, + size_t size) { + return avsCommon::utils::http2::HTTP2ReceiveDataStatus::SUCCESS; +} + +void MockMimeResponseSink::onResponseFinished(avsCommon::utils::http2::HTTP2ResponseFinishedStatus status) { +} + +std::vector MockMimeResponseSink::getMimePart(unsigned part) { + return m_mimeContents[part]; +} + +unsigned MockMimeResponseSink::getCountOfMimeParts() { + return m_mimeContents.size(); +} + +} // namespace test +} // namespace acl +} // namespace alexaClientSDK \ No newline at end of file diff --git a/ACL/test/Transport/HTTP2StreamPoolTest.cpp b/ACL/test/Transport/HTTP2StreamPoolTest.cpp deleted file mode 100644 index 74f52d4e3f..0000000000 --- a/ACL/test/Transport/HTTP2StreamPoolTest.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/// @file HTTP2StreamPoolTest.cpp - -#include -#include -#include - -#include -#include -#include -#include -#include -#include "Common/Common.h" -#include "TestableConsumer.h" -#include "MockMessageRequest.h" - -namespace alexaClientSDK { -namespace acl { -namespace test { - -using namespace alexaClientSDK::avsCommon::avs::initialization; -/// A test URL to initialize our object with -static const std::string TEST_LIBCURL_URL = "https://www.amazon.com/"; -/// The maximum number of streams in the stream pool -static const int TEST_MAX_STREAMS = 10; -/// A test auth string with which to initialize our test stream object. -static const std::string LIBCURL_TEST_AUTH_STRING = "test_auth_string"; - -/** - * Our GTest class. - */ -class HTTP2StreamPoolTest : public ::testing::Test { -public: - void SetUp() override; - - void TearDown() override; - - /// A mock message request object. - std::shared_ptr m_mockMessageRequest; - /// An object that is required for constructing a stream. - std::shared_ptr m_testableConsumer; - /// The actual stream we will be testing. - std::shared_ptr m_testableStreamPool; -}; - -void HTTP2StreamPoolTest::SetUp() { - AlexaClientSDKInit::initialize(std::vector()); - m_mockMessageRequest = std::make_shared(); - m_testableConsumer = std::make_shared(); - m_testableStreamPool = std::make_shared(TEST_MAX_STREAMS, nullptr); -} - -void HTTP2StreamPoolTest::TearDown() { - AlexaClientSDKInit::uninitialize(); -} - -/** - * Lets simulate sending more than max streams to GET requests - */ -TEST_F(HTTP2StreamPoolTest, GetStreamSendNullPtrForMoreThanMaxStreams) { - std::shared_ptr stream; - - // Send TEST_MAX_STREAMS number of streams, stream should not be nullptr - for (int numberOfStreamSent = 0; numberOfStreamSent < TEST_MAX_STREAMS; numberOfStreamSent++) { - stream = m_testableStreamPool->createGetStream(TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_testableConsumer); - ASSERT_NE(stream, nullptr); - } - // Send another stream in addition to TEST_MAX_STREAMS, stream should be a nullptr - stream = m_testableStreamPool->createGetStream(TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_testableConsumer); - ASSERT_EQ(stream, nullptr); -} - -/** - * This function tests failure of @c createGetStream on different types of failure on initGet() - */ -TEST_F(HTTP2StreamPoolTest, initGetFails) { - std::shared_ptr stream; - stream = m_testableStreamPool->createGetStream("", LIBCURL_TEST_AUTH_STRING, m_testableConsumer); - ASSERT_EQ(stream, nullptr); - stream = m_testableStreamPool->createGetStream(TEST_LIBCURL_URL, "", m_testableConsumer); - ASSERT_EQ(stream, nullptr); - stream = m_testableStreamPool->createGetStream(TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, nullptr); - ASSERT_EQ(stream, nullptr); -} - -/** - * This function tests failure of @c createPostStream on different types of failure on initPost() - */ -TEST_F(HTTP2StreamPoolTest, initPostFails) { - std::shared_ptr stream; - stream = - m_testableStreamPool->createPostStream("", LIBCURL_TEST_AUTH_STRING, m_mockMessageRequest, m_testableConsumer); - ASSERT_EQ(stream, nullptr); - stream = m_testableStreamPool->createPostStream(TEST_LIBCURL_URL, "", m_mockMessageRequest, m_testableConsumer); - ASSERT_EQ(stream, nullptr); - stream = - m_testableStreamPool->createPostStream(TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, nullptr, m_testableConsumer); - ASSERT_EQ(stream, nullptr); - stream = m_testableStreamPool->createPostStream( - TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_mockMessageRequest, nullptr); - ASSERT_EQ(stream, nullptr); -} - -/** - * Lets simulate sending more than max streams to POST requests - */ -TEST_F(HTTP2StreamPoolTest, PostStreamSendNullPtrForMoreThanMaxStreams) { - std::shared_ptr stream; - - // Send TEST_MAX_STREAMS number of streams, stream should not be nullptr - for (int numberOfStreamSent = 0; numberOfStreamSent < TEST_MAX_STREAMS; numberOfStreamSent++) { - stream = m_testableStreamPool->createPostStream( - TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_mockMessageRequest, m_testableConsumer); - ASSERT_NE(stream, nullptr); - } - // Send another stream in addition to TEST_MAX_STREAMS, stream should be a nullptr - stream = m_testableStreamPool->createPostStream( - TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_mockMessageRequest, m_testableConsumer); - ASSERT_EQ(stream, nullptr); -} - -/** - * Simulate sending more than max streams to GET requests; when it fails to add more, - * release a few streams using @c releaseStream, then add more streams which should pass. - */ -TEST_F(HTTP2StreamPoolTest, ReleaseStreamTestSendMoreThanMaxStreams) { - std::vector> stream_pool; - const int numOfRemovedStreams = 2; - - // Send max number of streams - for (int count = 0; count < TEST_MAX_STREAMS; count++) { - stream_pool.push_back( - m_testableStreamPool->createGetStream(TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_testableConsumer)); - ASSERT_NE(stream_pool.back(), nullptr); - } - // Send one more stream, it should fail - stream_pool.push_back( - m_testableStreamPool->createGetStream(TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_testableConsumer)); - ASSERT_EQ(stream_pool.back(), nullptr); - stream_pool.pop_back(); - - // Release few streams - for (int count = 0; count < numOfRemovedStreams; count++) { - m_testableStreamPool->releaseStream(stream_pool.back()); - stream_pool.pop_back(); - } - - // Send more streams, now it should pass - for (int count = 0; count < numOfRemovedStreams; count++) { - stream_pool.push_back( - m_testableStreamPool->createGetStream(TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_testableConsumer)); - ASSERT_NE(stream_pool.back(), nullptr); - } -} - -/** - * Try sending nullptr on @c releaseStream after sending max number of streams, - * check for failure of sending more streams. - */ -TEST_F(HTTP2StreamPoolTest, ReleaseStreamTestAfterNullTest) { - std::vector> stream_pool; - const int numOfRemovedStreams = 2; - - // Send max number of streams - for (int count = 0; count < TEST_MAX_STREAMS; count++) { - stream_pool.push_back( - m_testableStreamPool->createGetStream(TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_testableConsumer)); - ASSERT_NE(stream_pool.back(), nullptr); - } - - // Pass nullptr on releaseStream - for (int count = 0; count < numOfRemovedStreams; count++) { - m_testableStreamPool->releaseStream(nullptr); - } - - // Send more streams, it should still fail - for (int count = 0; count < numOfRemovedStreams; count++) { - stream_pool.push_back( - m_testableStreamPool->createGetStream(TEST_LIBCURL_URL, LIBCURL_TEST_AUTH_STRING, m_testableConsumer)); - ASSERT_EQ(stream_pool.back(), nullptr); - } -} -} // namespace test -} // namespace acl -} // namespace alexaClientSDK - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/ACL/test/Transport/HTTP2StreamTest.cpp b/ACL/test/Transport/HTTP2StreamTest.cpp deleted file mode 100644 index b081c60b95..0000000000 --- a/ACL/test/Transport/HTTP2StreamTest.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/// @file HTTP2StreamTest.cpp - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include "TestableConsumer.h" -#include "MockMessageRequest.h" -#include "Common/Common.h" - -namespace alexaClientSDK { -namespace acl { -namespace test { - -using namespace ::testing; -using namespace avsCommon::avs; -using namespace avsCommon::avs::attachment; -using namespace avsCommon::utils::sds; -using namespace alexaClientSDK::avsCommon::avs::initialization; - -/// A test url with which to initialize our test stream object. -static const std::string LIBCURL_TEST_URL = "http://example.com"; -/// A test auth string with which to initialize our test stream object. -static const std::string LIBCURL_TEST_AUTH_STRING = "test_auth_string"; -/// The length of the string we will test for the exception message. -static const int TEST_EXCEPTION_STRING_LENGTH = 200; -/// The number of iterations the multi-write test will perform. -static const int TEST_EXCEPTION_PARTITIONS = 7; -/// Number of bytes per word in the SDS circular buffer. -static const size_t SDS_WORDSIZE = 1; -/// Maximum number of readers to support in the SDS circular buffer. -static const size_t SDS_MAXREADERS = 1; -/// Number of words to hold in the SDS circular buffer. -static const size_t SDS_WORDS = 300; -/// Number of strings to read/write for the test -static const size_t NUMBER_OF_STRINGS = 1; -/** - * Our GTest class. - */ -class HTTP2StreamTest : public ::testing::Test { -public: - void SetUp() override; - - void TearDown() override; - - /// A message request to initiate @c m_readTestableStream with - std::shared_ptr m_MessageRequest; - /// A mock message request object. - std::shared_ptr m_mockMessageRequest; - /// An object that is required for constructing a stream. - std::shared_ptr m_testableConsumer; - /// The actual stream we will be testing. - std::shared_ptr m_testableStream; - /// The stream to test @c readCallback function - std::shared_ptr m_readTestableStream; - /// The attachment manager for the stream - std::shared_ptr m_attachmentManager; - /// A Writer to write data to SDS buffer. - std::unique_ptr m_writer; - /// The attachment reader for message request of @c m_readTestableStream - std::unique_ptr m_attachmentReader; - /// A char pointer to data buffer to read or write from callbacks - char* m_dataBegin; - /// A string to which @c m_dataBegin is pointing to - std::string m_testString; -}; - -void HTTP2StreamTest::SetUp() { - AlexaClientSDKInit::initialize(std::vector()); - m_testableConsumer = std::make_shared(); - - m_testString = createRandomAlphabetString(TEST_EXCEPTION_STRING_LENGTH); - m_dataBegin = const_cast(m_testString.c_str()); - - /// Create a SDS buffer and using a @c Writer, write a string into the buffer - size_t bufferSize = InProcessSDS::calculateBufferSize(SDS_WORDS, SDS_WORDSIZE, SDS_MAXREADERS); - auto buffer = std::make_shared(bufferSize); - std::shared_ptr stream = InProcessSDS::create(buffer, SDS_WORDSIZE, SDS_MAXREADERS); - ASSERT_NE(stream, nullptr); - - m_writer = stream->createWriter(InProcessSDS::Writer::Policy::NONBLOCKABLE); - ASSERT_NE(m_writer, nullptr); - ASSERT_EQ(TEST_EXCEPTION_STRING_LENGTH, m_writer->write(m_dataBegin, TEST_EXCEPTION_STRING_LENGTH)); - - /// Create an attachment Reader for @c m_MessageRequest - m_attachmentReader = InProcessAttachmentReader::create(InProcessSDS::Reader::Policy::NONBLOCKING, stream); - m_MessageRequest = std::make_shared("", std::move(m_attachmentReader)); - ASSERT_NE(m_MessageRequest, nullptr); - - m_mockMessageRequest = std::make_shared(); - ASSERT_NE(m_mockMessageRequest, nullptr); - m_attachmentManager = std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS); - - m_testableStream = std::make_shared(m_testableConsumer, m_attachmentManager); - ASSERT_NE(m_testableStream, nullptr); - ASSERT_TRUE(m_testableStream->initPost(LIBCURL_TEST_URL, LIBCURL_TEST_AUTH_STRING, m_mockMessageRequest)); - - m_readTestableStream = std::make_shared(m_testableConsumer, m_attachmentManager); - ASSERT_NE(m_readTestableStream, nullptr); - ASSERT_TRUE(m_readTestableStream->initPost(LIBCURL_TEST_URL, LIBCURL_TEST_AUTH_STRING, m_MessageRequest)); -} - -void HTTP2StreamTest::TearDown() { - AlexaClientSDKInit::uninitialize(); -} -/** - * Let's simulate that AVSConnectionManager->send() has been invoked, and the messageRequest object is - * waiting to be notified on the response from AVS. We will invoke the stream writeCallbacks directly to - * simulate exception data returning from AVS, and verify that the stream passes the correct data back to - * the request object. - */ -TEST_F(HTTP2StreamTest, testExceptionReceivedSingleWrite) { - HTTP2Stream::writeCallback(m_dataBegin, TEST_EXCEPTION_STRING_LENGTH, NUMBER_OF_STRINGS, m_testableStream.get()); - - EXPECT_CALL(*m_mockMessageRequest, exceptionReceived(_)).Times(1); - EXPECT_CALL(*m_mockMessageRequest, sendCompleted(_)).Times(1); - // This simulates stream cleanup, which flushes out the parsed exception message. - m_testableStream->notifyRequestObserver(); -} - -/** - * The same test as above, but now with multiple writes (simulating either a small buffer from libcurl, or a very - * long exception message). - */ -TEST_F(HTTP2StreamTest, testExceptionReceivedMultiWrite) { - int writeQuantum = TEST_EXCEPTION_STRING_LENGTH; - if (TEST_EXCEPTION_PARTITIONS > 0) { - writeQuantum = TEST_EXCEPTION_STRING_LENGTH / TEST_EXCEPTION_PARTITIONS; - } - int numberBytesWritten = 0; - - char* currBuffPosition = m_dataBegin; - while (numberBytesWritten < TEST_EXCEPTION_STRING_LENGTH) { - int bytesRemaining = TEST_EXCEPTION_STRING_LENGTH - numberBytesWritten; - int bytesToWrite = bytesRemaining < writeQuantum ? bytesRemaining : writeQuantum; - - HTTP2Stream::writeCallback(currBuffPosition, bytesToWrite, NUMBER_OF_STRINGS, m_testableStream.get()); - currBuffPosition += bytesToWrite; - numberBytesWritten += bytesToWrite; - } - - EXPECT_CALL(*m_mockMessageRequest, exceptionReceived(_)).Times(1); - EXPECT_CALL(*m_mockMessageRequest, sendCompleted(_)).Times(1); - - // This simulates stream cleanup, which flushes out the parsed exception message. - m_testableStream->notifyRequestObserver(); -} - -TEST_F(HTTP2StreamTest, testHeaderCallback) { - // Check if the length returned is as expected - int headerLength = TEST_EXCEPTION_STRING_LENGTH * NUMBER_OF_STRINGS; - int returnHeaderLength = HTTP2Stream::headerCallback( - m_dataBegin, TEST_EXCEPTION_STRING_LENGTH, NUMBER_OF_STRINGS, m_testableStream.get()); - ASSERT_EQ(headerLength, returnHeaderLength); - // Call the function with NULL HTTP2Stream and check if it fails - returnHeaderLength = - HTTP2Stream::headerCallback(m_dataBegin, TEST_EXCEPTION_STRING_LENGTH, NUMBER_OF_STRINGS, nullptr); - ASSERT_EQ(0, returnHeaderLength); -} - -TEST_F(HTTP2StreamTest, testReadCallBack) { - // Check if the bytesRead are equal to length of data written in SDS buffer - int bytesRead = HTTP2Stream::readCallback( - m_dataBegin, TEST_EXCEPTION_STRING_LENGTH, NUMBER_OF_STRINGS, m_readTestableStream.get()); - ASSERT_EQ(TEST_EXCEPTION_STRING_LENGTH, bytesRead); - // Call the function with NULL HTTP2Stream and check if it fails - bytesRead = HTTP2Stream::readCallback(m_dataBegin, TEST_EXCEPTION_STRING_LENGTH, NUMBER_OF_STRINGS, nullptr); - ASSERT_EQ(0, bytesRead); -} -} // namespace test -} // namespace acl -} // namespace alexaClientSDK - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/ACL/test/Transport/HTTP2TransportTest.cpp b/ACL/test/Transport/HTTP2TransportTest.cpp new file mode 100644 index 0000000000..0f648f36a9 --- /dev/null +++ b/ACL/test/Transport/HTTP2TransportTest.cpp @@ -0,0 +1,1142 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "MockAuthDelegate.h" +#include "MockHTTP2Connection.h" +#include "MockHTTP2Request.h" +#include "MockMessageConsumer.h" +#include "MockPostConnect.h" +#include "MockPostConnectFactory.h" +#include "MockTransportObserver.h" + +namespace alexaClientSDK { +namespace acl { +namespace transport { +namespace test { + +using namespace acl::test; +using namespace avsCommon::avs; +using namespace avsCommon::avs::attachment; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils; +using namespace avsCommon::utils::http; +using namespace avsCommon::utils::http2; +using namespace avsCommon::utils::http2::test; +using namespace ::testing; + +/// Test endpoint. +static const std::string TEST_AVS_ENDPOINT_STRING = "http://avs-alexa-na.amazon.com"; + +/// Expected Downchannel URL sent on requests. +static const std::string AVS_DOWNCHANNEL_URL_PATH_EXTENSION = "/v20160207/directives"; + +/// Expected ping URL sent on requests. +static const std::string AVS_PING_URL_PATH_EXTENSION = "/ping"; + +/// Expected Full Downchannel URL sent on requests. +static const std::string FULL_DOWNCHANNEL_URL = TEST_AVS_ENDPOINT_STRING + AVS_DOWNCHANNEL_URL_PATH_EXTENSION; + +/// Expected Full ping URL sent on requests. +static const std::string FULL_PING_URL = TEST_AVS_ENDPOINT_STRING + AVS_PING_URL_PATH_EXTENSION; + +/// A 100 millisecond delay used in tests. +static const auto ONE_HUNDRED_MILLISECOND_DELAY = std::chrono::milliseconds(100); + +/// A 10 millisecond delay used in tests. +static const auto TEN_MILLISECOND_DELAY = std::chrono::milliseconds(10); + +/// A short delay used in tests. +static const auto SHORT_DELAY = std::chrono::seconds(1); + +/// Typical Timeout used in waiting for responses. +static const auto RESPONSE_TIMEOUT = std::chrono::seconds(5); + +/// A longer timeout used in waiting for responses. +static const auto LONG_RESPONSE_TIMEOUT = std::chrono::seconds(10); + +/// HTTP Authorization header. +static const std::string HTTP_AUTHORIZATION_HEADER_BEARER = "Authorization: Bearer"; + +/// Authorization Token. +static const std::string CBL_AUTHORIZATION_TOKEN = "AUTH_TOKEN"; + +/// A test AttachmentId string. +static const std::string TEST_ATTACHMENT_ID_STRING_ONE = "testAttachmentId_1"; + +// Test message string to be sent. +static const std::string TEST_MESSAGE = "aaabbccc"; + +// Test attachment string. +static const std::string TEST_ATTACHMENT_MESSAGE = "MY_A_T_T_ACHMENT"; + +// Test attachment field. +static const std::string TEST_ATTACHMENT_FIELD = "ATTACHMENT"; + +// A constant that means no call as a response +static const long NO_CALL = -2; + +// Non-MIME payload +static const std::string NON_MIME_PAYLOAD = "A_NON_MIME_PAYLOAD"; + +// A test directive. +static const std::string DIRECTIVE1 = + "{\"namespace:\"SpeechSynthesizer\",name:\"Speak\",messageId:\"351df0ff-8041-4891-a925-136f52d54da1\"," + "dialogRequestId:\"58352bb2-7d07-4ba2-944b-10e6df25d193\"}"; + +// Another test directive. +static const std::string DIRECTIVE2 = + "{\"namespace:\"Alerts\",name:\"SetAlert\",messageId:\"ccc005b8-ca8f-4c34-aeb5-73a8dbbd8d37\",dialogRequestId:" + "\"dca0bece-16a7-44f3-b940-e6c4ecc2b1f5\"}"; + +// Test MIME Boundary. +static const std::string MIME_BOUNDARY = "thisisaboundary"; + +// MIME encoded DIRECTIVE1 with start and end MIME boundaries. +static const std::string MIME_BODY_DIRECTIVE1 = "--" + MIME_BOUNDARY + "\r\nContent-Type: application/json" + + "\r\n\r\n" + DIRECTIVE1 + "\r\n--" + MIME_BOUNDARY + "--\r\n"; +; + +// MIME encoded DIRECTIVE2 with start MIME boundary but no end MIME boundary. +static const std::string MIME_BODY_DIRECTIVE2 = "--" + MIME_BOUNDARY + "\r\nContent-Type: application/json" + + "\r\n\r\n" + DIRECTIVE2 + "\r\n--" + MIME_BOUNDARY + "\r\n"; + +// HTTP header to specify MIME boundary and content type. +static const std::string HTTP_BOUNDARY_HEADER = + "Content-Type: multipart/related; boundary=" + MIME_BOUNDARY + "; type=application/json"; + +// The maximum dedicated number of ping streams in HTTP2Transport +static const unsigned MAX_PING_STREAMS = 1; + +// The maximum dedicated number of downchannel streams in HTTP2Transport +static const unsigned MAX_DOWNCHANNEL_STREAMS = 1; + +// The maximum number of HTTP2 requests that can be enqueued at a time waiting for response completion +static const unsigned MAX_AVS_STREAMS = 10; + +// Maximum allowed of POST streams +static const unsigned MAX_POST_STREAMS = MAX_AVS_STREAMS - MAX_DOWNCHANNEL_STREAMS - MAX_PING_STREAMS; + +/// Test harness for @c HTTP2Transport class. +class HTTP2TransportTest : public Test { +public: + /// Initial setup for tests. + void SetUp() override; + + /// Cleanup method. + void TearDown() override; + +protected: + /** + * Setup the handlers for the mocked methods @c AuthDelegateInterface::addAuthObserver(), @c + * PostConnectFactoryInterface::createPostConnect() , @c PostConnectInterface::doPostConnect() , @c + * TransportObserverInterface::onConnected(). + * + * @param sendOnPostConnected A boolean to specify whether to send onPostConnected() event when @c + * PostConnectInterface::doPostConnect() is called. + * @param expectConnected Specify that a call to TransportObserverInterface::onConnected() is expected. + */ + void setupHandlers(bool sendOnPostConnected, bool expectConnected); + + /** + * Helper function to send @c Refreshed Auth State to the @c HTTP2Transport observer. + * It also checks that a proper Auth observer has been registered by @c HTTP2Transport. + */ + void sendAuthStateRefreshed(); + + /** + * Helper function to put the @c HTTP2Transport into connected state. + */ + void authorizeAndConnect(); + + /// The HTTP2Transport instance to be tested. + std::shared_ptr m_http2Transport; + + /// The mock @c AuthDelegateInterface. + std::shared_ptr m_mockAuthDelegate; + + /// The mock @c HTTP2ConnectionInterface. + std::shared_ptr m_mockHttp2Connection; + + /// The mock @c MessageConsumerInterface. + std::shared_ptr m_mockMessageConsumer; + + /// An instance of the @c AttachmentManager. + std::shared_ptr m_attachmentManager; + + /// The mock @c TransportObserverInterface. + std::shared_ptr m_mockTransportObserver; + + /// The mock @c PostConnectFactoryInterface + std::shared_ptr m_mockPostConnectFactory; + + /// The mock @c PostConnectInterface. + std::shared_ptr m_mockPostConnect; + + /// A promise that the Auth Observer will be set. + PromiseFuturePair> m_authObserverSet; + + /// A promise that @c PostConnectFactoryInterface::createPostConnect() will be called). + PromiseFuturePair m_createPostConnectCalled; + + /// A promise that @c PostConnectInterface:doPostConnect() will be called). + PromiseFuturePair> m_doPostConnected; + + /// A promise that the @c TransportObserver.onConnected() will be called. + PromiseFuturePair m_transportConnected; +}; + +/** + * A @c MessageRequestObserverInterface implementation used in this test. + */ +class TestMessageRequestObserver : public avsCommon::sdkInterfaces::MessageRequestObserverInterface { +public: + /* + * Called when a message request has been processed by AVS. + */ + void onSendCompleted(MessageRequestObserverInterface::Status status) { + m_status.setValue(status); + } + + /* + * Called when an exception is thrown when trying to send a message to AVS. + */ + void onExceptionReceived(const std::string& exceptionMessage) { + m_exception.setValue(exceptionMessage); + } + + /// A promise that @c MessageRequestObserverInterface::onSendCompleted() will be called with a @c + /// MessageRequestObserverInterface::Status value + PromiseFuturePair m_status; + + /// A promise that @c MessageRequestObserverInterface::onExceptionReceived() will be called with an exception + /// message + PromiseFuturePair m_exception; +}; + +void HTTP2TransportTest::SetUp() { + m_mockAuthDelegate = std::make_shared>(); + m_mockHttp2Connection = std::make_shared>(FULL_DOWNCHANNEL_URL, FULL_PING_URL); + m_mockMessageConsumer = std::make_shared>(); + m_attachmentManager = std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS); + m_mockTransportObserver = std::make_shared>(); + m_mockPostConnectFactory = std::make_shared>(); + m_mockPostConnect = std::make_shared>(); + m_mockAuthDelegate->setAuthToken(CBL_AUTHORIZATION_TOKEN); + m_http2Transport = HTTP2Transport::create( + m_mockAuthDelegate, + TEST_AVS_ENDPOINT_STRING, + m_mockHttp2Connection, + m_mockMessageConsumer, + m_attachmentManager, + m_mockTransportObserver, + m_mockPostConnectFactory); + + ASSERT_NE(m_http2Transport, nullptr); +} + +void HTTP2TransportTest::TearDown() { + m_http2Transport->shutdown(); +} + +void HTTP2TransportTest::setupHandlers(bool sendOnPostConnected, bool expectConnected) { + Sequence s1, s2; + + // Enforced ordering of mock method calls: + // addAuthObserver should be before onConnected + // createPostConnect should be before doPostConnect + + // Handle AuthDelegateInterface::addAuthObserver() when called. + EXPECT_CALL(*m_mockAuthDelegate, addAuthObserver(_)) + .InSequence(s1) + .WillOnce(Invoke([this](std::shared_ptr argAuthObserver) { + m_authObserverSet.setValue(argAuthObserver); + })); + + { + InSequence dummy; + + // Handle PostConnectFactoryInterface::createPostConnect() when called. + EXPECT_CALL(*m_mockPostConnectFactory, createPostConnect()).WillOnce(InvokeWithoutArgs([this] { + m_createPostConnectCalled.setValue(); + return m_mockPostConnect; + })); + + // Handle PostConnectInterface::doPostConnect() when called. + EXPECT_CALL(*m_mockPostConnect, doPostConnect(_)) + .InSequence(s2) + .WillOnce(Invoke([this, sendOnPostConnected](std::shared_ptr transport) { + m_doPostConnected.setValue(transport); + if (sendOnPostConnected) { + transport->onPostConnected(); + } + return true; + }) + + ); + } + + if (expectConnected) { + // Handle TransportObserverInterface::onConnected() when called. + EXPECT_CALL(*m_mockTransportObserver, onConnected(_)) + .InSequence(s1, s2) + .WillOnce(Invoke([this](std::shared_ptr transport) { m_transportConnected.setValue(); }) + + ); + } +} + +void HTTP2TransportTest::sendAuthStateRefreshed() { + std::shared_ptr authObserver; + + // Wait for HTTP2Transport Auth server registration. + ASSERT_TRUE(m_authObserverSet.waitFor(RESPONSE_TIMEOUT)); + + authObserver = m_authObserverSet.getValue(); + + // Check HTTP2Transport registered itself as the authObserver. + ASSERT_TRUE(authObserver != nullptr); + ASSERT_EQ(authObserver.get(), m_http2Transport.get()); + + // Send REFRESHED Auth State to HTTP2Transport. + authObserver->onAuthStateChange(AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS); +} + +void HTTP2TransportTest::authorizeAndConnect() { + setupHandlers(true, true); + + // Call connect(). + m_http2Transport->connect(); + + // Deliver a 'REFRESHED' status to observers of AuthDelegateInterface. + sendAuthStateRefreshed(); + + // The Mock HTTP2Request replies to any downchannel request with a 200. + ASSERT_TRUE(m_mockHttp2Connection->respondToDownchannelRequests( + static_cast(HTTPResponseCode::SUCCESS_OK), false, RESPONSE_TIMEOUT)); + + // Wait for PostConnectInterface:doPostConnect() call. + ASSERT_TRUE(m_doPostConnected.waitFor(RESPONSE_TIMEOUT)); + + // Wait for a long time (up to 5 second), terminating the wait when the mock of + // TransportObserverInterface::onConnected() is called. + ASSERT_TRUE(m_transportConnected.waitFor(LONG_RESPONSE_TIMEOUT)); +} + +/** + * Test non-authorization on empty auth token. + */ +TEST_F(HTTP2TransportTest, testSlow_emptyAuthToken) { + // Send an empty Auth token. + m_mockAuthDelegate->setAuthToken(""); + + setupHandlers(false, false); + + m_http2Transport->connect(); + + // Give m_http2Transport a chance to misbehave and send a request w/o authorization. + std::this_thread::sleep_for(SHORT_DELAY); + + // Check that no HTTP requests created and sent at this point. + ASSERT_TRUE(m_mockHttp2Connection->isRequestQueueEmpty()); + + sendAuthStateRefreshed(); + + // Should not send any HTTP2 Request. + ASSERT_EQ(m_mockHttp2Connection->waitForRequest(ONE_HUNDRED_MILLISECOND_DELAY), nullptr); +} + +/** + * Test waiting for AuthDelegateInterface. + */ +TEST_F(HTTP2TransportTest, testSlow_waitAuthDelegateInterface) { + setupHandlers(false, false); + + m_http2Transport->connect(); + + // Give m_http2Transport a chance to misbehave and send a request w/o authorization. + std::this_thread::sleep_for(SHORT_DELAY); + + // Check that no HTTP requests created and sent at this point. + ASSERT_TRUE(m_mockHttp2Connection->isRequestQueueEmpty()); + + sendAuthStateRefreshed(); + + // Wait for HTTP2 Request. + ASSERT_NE(m_mockHttp2Connection->waitForRequest(RESPONSE_TIMEOUT), nullptr); + auto request = m_mockHttp2Connection->dequeRequest(); + ASSERT_EQ(request->getUrl(), FULL_DOWNCHANNEL_URL); +} + +/** + * Test verifying the proper inclusion of bearer token in requests. + */ +TEST_F(HTTP2TransportTest, test_bearerTokenInRequest) { + setupHandlers(false, false); + + m_mockHttp2Connection->setWaitRequestHeader(HTTP_AUTHORIZATION_HEADER_BEARER); + + m_http2Transport->connect(); + + sendAuthStateRefreshed(); + + // Wait for HTTP2 Request with Authorization: Bearer in header. + ASSERT_TRUE(m_mockHttp2Connection->waitForRequestWithHeader(RESPONSE_TIMEOUT)); +} + +/** + * Test creation and triggering of post-connect object. + */ +TEST_F(HTTP2TransportTest, test_triggerPostConnectObject) { + setupHandlers(false, false); + + // Don't expect TransportObserverInterface::onConnected() will be called. + EXPECT_CALL(*m_mockTransportObserver, onConnected(_)).Times(0); + + m_http2Transport->connect(); + + sendAuthStateRefreshed(); + + // The Mock HTTP2Request replies to any downchannel request with 200. + ASSERT_TRUE(m_mockHttp2Connection->respondToDownchannelRequests( + static_cast(HTTPResponseCode::SUCCESS_OK), false, RESPONSE_TIMEOUT)); + + // Wait for a long time (up to 5 second), terminating the wait when the mock of + // PostConnectFactoryInterface::createPostConnect() is called. + ASSERT_TRUE(m_createPostConnectCalled.waitFor(RESPONSE_TIMEOUT)); + + // Wait for a long time (up to 5 second), terminating the wait when the mock of + // PostConnectInterface::doPostConnect() is called. + ASSERT_TRUE(m_doPostConnected.waitFor(RESPONSE_TIMEOUT)); +} + +/** + * Test delay of connection status until post-connect object created / notifies success. + */ +TEST_F(HTTP2TransportTest, test_connectionStatusOnPostConnect) { + setupHandlers(true, true); + + // Call connect(). + m_http2Transport->connect(); + + // Deliver a 'REFRESHED' status to observers of AuthDelegateInterface. + sendAuthStateRefreshed(); + + // The Mock HTTP2Request replies to any downchannel request with a 200. + ASSERT_TRUE(m_mockHttp2Connection->respondToDownchannelRequests( + static_cast(HTTPResponseCode::SUCCESS_OK), false, RESPONSE_TIMEOUT)); + + // Wait for PostConnectInterface:doPostConnect() call. + ASSERT_TRUE(m_doPostConnected.waitFor(RESPONSE_TIMEOUT)); + + // Wait for a long time (up to 5 second), terminating the wait when the mock of + // TransportObserverInterface::onConnected() is called. + ASSERT_TRUE(m_transportConnected.waitFor(LONG_RESPONSE_TIMEOUT)); +} + +/** + * Test retry upon failed downchannel connection. + */ +TEST_F(HTTP2TransportTest, testSlow_retryOnDownchannelConnectionFailure) { + setupHandlers(false, false); + + EXPECT_CALL(*m_mockTransportObserver, onConnected(_)).Times(0); + + // Call connect(). + m_http2Transport->connect(); + + // Deliver a 'REFRESHED' status to observers of AuthDelegateInterface. + sendAuthStateRefreshed(); + + // The Mock HTTP2Request replies to any downchannel request with a 500. + ASSERT_TRUE(m_mockHttp2Connection->respondToDownchannelRequests( + static_cast(HTTPResponseCode::SERVER_ERROR_INTERNAL), false, RESPONSE_TIMEOUT)); + + // Wait for a long time (up to 10 second), terminating the wait when the mock of HTTP2Connection receives a second + // attempt to create a downchannel request. + m_mockHttp2Connection->waitForRequest(LONG_RESPONSE_TIMEOUT, 2); +} + +/** + * Test sending of MessageRequest content. + */ +TEST_F(HTTP2TransportTest, test_messageRequestContent) { + setupHandlers(false, false); + + // Call connect(). + m_http2Transport->connect(); + + // Deliver a 'REFRESHED' status to observers of AuthDelegateInterface. + sendAuthStateRefreshed(); + + ASSERT_TRUE(m_mockHttp2Connection->respondToDownchannelRequests( + static_cast(HTTPResponseCode::SUCCESS_OK), false, RESPONSE_TIMEOUT)); + + // Wait for doPostConnect(). + ASSERT_TRUE(m_doPostConnected.waitFor(RESPONSE_TIMEOUT)); + + // Send post connect message. + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + m_http2Transport->sendPostConnectMessage(messageReq); + + // Wait for the postConnect message to become HTTP message request and HTTP body to be fully reassembled. + auto postMessage = m_mockHttp2Connection->waitForPostRequest(LONG_RESPONSE_TIMEOUT); + ASSERT_NE(postMessage, nullptr); + + // The number of MIME parts decoded should just be 1. + ASSERT_EQ(postMessage->getMimeResponseSink()->getCountOfMimeParts(), 1u); + + // Get the first MIME part message. + auto mimeMessage = postMessage->getMimeResponseSink()->getMimePart(0); + std::string mimeMessageString(mimeMessage.begin(), mimeMessage.end()); + + // Check the MIME part is the message sent. + ASSERT_EQ(TEST_MESSAGE, mimeMessageString); +} + +/** + * Test sending of MessageRequest with attachment data. + */ +TEST_F(HTTP2TransportTest, test_messageRequestWithAttachment) { + // Create an attachment reader. + std::vector attachment(TEST_ATTACHMENT_MESSAGE.begin(), TEST_ATTACHMENT_MESSAGE.end()); + std::shared_ptr attachmentReader = + avsCommon::avs::attachment::AttachmentUtils::createAttachmentReader(attachment); + ASSERT_NE(attachmentReader, nullptr); + + setupHandlers(false, false); + + // Call connect(). + m_http2Transport->connect(); + + // Deliver a 'REFRESHED' status to observers of AuthDelegateInterface. + sendAuthStateRefreshed(); + + m_mockHttp2Connection->respondToDownchannelRequests( + static_cast(HTTPResponseCode::SUCCESS_OK), false, RESPONSE_TIMEOUT); + + // Wait for doPostConnect(). + ASSERT_TRUE(m_doPostConnected.waitFor(RESPONSE_TIMEOUT)); + + // Send post connect message with attachment. + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + messageReq->addAttachmentReader(TEST_ATTACHMENT_FIELD, attachmentReader); + m_http2Transport->sendPostConnectMessage(messageReq); + + // Wait for the postConnect message to become HTTP message request and HTTP body to be fully reassembled. + auto postMessage = m_mockHttp2Connection->waitForPostRequest(LONG_RESPONSE_TIMEOUT); + ASSERT_NE(postMessage, nullptr); + + // The number of MIME parts decoded should just be 2. + ASSERT_EQ(postMessage->getMimeResponseSink()->getCountOfMimeParts(), 2u); + + // Get the first MIME part message and check it is the message sent. + auto mimeMessage = postMessage->getMimeResponseSink()->getMimePart(0); + std::string mimeMessageString(mimeMessage.begin(), mimeMessage.end()); + ASSERT_EQ(TEST_MESSAGE, mimeMessageString); + + // Get the second MIME part message and check it is the attachement sent. + auto mimeAttachment = postMessage->getMimeResponseSink()->getMimePart(1); + std::string mimeAttachmentString(mimeAttachment.begin(), mimeAttachment.end()); + ASSERT_EQ(TEST_ATTACHMENT_MESSAGE, mimeAttachmentString); +} + +/** + * Test pause of sending message when attachment buffer (SDS) empty but not closed. + */ +TEST_F(HTTP2TransportTest, test_pauseSendWhenSDSEmpty) { + setupHandlers(false, false); + + // Call connect(). + m_http2Transport->connect(); + + // Deliver a 'REFRESHED' status to observers of AuthDelegateInterface. + sendAuthStateRefreshed(); + + m_mockHttp2Connection->respondToDownchannelRequests( + static_cast(HTTPResponseCode::SUCCESS_OK), false, RESPONSE_TIMEOUT); + + // Wait for doPostConnect(). + ASSERT_TRUE(m_doPostConnected.waitFor(RESPONSE_TIMEOUT)); + + // Send post connect message with attachment. + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + AttachmentManager attMgr(AttachmentManager::AttachmentType::IN_PROCESS); + std::vector attachment(TEST_ATTACHMENT_MESSAGE.begin(), TEST_ATTACHMENT_MESSAGE.end()); + std::shared_ptr attachmentReader = + attMgr.createReader(TEST_ATTACHMENT_ID_STRING_ONE, avsCommon::utils::sds::ReaderPolicy::NONBLOCKING); + ASSERT_NE(attachmentReader, nullptr); + messageReq->addAttachmentReader(TEST_ATTACHMENT_FIELD, attachmentReader); + m_http2Transport->sendPostConnectMessage(messageReq); + + // Send the attachment in chunks in another thread. + std::thread writerThread([this, &attMgr, &attachment]() { + // Number of chunks the attachment will be divided into + const unsigned chunks = 4; + // the size of each chunk in bytes, this is the ceiling of (attachment.size / chunks) + unsigned int chunkSize = (attachment.size() + chunks - 1) / chunks; + auto writer = attMgr.createWriter(TEST_ATTACHMENT_ID_STRING_ONE, avsCommon::utils::sds::WriterPolicy::BLOCKING); + AttachmentWriter::WriteStatus writeStatus = AttachmentWriter::WriteStatus::OK; + unsigned int lastChunkSize = (attachment.size() % chunks == 0) ? chunkSize : attachment.size() % chunks; + for (unsigned chunk = 0; chunk < chunks; chunk++) { + writer->write( + &attachment[chunk * chunkSize], (chunk == chunks - 1) ? lastChunkSize : chunkSize, &writeStatus); + ASSERT_EQ(writeStatus, AttachmentWriter::WriteStatus::OK); + ASSERT_TRUE(m_mockHttp2Connection->isPauseOnSendReceived(ONE_HUNDRED_MILLISECOND_DELAY)); + } + writer->close(); + }); + + // Wait for the postConnect message to become HTTP message request and HTTP body to be fully reassembled. + auto postMessage = m_mockHttp2Connection->waitForPostRequest(LONG_RESPONSE_TIMEOUT); + ASSERT_NE(postMessage, nullptr); + + // The number of MIME parts decoded should just be 2. + ASSERT_EQ(postMessage->getMimeResponseSink()->getCountOfMimeParts(), 2u); + + // Get the first MIME part message and check it is the message sent. + auto mimeMessage = postMessage->getMimeResponseSink()->getMimePart(0); + std::string mimeMessageString(mimeMessage.begin(), mimeMessage.end()); + ASSERT_EQ(TEST_MESSAGE, mimeMessageString); + + // Get the second MIME part message and check it is the attachment sent. + auto mimeAttachment = postMessage->getMimeResponseSink()->getMimePart(1); + std::string mimeAttachmentString(mimeAttachment.begin(), mimeAttachment.end()); + ASSERT_EQ(TEST_ATTACHMENT_MESSAGE, mimeAttachmentString); + + writerThread.join(); +} + +/** + * Test queuing MessageRequests until a response code has been received for any outstanding MessageRequest + */ +TEST_F(HTTP2TransportTest, testSlow_messageRequestsQueuing) { + authorizeAndConnect(); + + // Send 5 messages. + std::vector> messageObservers; + const unsigned messagesCount = 5; // number of test messages to Send + for (unsigned messageNum = 0; messageNum < messagesCount; messageNum++) { + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + auto messageObserver = std::make_shared(); + messageObservers.push_back(messageObserver); + messageReq->addObserver(messageObserver); + m_http2Transport->send(messageReq); + } + + // Give m_http2Transport a chance to misbehave and send more than a single request before receiving a response. + std::this_thread::sleep_for(SHORT_DELAY); + + // Check that only 1 out of the 5 POST messages have been in the outgoing send queue. + ASSERT_EQ(m_mockHttp2Connection->getPostRequestsNum(), 1u); + + // Delayed 200 response for each POST request. + unsigned int postsRequestsCount = 0; + while (postsRequestsCount < messagesCount) { + auto request = m_mockHttp2Connection->waitForPostRequest(RESPONSE_TIMEOUT); + if (request) { + postsRequestsCount++; + // Give m_http2Transport a chance to misbehave and send requests before receiving a response. + std::this_thread::sleep_for(SHORT_DELAY); + request->getSink()->onReceiveResponseCode(HTTPResponseCode::SUCCESS_OK); + } else { + break; + } + } + + // Make sure HTTP2Transport sends out the 5 POST requests. + ASSERT_EQ(postsRequestsCount, messagesCount); + + // On disconnect, send CANCELED response for each POST REQUEST. + EXPECT_CALL(*m_mockHttp2Connection, disconnect()).WillOnce(Invoke([this]() { + while (true) { + auto request = m_mockHttp2Connection->dequePostRequest(); + if (!request) break; + + request->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::CANCELLED); + }; + })); + + m_http2Transport->shutdown(); + + // Count the number of messages that received CANCELED or NOT_CONNECTED event. + unsigned messagesCanceled = 0; + for (unsigned messageNum = 0; messageNum < messagesCount; messageNum++) { + if (messageObservers[messageNum]->m_status.waitFor(RESPONSE_TIMEOUT)) { + switch (messageObservers[messageNum]->m_status.getValue()) { + case MessageRequestObserverInterface::Status::CANCELED: + case MessageRequestObserverInterface::Status::NOT_CONNECTED: + messagesCanceled++; + default: + break; + } + } + } + + ASSERT_EQ(messagesCanceled, messagesCount); +} + +/** + * Test notification of onSendCompleted (check mapping of all cases and their mapping to + * MessageRequestObserverInterface::Status). + */ +TEST_F(HTTP2TransportTest, test_onSendCompletedNotification) { + // Contains the mapping of HTTPResponseCode, HTTP2ResponseFinishedStatus and the expected + // MessageRequestObserverInterface::Status. + const std::vector> + messageResponseMap = { + {std::make_tuple( + NO_CALL, + HTTP2ResponseFinishedStatus::INTERNAL_ERROR, + MessageRequestObserverInterface::Status::INTERNAL_ERROR)}, + {std::make_tuple( + NO_CALL, HTTP2ResponseFinishedStatus::CANCELLED, MessageRequestObserverInterface::Status::CANCELED)}, + {std::make_tuple( + NO_CALL, HTTP2ResponseFinishedStatus::TIMEOUT, MessageRequestObserverInterface::Status::TIMEDOUT)}, + {std::make_tuple( + NO_CALL, + static_cast(-1), + MessageRequestObserverInterface::Status::INTERNAL_ERROR)}, + {std::make_tuple( + HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED, + HTTP2ResponseFinishedStatus::INTERNAL_ERROR, + MessageRequestObserverInterface::Status::INTERNAL_ERROR)}, + {std::make_tuple( + HTTPResponseCode::SUCCESS_OK, + HTTP2ResponseFinishedStatus::CANCELLED, + MessageRequestObserverInterface::Status::CANCELED)}, + {std::make_tuple( + HTTPResponseCode::REDIRECTION_START_CODE, + HTTP2ResponseFinishedStatus::TIMEOUT, + MessageRequestObserverInterface::Status::TIMEDOUT)}, + {std::make_tuple( + HTTPResponseCode::CLIENT_ERROR_BAD_REQUEST, + static_cast(-1), + MessageRequestObserverInterface::Status::INTERNAL_ERROR)}, + {std::make_tuple( + HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED, + HTTP2ResponseFinishedStatus::COMPLETE, + MessageRequestObserverInterface::Status::INTERNAL_ERROR)}, + {std::make_tuple( + HTTPResponseCode::SUCCESS_OK, + HTTP2ResponseFinishedStatus::COMPLETE, + MessageRequestObserverInterface::Status::SUCCESS)}, + {std::make_tuple( + HTTPResponseCode::SUCCESS_NO_CONTENT, + HTTP2ResponseFinishedStatus::COMPLETE, + MessageRequestObserverInterface::Status::SUCCESS_NO_CONTENT)}, + {std::make_tuple( + HTTPResponseCode::REDIRECTION_START_CODE, + HTTP2ResponseFinishedStatus::COMPLETE, + MessageRequestObserverInterface::Status::SERVER_OTHER_ERROR)}, + {std::make_tuple( + HTTPResponseCode::REDIRECTION_END_CODE, + HTTP2ResponseFinishedStatus::COMPLETE, + MessageRequestObserverInterface::Status::SERVER_OTHER_ERROR)}, + {std::make_tuple( + HTTPResponseCode::CLIENT_ERROR_BAD_REQUEST, + HTTP2ResponseFinishedStatus::COMPLETE, + MessageRequestObserverInterface::Status::BAD_REQUEST)}, + {std::make_tuple( + HTTPResponseCode::CLIENT_ERROR_FORBIDDEN, + HTTP2ResponseFinishedStatus::COMPLETE, + MessageRequestObserverInterface::Status::INVALID_AUTH)}, + {std::make_tuple( + HTTPResponseCode::SERVER_ERROR_INTERNAL, + HTTP2ResponseFinishedStatus::COMPLETE, + MessageRequestObserverInterface::Status::SERVER_INTERNAL_ERROR_V2)}, + {std::make_tuple( + -1, + HTTP2ResponseFinishedStatus::COMPLETE, + MessageRequestObserverInterface::Status::SERVER_OTHER_ERROR)}, + }; + + authorizeAndConnect(); + + // Send a message for each test case defined in the messageResponseMap. + std::vector> messageObservers; + unsigned messagesCount = messageResponseMap.size(); // number of test messages to send + for (unsigned messageNum = 0; messageNum < messagesCount; messageNum++) { + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + auto messageObserver = std::make_shared(); + messageObservers.push_back(messageObserver); + messageReq->addObserver(messageObserver); + m_http2Transport->send(messageReq); + } + + // Send the response code for each POST request. + unsigned int postsRequestsCount = 0; + for (unsigned int i = 0; i < messagesCount; i++) { + auto request = m_mockHttp2Connection->waitForPostRequest(RESPONSE_TIMEOUT); + if (request) { + m_mockHttp2Connection->dequePostRequest(); + postsRequestsCount++; + auto responseCode = std::get<0>(messageResponseMap[i]); + if (responseCode != NO_CALL) { + request->getSink()->onReceiveResponseCode(responseCode); + } + auto responseFinished = std::get<1>(messageResponseMap[i]); + request->getSink()->onResponseFinished(static_cast(responseFinished)); + + } else { + break; + } + } + + // Check if we got all the POST requests. + ASSERT_EQ(postsRequestsCount, messagesCount); + + // Check that we got the right onSendCompleted notifications. + for (unsigned messageNum = 0; messageNum < messagesCount; messageNum++) { + if (messageObservers[messageNum]->m_status.waitFor(RESPONSE_TIMEOUT)) { + auto expectedMessageObserverStatus = std::get<2>(messageResponseMap[messageNum]); + ASSERT_EQ(messageObservers[messageNum]->m_status.getValue(), expectedMessageObserverStatus); + } + } +} + +/** + * Test onExceptionReceived() notification for non 200 content. + */ +TEST_F(HTTP2TransportTest, test_onExceptionReceivedNon200Content) { + authorizeAndConnect(); + + // Send a message. + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + auto messageObserver = std::make_shared(); + messageReq->addObserver(messageObserver); + m_http2Transport->send(messageReq); + + auto request = m_mockHttp2Connection->waitForPostRequest(RESPONSE_TIMEOUT); + ASSERT_NE(request, nullptr); + request->getSink()->onReceiveResponseCode(HTTPResponseCode::SERVER_ERROR_INTERNAL); + request->getSink()->onReceiveData(NON_MIME_PAYLOAD.c_str(), NON_MIME_PAYLOAD.size()); + request->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::COMPLETE); + + ASSERT_TRUE(messageObserver->m_exception.waitFor(RESPONSE_TIMEOUT)); + ASSERT_EQ(messageObserver->m_exception.getValue(), NON_MIME_PAYLOAD); + ASSERT_TRUE(messageObserver->m_status.waitFor(RESPONSE_TIMEOUT)); + ASSERT_EQ(messageObserver->m_status.getValue(), MessageRequestObserverInterface::Status::SERVER_INTERNAL_ERROR_V2); +} + +/** + * Test MessageConsumerInterface receipt of directives on Downchannel and Event streams. + */ +TEST_F(HTTP2TransportTest, test_messageConsumerReceiveDirective) { + PromiseFuturePair messagesAreConsumed; + unsigned int consumedMessageCount = 0; + std::vector messages; + + authorizeAndConnect(); + + EXPECT_CALL(*m_mockMessageConsumer, consumeMessage(_, _)) + .WillRepeatedly(Invoke([&messagesAreConsumed, &consumedMessageCount, &messages]( + const std::string& contextId, const std::string& message) { + consumedMessageCount++; + messages.push_back(message); + if (consumedMessageCount == 2u) { + messagesAreConsumed.setValue(); + } + }) + + ); + + // Send a message. + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + auto messageObserver = std::make_shared(); + messageReq->addObserver(messageObserver); + m_http2Transport->send(messageReq); + + auto eventStream = m_mockHttp2Connection->waitForPostRequest(RESPONSE_TIMEOUT); + ASSERT_NE(eventStream, nullptr); + eventStream->getSink()->onReceiveResponseCode(HTTPResponseCode::SUCCESS_OK); + eventStream->getSink()->onReceiveHeaderLine(HTTP_BOUNDARY_HEADER); + eventStream->getSink()->onReceiveData(MIME_BODY_DIRECTIVE1.c_str(), MIME_BODY_DIRECTIVE1.size()); + eventStream->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::COMPLETE); + + auto downchannelStream = m_mockHttp2Connection->getDownchannelRequest(); + ASSERT_NE(downchannelStream, nullptr); + downchannelStream->getSink()->onReceiveResponseCode(HTTPResponseCode::SUCCESS_OK); + downchannelStream->getSink()->onReceiveHeaderLine(HTTP_BOUNDARY_HEADER); + downchannelStream->getSink()->onReceiveData(MIME_BODY_DIRECTIVE2.c_str(), MIME_BODY_DIRECTIVE2.size()); + + ASSERT_TRUE(messagesAreConsumed.waitFor(RESPONSE_TIMEOUT)); + ASSERT_EQ(messages[0], DIRECTIVE1); + ASSERT_EQ(messages[1], DIRECTIVE2); +} + +/** + * Test broadcast onServerSideDisconnect() upon closure of successfully opened downchannel. + */ +TEST_F(HTTP2TransportTest, test_onServerSideDisconnectOnDownchannelClosure) { + authorizeAndConnect(); + + // Send a message. + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + m_http2Transport->send(messageReq); + + PromiseFuturePair gotOnServerSideDisconnect; + auto setGotOnServerSideDisconnect = [&gotOnServerSideDisconnect] { gotOnServerSideDisconnect.setValue(); }; + + PromiseFuturePair gotOnDisconnected; + auto setGotOnDisconnected = [&gotOnDisconnected] { gotOnDisconnected.setValue(); }; + + // Expect disconnect events later when downchannel receives a COMPLETE finished response. + { + InSequence dummy; + EXPECT_CALL(*m_mockTransportObserver, onServerSideDisconnect(_)) + .Times(1) + .WillOnce(InvokeWithoutArgs(setGotOnServerSideDisconnect)); + EXPECT_CALL(*m_mockTransportObserver, onDisconnected(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(setGotOnDisconnected)); + } + + // Upon receiving the message, HTTP2Connection/request will reply to the down-channel request with + // onResponseFinished(COMPLETE). + auto eventStream = m_mockHttp2Connection->waitForPostRequest(RESPONSE_TIMEOUT); + ASSERT_NE(eventStream, nullptr); + auto downchannelStream = m_mockHttp2Connection->getDownchannelRequest(); + downchannelStream->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::COMPLETE); + + // Wait for onResponseFinished() to be handled. + ASSERT_TRUE(gotOnServerSideDisconnect.waitFor(RESPONSE_TIMEOUT)); + ASSERT_TRUE(gotOnDisconnected.waitFor(RESPONSE_TIMEOUT)); +} + +/** + * Test detection of MessageRequest timeout and trigger of ping request. + */ +TEST_F(HTTP2TransportTest, test_messageRequestTimeoutPingRequest) { + authorizeAndConnect(); + + // Send a message. + std::shared_ptr messageReq = std::make_shared(TEST_MESSAGE, ""); + m_http2Transport->send(messageReq); + + // Upon receiving the message, the mock HTTP2Connection/request will reply to the request with + // onResponseFinished(TIMEOUT). + auto eventStream = m_mockHttp2Connection->waitForPostRequest(RESPONSE_TIMEOUT); + ASSERT_NE(eventStream, nullptr); + eventStream->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::TIMEOUT); + + // Wait for a long time (up to 5 seconds) for the mock HTTP2Connection to receive a ping request. + ASSERT_NE(m_mockHttp2Connection->waitForPingRequest(RESPONSE_TIMEOUT), nullptr); +} + +/** + * Test detection of network inactivity and trigger of ping request and continued ping for continued inactivity. + */ +TEST_F(HTTP2TransportTest, testTimer_networkInactivityPingRequest) { + // Short time to wait for inactivity before sending a ping. + static const std::chrono::seconds testInactivityTimeout = SHORT_DELAY; + // This test just checks that a second and third ping will be sen + static const unsigned expectedInactivityPingCount{3u}; + // How long until pings should be sent plus some extra time to allow notifications to be processed. + static const std::chrono::seconds testInactivityTime = {testInactivityTimeout * expectedInactivityPingCount + + SHORT_DELAY}; + + // Setup HTTP2Transport with shorter ping inactivity timeout. + HTTP2Transport::Configuration cfg; + cfg.inactivityTimeout = testInactivityTimeout; + m_http2Transport = HTTP2Transport::create( + m_mockAuthDelegate, + TEST_AVS_ENDPOINT_STRING, + m_mockHttp2Connection, + m_mockMessageConsumer, + m_attachmentManager, + m_mockTransportObserver, + m_mockPostConnectFactory, + cfg); + + authorizeAndConnect(); + + PromiseFuturePair gotPings; + + // Respond 204 to ping requests. + std::thread pingResponseThread([this, &gotPings]() { + unsigned pingCount{0}; + while (pingCount < expectedInactivityPingCount) { + auto pingRequest = m_mockHttp2Connection->waitForPingRequest(RESPONSE_TIMEOUT); + if (!pingRequest) { + continue; + } + m_mockHttp2Connection->dequePingRequest(); + pingRequest->getSink()->onReceiveResponseCode(HTTPResponseCode::SUCCESS_NO_CONTENT); + pingRequest->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::COMPLETE); + pingCount++; + } + gotPings.setValue(); + }); + + ASSERT_TRUE(gotPings.waitFor(testInactivityTime)); + + pingResponseThread.join(); +} + +/** + * Test connection tear down for ping timeout. + */ +TEST_F(HTTP2TransportTest, testSlow_tearDownPingTimeout) { + // Short time to wait for inactivity before sending a ping. + const std::chrono::seconds testInactivityTimeout = SHORT_DELAY; + + // Setup HTTP2Transport with shorter ping inactivity timeout. + HTTP2Transport::Configuration cfg; + cfg.inactivityTimeout = testInactivityTimeout; + m_http2Transport = HTTP2Transport::create( + m_mockAuthDelegate, + TEST_AVS_ENDPOINT_STRING, + m_mockHttp2Connection, + m_mockMessageConsumer, + m_attachmentManager, + m_mockTransportObserver, + m_mockPostConnectFactory, + cfg); + + authorizeAndConnect(); + + PromiseFuturePair gotOnDisconnected; + auto setGotOnDisconnected = [&gotOnDisconnected] { gotOnDisconnected.setValue(); }; + + EXPECT_CALL(*m_mockTransportObserver, onDisconnected(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(setGotOnDisconnected)); + + // Reply to a ping request. + std::thread pingThread([this]() { + auto pingRequest = m_mockHttp2Connection->waitForPingRequest(RESPONSE_TIMEOUT); + ASSERT_TRUE(pingRequest); + m_mockHttp2Connection->dequePingRequest(); + pingRequest->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::TIMEOUT); + }); + + ASSERT_TRUE(gotOnDisconnected.waitFor(RESPONSE_TIMEOUT)); + + pingThread.join(); +} + +/** + * Test connection tear down for ping failure. + */ +TEST_F(HTTP2TransportTest, testSlow_tearDownPingFailure) { + // Short time to wait for inactivity before sending a ping. + const std::chrono::seconds testInactivityTimeout = SHORT_DELAY; + + // Setup HTTP2Transport with shorter ping inactivity timeout. + HTTP2Transport::Configuration cfg; + cfg.inactivityTimeout = testInactivityTimeout; + m_http2Transport = HTTP2Transport::create( + m_mockAuthDelegate, + TEST_AVS_ENDPOINT_STRING, + m_mockHttp2Connection, + m_mockMessageConsumer, + m_attachmentManager, + m_mockTransportObserver, + m_mockPostConnectFactory, + cfg); + + authorizeAndConnect(); + + PromiseFuturePair gotOnDisconnected; + auto setGotOnDisconnected = [&gotOnDisconnected] { gotOnDisconnected.setValue(); }; + + EXPECT_CALL(*m_mockTransportObserver, onDisconnected(_, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(setGotOnDisconnected)); + + // Reply to a ping request. + std::thread pingThread([this]() { + auto pingRequest = m_mockHttp2Connection->waitForPingRequest(RESPONSE_TIMEOUT); + ASSERT_TRUE(pingRequest); + m_mockHttp2Connection->dequePingRequest(); + pingRequest->getSink()->onReceiveResponseCode(HTTPResponseCode::CLIENT_ERROR_BAD_REQUEST); + pingRequest->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::COMPLETE); + }); + + ASSERT_TRUE(gotOnDisconnected.waitFor(RESPONSE_TIMEOUT)); + + pingThread.join(); +} + +/** + * Test limiting use of AVS streams to 10. + */ +TEST_F(HTTP2TransportTest, testSlow_avsStreamsLimit) { + // Number of test messages to send for this test. This number is much larger than MAX_POST_STREAMS + // to assure that this test exercises the case where more requests are outstanding than are allowed + // to be sent at one time, forcing HTTP2Transport to queue the requests until some requests complete. + const unsigned messagesCount = MAX_POST_STREAMS * 2; + + authorizeAndConnect(); + + m_mockHttp2Connection->setResponseToPOSTRequests(HTTPResponseCode::SUCCESS_OK); + + // Send the 20 messages. + std::vector> messageObservers; + + for (unsigned messageNum = 0; messageNum < messagesCount; messageNum++) { + std::shared_ptr messageReq = + std::make_shared(TEST_MESSAGE + std::to_string(messageNum), ""); + auto messageObserver = std::make_shared(); + messageObservers.push_back(messageObserver); + messageReq->addObserver(messageObserver); + m_http2Transport->send(messageReq); + } + + // Check that there was a downchannel request sent out. + ASSERT_TRUE(m_mockHttp2Connection->getDownchannelRequest(RESPONSE_TIMEOUT)); + + // Check the messages we sent were limited. + ASSERT_EQ(m_mockHttp2Connection->getPostRequestsNum(), MAX_POST_STREAMS); + + unsigned int completed = 0; + std::shared_ptr request; + while ((request = m_mockHttp2Connection->dequePostRequest(RESPONSE_TIMEOUT)) != nullptr && + completed < messagesCount) { + request->getSink()->onResponseFinished(HTTP2ResponseFinishedStatus::COMPLETE); + completed++; + // Give m_http2Transport a little time to misbehave. + std::this_thread::sleep_for(TEN_MILLISECOND_DELAY); + } + + // Check all the POST requests have been enqueued + ASSERT_EQ(completed, messagesCount); + + // Check that the maximum number of enqueued messages at any time has been limited. + ASSERT_EQ(m_mockHttp2Connection->getMaxPostRequestsEnqueud(), MAX_POST_STREAMS); +} + +} // namespace test +} // namespace transport +} // namespace acl +} // namespace alexaClientSDK diff --git a/ACL/test/Transport/MessageRouterTest.cpp b/ACL/test/Transport/MessageRouterTest.cpp index 5681b8765f..0794b9a948 100644 --- a/ACL/test/Transport/MessageRouterTest.cpp +++ b/ACL/test/Transport/MessageRouterTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -23,26 +23,26 @@ namespace test { using namespace alexaClientSDK::avsCommon::sdkInterfaces; -TEST_F(MessageRouterTest, getConnectionStatusReturnsDisconnectedBeforeConnect) { +TEST_F(MessageRouterTest, test_getConnectionStatusReturnsDisconnectedBeforeConnect) { ASSERT_EQ(m_router->getConnectionStatus().first, ConnectionStatusObserverInterface::Status::DISCONNECTED); } -TEST_F(MessageRouterTest, getConnectionStatusReturnsPendingAfterConnectingStarts) { +TEST_F(MessageRouterTest, test_getConnectionStatusReturnsPendingAfterConnectingStarts) { setupStateToPending(); ASSERT_EQ(m_router->getConnectionStatus().first, ConnectionStatusObserverInterface::Status::PENDING); } -TEST_F(MessageRouterTest, getConnectionStatusReturnsConnectedAfterConnectionEstablished) { +TEST_F(MessageRouterTest, test_getConnectionStatusReturnsConnectedAfterConnectionEstablished) { setupStateToConnected(); ASSERT_EQ(m_router->getConnectionStatus().first, ConnectionStatusObserverInterface::Status::CONNECTED); } -TEST_F(MessageRouterTest, getConnectionStatusReturnsConnectedAfterDisconnected) { +TEST_F(MessageRouterTest, test_getConnectionStatusReturnsConnectedAfterDisconnected) { m_router->onDisconnected(m_mockTransport, ConnectionStatusObserverInterface::ChangedReason::ACL_DISABLED); ASSERT_EQ(m_router->getConnectionStatus().first, ConnectionStatusObserverInterface::Status::DISCONNECTED); } -TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfConnectionPendingAfterConnect) { +TEST_F(MessageRouterTest, test_ensureTheMessageRouterObserverIsInformedOfConnectionPendingAfterConnect) { setupStateToPending(); // wait for the result to propagate by scheduling a task on the client executor @@ -55,7 +55,7 @@ TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfConnectionPe ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); } -TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfNewConnection) { +TEST_F(MessageRouterTest, test_ensureTheMessageRouterObserverIsInformedOfNewConnection) { setupStateToConnected(); // wait for the result to propagate by scheduling a task on the client executor @@ -68,7 +68,7 @@ TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfNewConnectio ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); } -TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfTransportDisconnection) { +TEST_F(MessageRouterTest, test_ensureTheMessageRouterObserverIsInformedOfTransportDisconnection) { setupStateToConnected(); auto reason = ConnectionStatusObserverInterface::ChangedReason::ACL_DISABLED; @@ -83,7 +83,7 @@ TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfTransportDis ASSERT_EQ(m_mockMessageRouterObserver->getLatestConnectionChangedReason(), reason); } -TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfRouterDisconnection) { +TEST_F(MessageRouterTest, test_ensureTheMessageRouterObserverIsInformedOfRouterDisconnection) { setupStateToConnected(); m_router->disable(); @@ -99,7 +99,7 @@ TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfRouterDiscon ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); } -TEST_F(MessageRouterTest, sendIsSuccessfulWhenConnected) { +TEST_F(MessageRouterTest, test_sendIsSuccessfulWhenConnected) { setupStateToConnected(); auto messageRequest = createMessageRequest(); @@ -107,24 +107,22 @@ TEST_F(MessageRouterTest, sendIsSuccessfulWhenConnected) { // Expect to have the message sent to the transport EXPECT_CALL(*m_mockTransport, send(messageRequest)).Times(1); - // TODO: ACSDK-421: Revert this to use send(). m_router->sendMessage(messageRequest); // Since we connected we will be disconnected when the router is destroyed EXPECT_CALL(*m_mockTransport, disconnect()).Times(AnyNumber()); } -TEST_F(MessageRouterTest, sendFailsWhenDisconnected) { +TEST_F(MessageRouterTest, test_sendFailsWhenDisconnected) { auto messageRequest = createMessageRequest(); // Expect to have the message sent to the transport EXPECT_CALL(*m_mockTransport, send(messageRequest)).Times(0); - // TODO: ACSDK-421: Revert this to use send(). m_router->sendMessage(messageRequest); } -TEST_F(MessageRouterTest, sendFailsWhenPending) { +TEST_F(MessageRouterTest, test_sendFailsWhenPending) { // Ensure a transport exists initializeMockTransport(m_mockTransport.get()); m_router->enable(); @@ -134,12 +132,11 @@ TEST_F(MessageRouterTest, sendFailsWhenPending) { // Expect to have the message sent to the transport. EXPECT_CALL(*m_mockTransport, send(messageRequest)).Times(1); - // TODO: ACSDK-421: Revert this to use send(). m_router->sendMessage(messageRequest); waitOnMessageRouter(SHORT_TIMEOUT_MS); } -TEST_F(MessageRouterTest, sendMessageDoesNotSendAfterDisconnected) { +TEST_F(MessageRouterTest, test_sendMessageDoesNotSendAfterDisconnected) { setupStateToConnected(); auto messageRequest = createMessageRequest(); @@ -150,11 +147,10 @@ TEST_F(MessageRouterTest, sendMessageDoesNotSendAfterDisconnected) { // Expect to have the message sent to the transport EXPECT_CALL(*m_mockTransport, send(messageRequest)).Times(0); - // TODO: ACSDK-421: Revert this to use send(). m_router->sendMessage(messageRequest); } -TEST_F(MessageRouterTest, disconnectDisconnectsConnectedTransports) { +TEST_F(MessageRouterTest, test_disconnectDisconnectsConnectedTransports) { setupStateToConnected(); EXPECT_CALL(*m_mockTransport, doShutdown()).Times(1); @@ -162,7 +158,7 @@ TEST_F(MessageRouterTest, disconnectDisconnectsConnectedTransports) { m_router->disable(); } -TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) { +TEST_F(MessageRouterTest, test_serverSideDisconnectCreatesANewTransport) { /* * This test is difficult to setup in a nice way. The idea is to replace the original * transport with a new one, call onServerSideDisconnect to make it the new active @@ -175,7 +171,7 @@ TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) { auto newTransport = std::make_shared>(); initializeMockTransport(newTransport.get()); - m_router->setMockTransport(newTransport); + m_transportFactory->setMockTransport(newTransport); // Reset the MessageRouterObserver, there should be no interactions with the observer m_router->onServerSideDisconnect(oldTransport); @@ -210,7 +206,6 @@ TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) { EXPECT_CALL(*newTransport.get(), send(messageRequest)).Times(1); - // TODO: ACSDK-421: Revert this to use send(). m_router->sendMessage(messageRequest); waitOnMessageRouter(SHORT_TIMEOUT_MS); @@ -219,7 +214,7 @@ TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) { /** * This tests the calling of private method @c receive() for MessageRouterObserver from MessageRouter */ -TEST_F(MessageRouterTest, onReceiveTest) { +TEST_F(MessageRouterTest, test_onReceive) { m_mockMessageRouterObserver->reset(); m_router->consumeMessage(CONTEXT_ID, MESSAGE); waitOnMessageRouter(SHORT_TIMEOUT_MS); @@ -232,12 +227,40 @@ TEST_F(MessageRouterTest, onReceiveTest) { * This tests the calling of private method @c onConnectionStatusChanged() * for MessageRouterObserver from MessageRouter */ -TEST_F(MessageRouterTest, onConnectionStatusChangedTest) { +TEST_F(MessageRouterTest, test_onConnectionStatusChanged) { m_mockMessageRouterObserver->reset(); setupStateToConnected(); waitOnMessageRouter(SHORT_TIMEOUT_MS); ASSERT_TRUE(m_mockMessageRouterObserver->wasNotifiedOfStatusChange()); } + +/** + * Verify that when enable is called with active connection is pending + * we don't create a new connection. + */ +TEST_F(MessageRouterTest, test_enableTwiceOnPendingTransport) { + setupStateToPending(); + waitOnMessageRouter(SHORT_TIMEOUT_MS); + m_mockMessageRouterObserver->reset(); + + EXPECT_CALL(*m_mockTransport, connect()).Times(0); + + m_router->enable(); + + ASSERT_FALSE(m_mockMessageRouterObserver->wasNotifiedOfStatusChange()); +} + +/** + * Verify that if onConnected is called + * for inactive transport, we don't notify the observers and + * closing the connection. + */ +TEST_F(MessageRouterTest, test_onConnectedOnInactiveTransport) { + auto transport = std::make_shared(); + m_router->onConnected(transport); + ASSERT_FALSE(m_mockMessageRouterObserver->wasNotifiedOfStatusChange()); +} + } // namespace test } // namespace acl } // namespace alexaClientSDK diff --git a/ACL/test/Transport/MessageRouterTest.h b/ACL/test/Transport/MessageRouterTest.h index 91f564485d..950629a635 100644 --- a/ACL/test/Transport/MessageRouterTest.h +++ b/ACL/test/Transport/MessageRouterTest.h @@ -47,14 +47,9 @@ class TestableMessageRouter : public MessageRouter { TestableMessageRouter( std::shared_ptr authDelegate, std::shared_ptr attachmentManager, - std::shared_ptr transport, + std::shared_ptr factory, const std::string& avsEndpoint) : - MessageRouter(authDelegate, attachmentManager, avsEndpoint), - m_mockTransport{transport} { - } - - void setMockTransport(std::shared_ptr transport) { - m_mockTransport = transport; + MessageRouter(authDelegate, attachmentManager, factory, avsEndpoint) { } /** @@ -68,6 +63,16 @@ class TestableMessageRouter : public MessageRouter { auto status = future.wait_for(millisecondsToWait); return status == std::future_status::ready; } +}; + +class MockTransportFactory : public TransportFactoryInterface { +public: + MockTransportFactory(std::shared_ptr transport) : m_mockTransport{transport} { + } + + void setMockTransport(std::shared_ptr transport) { + m_mockTransport = transport; + } private: std::shared_ptr createTransport( @@ -91,10 +96,11 @@ class MessageRouterTest : public ::testing::Test { m_mockAuthDelegate{std::make_shared()}, m_attachmentManager{std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS)}, m_mockTransport{std::make_shared>()}, + m_transportFactory{std::make_shared(m_mockTransport)}, m_router{std::make_shared( m_mockAuthDelegate, m_attachmentManager, - m_mockTransport, + m_transportFactory, AVS_ENDPOINT)} { m_router->setObserver(m_mockMessageRouterObserver); } @@ -105,7 +111,7 @@ class MessageRouterTest : public ::testing::Test { } std::shared_ptr createMessageRequest() { - return std::make_shared(MESSAGE, nullptr); + return std::make_shared(MESSAGE); } void waitOnMessageRouter(std::chrono::milliseconds millisecondsToWait) { auto status = m_router->isExecutorReady(millisecondsToWait); @@ -133,6 +139,7 @@ class MessageRouterTest : public ::testing::Test { std::shared_ptr m_mockAuthDelegate; std::shared_ptr m_attachmentManager; std::shared_ptr> m_mockTransport; + std::shared_ptr m_transportFactory; std::shared_ptr m_router; // TestableMessageRouter m_router; }; diff --git a/ACL/test/Transport/MimeParserFuzzTest.cpp b/ACL/test/Transport/MimeParserFuzzTest.cpp deleted file mode 100644 index feefaaccab..0000000000 --- a/ACL/test/Transport/MimeParserFuzzTest.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/// @file MimeParserFuzzTest.cpp - -#include -#include -#include - -#include -#include - -#include "ACL/Transport/MessageConsumerInterface.h" -#include "ACL/Transport/HTTP2Stream.h" - -#include -#include -#include "AVSCommon/AVS/Attachment/InProcessAttachment.h" -#include -#include - -#include "Common/TestableAttachmentManager.h" -#include "Common/Common.h" -#include "Common/MimeUtils.h" -#include "Common/TestableMessageObserver.h" - -#include "MockMessageRequest.h" -#include "TestableConsumer.h" - -namespace alexaClientSDK { -namespace acl { -namespace test { - -using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::avs::attachment; - -/// String to identify log entries originating from this file. -static const std::string TAG("MimeParserFuzzTest"); - -/** - * Create a LogEntry using this file's TAG and the specified event string. - * - * @param The event string for this @c LogEntry. - */ -#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) - -/// The number of feed() calls to be made in @c MimeParserFuzzTest::feed() -static const int FEED_COUNT = 0x100; -/// Max pseudo-random buffer size to pass to @c feed() (large enough to assure we trigger attachment buffer full) -static const size_t MAX_FEED_SIZE = (InProcessAttachment::SDS_BUFFER_DEFAULT_SIZE_IN_BYTES / FEED_COUNT) * 4; -/// Max buffer size to read from the attachment reader (small enough trigger multiple reads per feed) -static const size_t MAX_READ_SIZE = MAX_FEED_SIZE / 8; -/// Consistent seed (for repeatable tests) with which to generate pseudo-random bytes. -static const unsigned int BYTES_SEED = 1; -/// Consistent seed (for repeatable tests) with which to generate pseudo-random feed sizes. -static const unsigned int FEED_SIZE_SEED = 2; -/// Consistent but different seed (for repeatable tests) with which to generate pseudo-random read sizes. -static const unsigned int READ_SIZE_SEED = FEED_SIZE_SEED + 1; -/// Content ID for mime part. -static const std::string CONTENT_ID = "CONTENT_ID"; -/// Context ID for generating attachment ID. -static const std::string CONTEXT_ID = "CONTEXT_ID"; -/// CR,LF to separate lines in mime headers and boundaries. -static const std::string CRLF = "\r\n"; -/// Dashes used as a prefix or suffix to mime boundaries. -static const std::string DASHES = "--"; -/// Mime Boundary string (without CR,LF or dashes). -static const std::string BOUNDARY = "BoundaryBoundaryBoundaryBoundaryBoundaryBoundaryBoundary"; -/// Mime prefix to our attachment. -static const std::string ATTACHMENT_PREFIX = CRLF + DASHES + BOUNDARY + CRLF + "Content-ID: " + CONTENT_ID + CRLF + - "Content-Type: application/octet-stream" + CRLF + CRLF; -/// Mime suffix terminating our attachment. -static const std::string ATTACHMENT_SUFFIX = CRLF + DASHES + BOUNDARY + DASHES; - -/** - * Class to generate consistent pseudo-random byte stream. - */ -class ByteGenerator { -public: - ByteGenerator(); - - /** - * Generate the next set of bytes in the stream. - * - * @param[out] buffer The buffer in which to place the generated bytes. - * @param size The number of bytes to generate. - */ - void generateBytes(std::vector* buffer, size_t size); - -private: - /// The underlying random byte generator. - std::independent_bits_engine m_engine; -}; - -ByteGenerator::ByteGenerator() { - m_engine.seed(BYTES_SEED); -} - -void ByteGenerator::generateBytes(std::vector* buffer, size_t size) { - for (size_t ix = 0; ix < size; ix++) { - (*buffer)[ix] = m_engine(); - } -} - -/** - * Our GTest class. - */ -class MimeParserFuzzTest : public ::testing::Test { -public: - /** - * Construct the objects we will use across tests. - */ - void SetUp() override; - - /** - * Feed a pseudo-random stream of bytes to the mime parser, making a number of - * calls with pseudo-random sizes. - */ - void feed(); - - /** - * Read the attachment the mime parser is rendering to, requesting a pseudo-random - * number of bytes with each read. - */ - void read(); - - /// The AttachmentManager. - std::shared_ptr m_attachmentManager; - /// The MimeParser which we will be primarily testing. - std::shared_ptr m_parser; - /// Flag to indicate the test has failed and loops should exit. - std::atomic m_failed; -}; - -void MimeParserFuzzTest::SetUp() { - m_attachmentManager = std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS); - auto testableConsumer = std::make_shared(); - testableConsumer->setMessageObserver(std::make_shared()); - m_parser = std::make_shared(testableConsumer, m_attachmentManager); - m_parser->setAttachmentContextId(CONTEXT_ID); - m_parser->setBoundaryString(BOUNDARY); - m_failed = false; -} - -void MimeParserFuzzTest::feed() { - m_parser->feed(const_cast(ATTACHMENT_PREFIX.c_str()), ATTACHMENT_PREFIX.size()); - - ByteGenerator feedBytesSource; - - std::default_random_engine feedSizeGenerator; - feedSizeGenerator.seed(FEED_SIZE_SEED); - std::uniform_int_distribution feedSizeDistribution(1, MAX_FEED_SIZE); - auto feedSizeSource = std::bind(feedSizeDistribution, feedSizeGenerator); - - std::vector feedBuffer(MAX_FEED_SIZE); - - size_t totalBytesFed = 0; - int incompleteCount = 0; - for (int ix = 0; !m_failed && ix < FEED_COUNT; ix++) { - auto feedSize = feedSizeSource(); - feedBytesSource.generateBytes(&feedBuffer, feedSize); - while (true) { - ACSDK_DEBUG9(LX("callingFeed").d("totalBytesFed", totalBytesFed).d("feedSize", feedSize)); - auto status = m_parser->feed((char*)(&feedBuffer[0]), feedSize); - if (MimeParser::DataParsedStatus::OK == status) { - totalBytesFed += feedSize; - break; - } else if (MimeParser::DataParsedStatus::INCOMPLETE == status) { - ACSDK_DEBUG9(LX("feedIncomplete").d("action", "waitAndReDrive")); - ++incompleteCount; - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } else { - ASSERT_NE(status, status); - } - } - } - - m_parser->feed(const_cast(ATTACHMENT_SUFFIX.c_str()), ATTACHMENT_SUFFIX.size()); - - // Make sure there were enough INCOMPLETE return statuses. - ACSDK_DEBUG9(LX("doneFeeding").d("incompleteCount", incompleteCount).d("FEED_COUNT", FEED_COUNT)); - ASSERT_GT(incompleteCount, FEED_COUNT); -} - -void MimeParserFuzzTest::read() { - ByteGenerator verifyBytesSource; - - std::default_random_engine readSizeGenerator; - readSizeGenerator.seed(READ_SIZE_SEED); - std::uniform_int_distribution readSizeDistribution(1, MAX_READ_SIZE); - auto readSizeSource = std::bind(readSizeDistribution, readSizeGenerator); - - auto attachmentId = m_attachmentManager->generateAttachmentId(CONTEXT_ID, CONTENT_ID); - auto reader = m_attachmentManager->createReader(attachmentId, avsCommon::utils::sds::ReaderPolicy::BLOCKING); - - std::vector readBuffer(MAX_READ_SIZE); - std::vector verifyBuffer(MAX_READ_SIZE); - size_t totalBytesRead = 0; - - while (true) { - // Delay reads so that AnotherMimeParserTest::feed() blocks on a full attachment buffer - // in a reliable way. This makes the test results consistent run to run. - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - - auto readSizeIn = readSizeSource(); - auto readStatus = AttachmentReader::ReadStatus::OK; - ACSDK_DEBUG9(LX("callingRead").d("totalBytesRead", totalBytesRead).d("readSizeIn", readSizeIn)); - auto readSizeOut = reader->read(readBuffer.data(), readSizeIn, &readStatus); - if (readSizeOut != 0) { - ACSDK_DEBUG9(LX("readReturned").d("readSizeOut", readSizeOut)); - verifyBytesSource.generateBytes(&verifyBuffer, readSizeOut); - for (size_t ix = 0; ix < readSizeOut; ix++) { - auto good = readBuffer[ix] == verifyBuffer[ix]; - if (!good) { - m_failed = true; - ASSERT_EQ(readBuffer[ix], verifyBuffer[ix]) << "UnexpectedByte at: " << totalBytesRead + ix; - } - } - totalBytesRead += readSizeOut; - } - - switch (readStatus) { - case AttachmentReader::ReadStatus::OK: - break; - - case AttachmentReader::ReadStatus::CLOSED: - ACSDK_DEBUG9(LX("readClosed")); - return; - - case AttachmentReader::ReadStatus::OK_WOULDBLOCK: - case AttachmentReader::ReadStatus::OK_TIMEDOUT: - case AttachmentReader::ReadStatus::ERROR_OVERRUN: - case AttachmentReader::ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE: - case AttachmentReader::ReadStatus::ERROR_INTERNAL: - ASSERT_NE(readStatus, readStatus); - break; - } - } -} - -/** - * Exercise MimeParser's re-drive, parsing, and attachment buffering by feeding in a large attachment - * consisting of pseudo-random bytes sent in pseudo-random sized chunks. At the same time, read the - * resulting attachment in pseudo-random sized requests. Expect that the data read matches the data - * that was fed. - */ -TEST_F(MimeParserFuzzTest, testRandomFeedAndReadSizesOfRandomData) { - auto readThread = std::thread(&MimeParserFuzzTest::read, this); - feed(); - readThread.join(); -} - -} // namespace test -} // namespace acl -} // namespace alexaClientSDK diff --git a/ACL/test/Transport/MimeParserTest.cpp b/ACL/test/Transport/MimeParserTest.cpp deleted file mode 100644 index 26d5e93435..0000000000 --- a/ACL/test/Transport/MimeParserTest.cpp +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/// @file MimeParserTest.cpp - -#include -#include - -#include - -#include "ACL/Transport/MessageConsumerInterface.h" -#include -#include -#include -#include - -#include "Common/TestableAttachmentManager.h" - -#include "Common/Common.h" -#include "Common/MimeUtils.h" -#include "Common/TestableMessageObserver.h" -#include "MockMessageRequest.h" -#include "TestableConsumer.h" - -namespace alexaClientSDK { -namespace acl { -namespace test { - -using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::avs::attachment; - -/// String to identify log entries originating from this file. -static const std::string TAG("TestableMessageObserver"); - -/** - * Create a LogEntry using this file's TAG and the specified event string. - * - * @param The event string for this @c LogEntry. - */ -#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) - -/// The size of the data for directive and attachments we will use. -static const int TEST_DATA_SIZE = 100; -/// The number of segments that the MIME string will be broken into during -/// simple testing. -static const int TEST_MULTI_WRITE_ITERATIONS = 4; -/// An upper bound that the feedParser logic may use to ensure we don't loop -/// infinitely. -static const int TEST_MULTI_MAX_ITERATIONS = 100; -/// A test context id. -static const std::string TEST_CONTEXT_ID = "TEST_CONTEXT_ID"; -/// A test content id. -static const std::string TEST_CONTENT_ID_01 = "TEST_CONTENT_ID_01"; -/// A test content id. -static const std::string TEST_CONTENT_ID_02 = "TEST_CONTENT_ID_02"; -/// A test content id. -static const std::string TEST_CONTENT_ID_03 = "TEST_CONTENT_ID_03"; -/// A test boundary string, copied from a real interaction with AVS. -static const std::string MIME_TEST_BOUNDARY_STRING = "84109348-943b-4446-85e6-e73eda9fac43"; -/// The newline characters that MIME parsers expect. -static const std::string MIME_NEWLINE = "\r\n"; -/// The double dashes which may occur before and after a boundary string. -static const std::string MIME_BOUNDARY_DASHES = "--"; -/// The test boundary string with the preceding dashes. -static const std::string BOUNDARY = MIME_BOUNDARY_DASHES + MIME_TEST_BOUNDARY_STRING; -/// A complete boundary, including the CRLF prefix -static const std::string BOUNDARY_LINE = MIME_NEWLINE + BOUNDARY; -/// Header line without prefix or suffix CRLF. -static const std::string HEADER_LINE = "Content-Type: application/json"; -/// JSON payload. -static const std::string TEST_MESSAGE = - "{\"directive\":{\"header\":{\"namespace\":\"SpeechRecognizer\",\"name\":" - "\"StopCapture\",\"messageId\":\"4e5612af-e05c-4611-8910-1e23f47ffb41\"}," - "\"payload\":{}}}"; - -// The following *_LINES definitions are raw mime text for various test parts. Each one assumes that -// it will be prefixed by a boundary and a CRLF. These get concatenated by constructTestMimeString() -// which provides an initiating boundary and CRLF, and which also inserts a CRLF between each part -// that is added. Leaving out the terminal CRLFs here allows constructTestMimeString() to append a -// pair of dashes to the boundary terminating the last part. Those final dashes are the standard -// syntax for the end of a sequence of mime parts. - -/// Normal section with header, test message and terminating boundary -/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. -static const std::string NORMAL_LINES = HEADER_LINE + MIME_NEWLINE + MIME_NEWLINE + TEST_MESSAGE + BOUNDARY_LINE; -/// Normal section preceded by a duplicate boundary (one CRLF between boundaries) -/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. -static const std::string DUPLICATE_BOUNDARY_LINES = BOUNDARY + MIME_NEWLINE + NORMAL_LINES; -/// Normal section preceded by a duplicate boundary and CRLF (two CRLFs between boundaries) -/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. -static const std::string CRLF_DUPLICATE_BOUNDARY_LINES = BOUNDARY_LINE + MIME_NEWLINE + NORMAL_LINES; -/// Normal section preceded by triplicate boundaries (one CRLF between boundaries) -/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. -static const std::string TRIPLICATE_BOUNDARY_LINES = BOUNDARY + MIME_NEWLINE + BOUNDARY + MIME_NEWLINE + NORMAL_LINES; -/// Normal section preceded by triplicate boundaries with trailing CRLF (two CRLFs between boundaries) -/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. -static const std::string CRLF_TRIPLICATE_BOUNDARY_LINES = - BOUNDARY_LINE + MIME_NEWLINE + BOUNDARY_LINE + MIME_NEWLINE + NORMAL_LINES; - -/** - * Our GTest class. - */ -class MimeParserTest : public ::testing::Test { -public: - /** - * Construct the objects we will use across tests. - */ - void SetUp() override { - m_attachmentManager = std::make_shared(); - - m_testableMessageObserver = std::make_shared(); - m_testableConsumer = std::make_shared(); - m_testableConsumer->setMessageObserver(m_testableMessageObserver); - - m_parser = std::make_shared(m_testableConsumer, m_attachmentManager); - m_parser->setAttachmentContextId(TEST_CONTEXT_ID); - m_parser->setBoundaryString(MIME_TEST_BOUNDARY_STRING); - } - - /** - * A utility function to feed data into our MimeParser object. A result of - * this function is that the MimeParser object will route Directives and - * Attachments to the appropriate objects as they are broken out of the - * aggregate MIME string. - * - * @param data The MIME string to be parsed. - * @param numberIterations The number of segments the MIME string is to be - * broken into, and then fed to the parser. - */ - void feedParser(const std::string& data, int numberIterations = 1) { - // Here we're simulating an ACL stream. We've got a mime string that we - // will feed to the mime parser in chunks. If any chunk fails (due to - // simulated attachment failing to write), we will re-drive it. - - int writeQuantum = data.length(); - if (numberIterations > 1) { - writeQuantum /= numberIterations; - } - - size_t numberBytesWritten = 0; - int iterations = 0; - while (numberBytesWritten < data.length() && iterations < TEST_MULTI_MAX_ITERATIONS) { - int bytesRemaining = data.length() - numberBytesWritten; - int bytesToFeed = bytesRemaining < writeQuantum ? bytesRemaining : writeQuantum; - - if (MimeParser::DataParsedStatus::OK == - m_parser->feed(const_cast(&(data.c_str()[numberBytesWritten])), bytesToFeed)) { - numberBytesWritten += bytesToFeed; - } - - iterations++; - } - } - - /** - * A utility function to validate that each MimePart we're tracking was - * received ok at its expected destination. - */ - void validateMimePartsParsedOk() { - for (auto mimePart : m_mimeParts) { - ASSERT_TRUE(mimePart->validateMimeParsing()); - } - } - - /// Our MimePart vector. - std::vector> m_mimeParts; - /// The AttachmentManager. - std::shared_ptr m_attachmentManager; - /// The ACL consumer object which the MimeParser requires. - std::shared_ptr m_testableConsumer; - /// An observer which will receive Directives. - std::shared_ptr m_testableMessageObserver; - /// The MimeParser which we will be primarily testing. - std::shared_ptr m_parser; -}; - -/** - * Test feeding a MIME string to the parser in a single pass which only contains - * a JSON message. - */ -TEST_F(MimeParserTest, testDirectiveReceivedSingleWrite) { - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - - auto mimeString = constructTestMimeString(m_mimeParts, MIME_TEST_BOUNDARY_STRING); - feedParser(mimeString); - - validateMimePartsParsedOk(); -} - -/** - * Test feeding a MIME string to the parser in multiple passes which only - * contains a JSON message. - */ -TEST_F(MimeParserTest, testDirectiveReceivedMultiWrite) { - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - - auto mimeString = constructTestMimeString(m_mimeParts, MIME_TEST_BOUNDARY_STRING); - feedParser(mimeString, TEST_MULTI_WRITE_ITERATIONS); - - validateMimePartsParsedOk(); -} - -/** - * Test feeding a MIME string to the parser in a single pass which only contains - * a binary attachment message. - */ -TEST_F(MimeParserTest, testAttachmentReceivedSingleWrite) { - m_mimeParts.push_back(std::make_shared( - MIME_TEST_BOUNDARY_STRING, TEST_CONTEXT_ID, TEST_CONTENT_ID_01, TEST_DATA_SIZE, m_attachmentManager)); - - auto mimeString = constructTestMimeString(m_mimeParts, MIME_TEST_BOUNDARY_STRING); - feedParser(mimeString); - - validateMimePartsParsedOk(); -} - -/** - * Test feeding a MIME string to the parser in multiple passes which only - * contains a binary attachment message. - */ -TEST_F(MimeParserTest, testAttachmentReceivedMultiWrite) { - m_mimeParts.push_back(std::make_shared( - MIME_TEST_BOUNDARY_STRING, TEST_CONTEXT_ID, TEST_CONTENT_ID_01, TEST_DATA_SIZE, m_attachmentManager)); - - auto mimeString = constructTestMimeString(m_mimeParts, MIME_TEST_BOUNDARY_STRING); - feedParser(mimeString, TEST_MULTI_WRITE_ITERATIONS); - - validateMimePartsParsedOk(); -} - -/** - * Test feeding a MIME string to the parser in a single pass which contains a - * JSON message followed by a binary attachment message. - */ -TEST_F(MimeParserTest, testDirectiveAndAttachmentReceivedSingleWrite) { - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - m_mimeParts.push_back(std::make_shared( - MIME_TEST_BOUNDARY_STRING, TEST_CONTEXT_ID, TEST_CONTENT_ID_01, TEST_DATA_SIZE, m_attachmentManager)); - - auto mimeString = constructTestMimeString(m_mimeParts, MIME_TEST_BOUNDARY_STRING); - feedParser(mimeString); - - validateMimePartsParsedOk(); -} - -/** - * Test feeding a MIME string to the parser in multiple passes which contains a - * JSON message followed by a binary attachment message. - */ -TEST_F(MimeParserTest, testDirectiveAndAttachmentReceivedMultiWrite) { - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - m_mimeParts.push_back(std::make_shared( - MIME_TEST_BOUNDARY_STRING, TEST_CONTEXT_ID, TEST_CONTENT_ID_01, TEST_DATA_SIZE, m_attachmentManager)); - - auto mimeString = constructTestMimeString(m_mimeParts, MIME_TEST_BOUNDARY_STRING); - feedParser(mimeString, TEST_MULTI_WRITE_ITERATIONS); - - validateMimePartsParsedOk(); -} - -/** - * Test feeding mime text including duplicate boundaries that we want to just skip over. - */ -TEST_F(MimeParserTest, testDuplicateBounaries) { - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - m_mimeParts.push_back(std::make_shared(NORMAL_LINES, TEST_MESSAGE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(DUPLICATE_BOUNDARY_LINES, TEST_MESSAGE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(CRLF_DUPLICATE_BOUNDARY_LINES, TEST_MESSAGE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(TRIPLICATE_BOUNDARY_LINES, TEST_MESSAGE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(CRLF_TRIPLICATE_BOUNDARY_LINES, TEST_MESSAGE, m_testableMessageObserver)); - m_mimeParts.push_back( - std::make_shared(MIME_TEST_BOUNDARY_STRING, TEST_DATA_SIZE, m_testableMessageObserver)); - - auto mimeString = constructTestMimeString(m_mimeParts, MIME_TEST_BOUNDARY_STRING); - ACSDK_INFO(LX("testDuplicateBoundaries").d("mimeString", mimeString)); - feedParser(mimeString, TEST_MULTI_WRITE_ITERATIONS); - - validateMimePartsParsedOk(); -} - -} // namespace test -} // namespace acl -} // namespace alexaClientSDK - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - -// ACSDK-1051 - MimeParser tests with attachments fail on Windows -#if defined(_WIN32) && !defined(RESOLVED_ACSDK_1051) - ::testing::GTEST_FLAG(filter) = "-MimeParserTest*Attachment*"; -#endif - return RUN_ALL_TESTS(); -} diff --git a/ACL/test/Transport/MockAuthDelegate.h b/ACL/test/Transport/MockAuthDelegate.h index ca1f6817b3..95737dc0e7 100644 --- a/ACL/test/Transport/MockAuthDelegate.h +++ b/ACL/test/Transport/MockAuthDelegate.h @@ -31,11 +31,32 @@ namespace test { class MockAuthDelegate : public avsCommon::sdkInterfaces::AuthDelegateInterface { public: + /// @name AuthDelegateInterface methods + /// @{ MOCK_METHOD1(addAuthObserver, void(std::shared_ptr)); MOCK_METHOD1(removeAuthObserver, void(std::shared_ptr)); - MOCK_METHOD0(getAuthToken, std::string()); + std::string getAuthToken(); + MOCK_METHOD1(onAuthFailure, void(const std::string& token)); + /// @} + + /* + * Set the token string. + * @param authToken The string to be returned when @c getAuthToken() is called. + */ + void setAuthToken(std::string authToken); + +private: + /// Holds the token string to be returned when @c getAuthToken() is called. + std::string m_authToken; }; +std::string MockAuthDelegate::getAuthToken() { + return m_authToken; +} +void MockAuthDelegate::setAuthToken(std::string authToken) { + m_authToken = authToken; +} + } // namespace test } // namespace acl } // namespace alexaClientSDK diff --git a/ACL/test/Transport/MockHTTP2Connection.h b/ACL/test/Transport/MockHTTP2Connection.h new file mode 100644 index 0000000000..060f8183fc --- /dev/null +++ b/ACL/test/Transport/MockHTTP2Connection.h @@ -0,0 +1,272 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKHTTP2CONNECTION_H_ +#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKHTTP2CONNECTION_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "MockHTTP2Request.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { +namespace test { + +/** + * A mock class of @c HTTP2ConnectionInterface. + */ +class MockHTTP2Connection : public HTTP2ConnectionInterface { +public: + /** + * Constructor + * @param dURL The URL for downchannel requests. + * @param pingURL The URL for ping requests. + */ + MockHTTP2Connection(std::string dURL, std::string pingURL); + + ~MockHTTP2Connection() = default; + + /// @name HTTP2ConnectionInterface methods + /// @{ + std::shared_ptr createAndSendRequest(const HTTP2RequestConfig& config); + MOCK_METHOD0(disconnect, void()); + /// @} + + /** + * Check whether there are any HTTP requests sent. + * + * @return True if there are no HTTP requests sent by @c HTTP2Transport. + */ + bool isRequestQueueEmpty(); + + /** + * Wait for an HTTP request to be sent. + * + * @param timeout Wait timeout in milliseconds. + * @param requestNum The number of HTTP2 requests to wait for. + * @return A pointer to the request if there is one before the timeout occurs, otherwise nullptr + */ + std::shared_ptr waitForRequest(std::chrono::milliseconds timeout, unsigned requestNum = 1); + + /** + * Pop the latest HTTP2 Request from queue. + * + * @return The oldest HTTP2 Request in the queue. + */ + std::shared_ptr dequeRequest(); + + /** + * Set the Header content to be matched by @c waitForRequestWithHeader. + * + * @param matchString The contents of the header to match. + */ + void setWaitRequestHeader(const std::string& matchString); + + /** + * Wait for a request with a particular Header content. + * + * @param timeout The wait timeout in milliseconds + * @param matchString The contents of the header that it is waiting for + * @return True if a match occurred before timeout + */ + bool waitForRequestWithHeader(std::chrono::milliseconds timeout); + + /** + * Wait for a POST HTTP2request. + * + * @param timeout The wait timeout in milliseconds. + * @return A pointer to the POST request if there is one before the timeout occurs, otherwise nullptr + */ + std::shared_ptr waitForPostRequest(const std::chrono::milliseconds timeout); + + /** + * Wait for a Ping HTTP2request. + * + * @param timeout The wait timeout in milliseconds. + * @return A pointer to the Ping request if there is one before the timeout occurs, otherwise nullptr + */ + std::shared_ptr waitForPingRequest(const std::chrono::milliseconds timeout); + + /** + * Respond to downchannel requests with a response code and notify onResponseFinished() if needed. + * + * @param responseCode The HTTP response code to reply. + * @param sendResponseFinished True if needed to notify onResponseFinished(), otherwise false. + * @param timeout Timeout for wating for downchannel HTTP2 requests. + * @return True if a response has been sent to the downchannel request before timeout, otherwise false. + */ + bool respondToDownchannelRequests( + long responseCode, + bool sendResponseFinished, + const std::chrono::milliseconds timeout); + + /** + * Set the response code for POST requests that will be immediately replied when an HTTP POST request is sent. + * + * @param responseCode The HTTP response code to reply to the request. If set to @c + * HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED, a response code will not be sent. + */ + void setResponseToPOSTRequests(http::HTTPResponseCode responseCode); + + /** + * Retrieve the first HTTP2 request made on the downchannel. + * + * @return The first downchannel request or nullptr if none found + */ + std::shared_ptr getDownchannelRequest( + std::chrono::milliseconds timeout = std::chrono::milliseconds(0)); + + /** + * Check if a pause is received while sending data. + * + * @return True if a pause is received, false otherwise + */ + bool isPauseOnSendReceived(std::chrono::milliseconds timeout = std::chrono::milliseconds(0)); + + /* + * Get the number of POST requests in the queue. + * + * @return The number of POST requests in the queue. + */ + std::size_t getPostRequestsNum(); + + /* + * Get the number of HTTP2 requests in the queue. + * + * @return The number of HTTP2 requests in the queue. + */ + std::size_t getRequestsNum(); + + /* + * Get the number of downchannel requests in the queue. + * + * @return The number of downchannel requests in the queue. + */ + std::size_t getDownchannelRequestsNum(); + + /* + * Pop the oldest HTTP2 POST request from queue if it is not empty, otherwise wait for at most @c timeout for a + * request to be enqueued + * + * @return The oldest HTTP2 POST request in the queue or nullptr if the timeout expires + */ + std::shared_ptr dequePostRequest(const std::chrono::milliseconds timeout); + + /* + * Pop the oldest HTTP2 POST request from queue. + * + * @return The oldest HTTP2 POST request in the queue. + */ + std::shared_ptr dequePostRequest(); + + /* + * Pop the oldest HTTP2 Ping request from queue. + * + * @return The oldest HTTP2 Ping request in the queue. + */ + std::shared_ptr dequePingRequest(); + + /* + * Retrieve the maximum number of POST requests in the queue at any given time. + * + * @return The maximum number of POST requests in the queue at any given time. + */ + std::size_t getMaxPostRequestsEnqueud(); + +private: + /// Queue of HTTP2 requests from @c HTTP2Transport. Serialized by @c m_requestMutex. + std::deque> m_requestQueue; + + /// Serializes concurrent access to the m_requestQueue and m_isStopping members. + std::mutex m_requestMutex; + + /// Used to wake a thread that processes HTTP2Request in @c m_requestQueue. + std::condition_variable m_requestCv; + + /// A string that identifies the downchannel URL. + std::string m_downchannelURL; + + /// A string that identifies the ping URL. + std::string m_pingURL; + + /// Queue of HTTP2 requests that are only for the downchannel. Serialized by @c m_downchannelRequestMutex. + std::deque> m_downchannelRequestQueue; + + /// Serializes access to receiving downchannel requests. + std::mutex m_downchannelRequestMutex; + + // Used to wake a thread that is waiting for a downchannel request. + std::condition_variable m_downchannelRequestCv; + + /// Queue of HTTP2 POST requests. Serialized by @c m_postRequestMutex. + std::deque> m_postRequestQueue; + + /// Serializes access to receiving POST requests. + std::mutex m_postRequestMutex; + + /// Used to wake a thread that is waiting a POST HTTP2Request. + std::condition_variable m_requestPostCv; + + /// Queue of Ping requests. Serialized by @c m_pingRequestMutex. + std::deque> m_pingRequestQueue; + + /// Serializes access to receiving Ping requests. + std::mutex m_pingRequestMutex; + + // Used to wake a thread that is waiting for a Ping request. + std::condition_variable m_pingRequestCv; + + /// A string that contains the header to match in @c waitForRequestWithHeader(). + std::string m_headerMatch; + + /// Used to wake a thread that processes HTTP2Request with a header content matching @c m_headerMatch. + std::condition_variable m_requestHeaderCv; + + /// Indicator that a pause is received while doing @c HTTP2RequestSourceInterface::onSendData() + PromiseFuturePair m_receivedPauseOnSend; + + /// The response code to be replied for every POST request received. + http::HTTPResponseCode m_postResponseCode; + + /// The maximum number of POST requests in the queue at any given time. + std::size_t m_maxPostRequestsEnqueued; + + /// A constant to specify the buffer size to be used in reading HTTP2 data. + /// Set to 100 bytes for test purposes + static const unsigned READ_DATA_BUF_SIZE = 100; +}; + +} // namespace test +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKHTTP2CONNECTION_H_ diff --git a/ACL/test/Transport/MockHTTP2Request.h b/ACL/test/Transport/MockHTTP2Request.h new file mode 100644 index 0000000000..9cb74bb466 --- /dev/null +++ b/ACL/test/Transport/MockHTTP2Request.h @@ -0,0 +1,114 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKHTTP2REQUEST_H_ +#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKHTTP2REQUEST_H_ + +#include +#include + +#include +#include + +#include +#include +#include + +#include "MockMimeResponseSink.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { +namespace test { + +/** + * A mock class of @c HTTP2RequestInterface. + */ +class MockHTTP2Request : public HTTP2RequestInterface { +public: + MockHTTP2Request(const alexaClientSDK::avsCommon::utils::http2::HTTP2RequestConfig& config); + MOCK_METHOD0(cancel, bool()); + MOCK_CONST_METHOD0(getId, std::string()); + + /** + * Retrieve the URL for the request. + * + * @return The request URL. + */ + const std::string getUrl(); + + /** + * Retrieve the HTTP2 request source. + * + * @return The HTTP2 request source. + */ + std::shared_ptr getSource(); + + /** + * Retrieve the HTTP2 request sink. + * + * @return The HTTP2 request sink. + */ + std::shared_ptr getSink(); + + /** + * Retrieve the HTTP2 request type. + * + * @return The HTTP2 request type. + */ + HTTP2RequestType getRequestType(); + + /** + * Retrieve The MIME response sink that contains the parsed mime contents of the HTTP2 request. + * + * @return The pointer to the MIME response sink. + */ + std::shared_ptr getMimeResponseSink(); + + /** + * Retrieve The MIME response decoder of the HTTP2 request. + * + * @return The pointer to the MIME response decoder. + */ + std::shared_ptr getMimeDecoder(); + +private: + /// The URL to receive the request. + const std::string m_url; + + /// The object to provide the data for this HTTP2 POST request. + std::shared_ptr m_source; + + /// The object to receive the response to this HTTP2 request. + std::shared_ptr m_sink; + + /// The HTTP Request Type. + HTTP2RequestType m_type; + + /// The MIME response sink that contains the parsed mime contents of the HTTP2 request. + std::shared_ptr m_mimeResponseSink; + + /// An instance of the @c HTTP2MimeResponseDecoder which decodes the contents of an HTTP2 request. + std::shared_ptr m_mimeDecoder; +}; + +} // namespace test +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif /* ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKHTTP2REQUEST_H_ */ diff --git a/ACL/test/Transport/MockMessageConsumer.h b/ACL/test/Transport/MockMessageConsumer.h new file mode 100644 index 0000000000..b026fbc3f4 --- /dev/null +++ b/ACL/test/Transport/MockMessageConsumer.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKMESSAGECONSUMER_H_ +#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKMESSAGECONSUMER_H_ + +#include +#include + +#include + +namespace alexaClientSDK { +namespace acl { +namespace test { + +/** + * A mock class of @c MessageConsumerInterface. + */ +class MockMessageConsumer : public MessageConsumerInterface { +public: + MOCK_METHOD2(consumeMessage, void(const std::string& contextId, const std::string& message)); +}; + +} // namespace test +} // namespace acl +} // namespace alexaClientSDK + +#endif /* ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKMESSAGECONSUMER_H_ */ diff --git a/ACL/test/Transport/MockMessageRequest.h b/ACL/test/Transport/MockMessageRequest.h index 1c9b3a8920..1fa1f8223f 100644 --- a/ACL/test/Transport/MockMessageRequest.h +++ b/ACL/test/Transport/MockMessageRequest.h @@ -36,7 +36,7 @@ class MockMessageRequest : public avsCommon::avs::MessageRequest { /** * Constructor. */ - MockMessageRequest() : avsCommon::avs::MessageRequest{"", nullptr} { + MockMessageRequest() : avsCommon::avs::MessageRequest{""} { } MOCK_METHOD1(exceptionReceived, void(const std::string& exceptionMessage)); MOCK_METHOD1(sendCompleted, void(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status status)); diff --git a/ACL/test/Transport/MockMimeResponseSink.h b/ACL/test/Transport/MockMimeResponseSink.h new file mode 100644 index 0000000000..4cc2d276e9 --- /dev/null +++ b/ACL/test/Transport/MockMimeResponseSink.h @@ -0,0 +1,83 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKMIMERESPONSESINK_H_ +#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKMIMERESPONSESINK_H_ + +#include + +#include +#include + +#include + +namespace alexaClientSDK { +namespace acl { +namespace test { + +/** + * A mock class of @c HTTP2MimeResponseSinkInterface that stores the contents of parsed of a MIME message. + */ +class MockMimeResponseSink : public avsCommon::utils::http2::HTTP2MimeResponseSinkInterface { +public: + /** + * Constructor. + */ + MockMimeResponseSink(); + + /** + * Destructor. + */ + virtual ~MockMimeResponseSink() = default; + + /// @name HTTP2MimeResponseSinkInterface methods + /// @{ + bool onReceiveResponseCode(long responseCode) override; + bool onReceiveHeaderLine(const std::string& line) override; + bool onBeginMimePart(const std::multimap& headers) override; + avsCommon::utils::http2::HTTP2ReceiveDataStatus onReceiveMimeData(const char* bytes, size_t size) override; + bool onEndMimePart() override; + avsCommon::utils::http2::HTTP2ReceiveDataStatus onReceiveNonMimeData(const char* bytes, size_t size) override; + void onResponseFinished(avsCommon::utils::http2::HTTP2ResponseFinishedStatus status) override; + /// @} + + /** + * Get the contents of a MIME part. + * + * @param part The index of MIME part starting from 0. + * @return A buffer that contains the MIME part. + */ + std::vector getMimePart(unsigned part); + + /** + * Get the total number of MIME parts parsed. + * + * @return The number of MIME parts parsed. + */ + unsigned getCountOfMimeParts(); + +private: + /// A buffer that contains of all the contents of the MIME parts. + std::vector> m_mimeContents; + + /// A buffer that contains the current MIME part being parsed. + std::vector m_mimeCurrentContent; +}; + +} // namespace test +} // namespace acl +} // namespace alexaClientSDK + +#endif /* ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKMIMERESPONSESINK_H_ */ diff --git a/AuthDelegate/test/MockAuthObserver.h b/ACL/test/Transport/MockPostConnect.h similarity index 51% rename from AuthDelegate/test/MockAuthObserver.h rename to ACL/test/Transport/MockPostConnect.h index 4bf07c456f..75cea7360e 100644 --- a/AuthDelegate/test/MockAuthObserver.h +++ b/ACL/test/Transport/MockPostConnect.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,24 +13,29 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_AUTHDELEGATE_TEST_MOCKAUTHOBSERVER_H_ -#define ALEXA_CLIENT_SDK_AUTHDELEGATE_TEST_MOCKAUTHOBSERVER_H_ +#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKPOSTCONNECT_H_ +#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKPOSTCONNECT_H_ #include -#include +#include + +#include namespace alexaClientSDK { -namespace authDelegate { +namespace acl { namespace test { -/// Mock AuthObserverInterface class -class MockAuthObserver : public avsCommon::sdkInterfaces::AuthObserverInterface { +/** + * A mock class of @c PostConnectInterface. + */ +class MockPostConnect : public PostConnectInterface { public: - MOCK_METHOD2(onAuthStateChange, void(State newState, Error error)); + MOCK_METHOD1(doPostConnect, bool(std::shared_ptr transport)); + MOCK_METHOD0(onDisconnect, void()); }; } // namespace test -} // namespace authDelegate +} // namespace acl } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_AUTHDELEGATE_TEST_MOCKAUTHOBSERVER_H_ +#endif /* ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKPOSTCONNECT_H_ */ diff --git a/ACL/test/Transport/MockPostConnectFactory.h b/ACL/test/Transport/MockPostConnectFactory.h new file mode 100644 index 0000000000..70b51af568 --- /dev/null +++ b/ACL/test/Transport/MockPostConnectFactory.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKPOSTCONNECTFACTORY_H_ +#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKPOSTCONNECTFACTORY_H_ + +#include +#include + +#include + +namespace alexaClientSDK { +namespace acl { +namespace test { + +/** + * A mock class of @c PostConnectFactoryInterface. + */ +class MockPostConnectFactory : public PostConnectFactoryInterface { +public: + MOCK_METHOD0(createPostConnect, std::shared_ptr()); +}; + +} // namespace test +} // namespace acl +} // namespace alexaClientSDK + +#endif /* ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKPOSTCONNECTFACTORY_H_ */ diff --git a/ACL/test/Transport/MockTransportObserver.h b/ACL/test/Transport/MockTransportObserver.h index 3cafb0a0a8..e2f8c02a38 100644 --- a/ACL/test/Transport/MockTransportObserver.h +++ b/ACL/test/Transport/MockTransportObserver.h @@ -16,10 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKTRANSPORTOBSERVER_H_ #define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_MOCKTRANSPORTOBSERVER_H_ -#include "ACL/MessageRequest.h" -#include "ACL/Message.h" -#include "ACL/Values.h" -#include "ACL/Transport/TransportObserverInterface.h" +#include #include @@ -30,14 +27,15 @@ namespace acl { namespace transport { namespace test { -using ::testing::Return; - class MockTransportObserver : public TransportObserverInterface { public: - MOCK_METHOD0(onConnected, void()); - MOCK_METHOD1(onDisconnected, void(ConnectionChangedReason)); - MOCK_METHOD0(onServerSideDisconnect, void()); - MOCK_METHOD1(onMessageReceived, void(std::shared_ptr)); + MOCK_METHOD1(onConnected, void(std::shared_ptr transport)); + MOCK_METHOD2( + onDisconnected, + void( + std::shared_ptr transport, + avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason)); + MOCK_METHOD1(onServerSideDisconnect, void(std::shared_ptr transport)); }; } // namespace test diff --git a/ADSL/CMakeLists.txt b/ADSL/CMakeLists.txt index 48b7143f6d..725b0c65fd 100644 --- a/ADSL/CMakeLists.txt +++ b/ADSL/CMakeLists.txt @@ -4,4 +4,4 @@ project(ADSL LANGUAGES CXX) include(../build/BuildDefaults.cmake) add_subdirectory("src") -acsdk_add_test_subdirectory_if_allowed() +add_subdirectory("test") diff --git a/ADSL/include/ADSL/DirectiveProcessor.h b/ADSL/include/ADSL/DirectiveProcessor.h index 224723b3a3..dcd6c6c6eb 100644 --- a/ADSL/include/ADSL/DirectiveProcessor.h +++ b/ADSL/include/ADSL/DirectiveProcessor.h @@ -16,10 +16,13 @@ #ifndef ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVEPROCESSOR_H_ #define ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVEPROCESSOR_H_ +#include #include #include +#include #include #include +#include #include #include #include @@ -122,6 +125,9 @@ class DirectiveProcessor { */ using ProcessorHandle = unsigned int; + /// The type of the handling queue items. + using DirectiveAndPolicy = std::pair, avsCommon::avs::BlockingPolicy>; + /** * Implementation of @c DirectiveHandlerResultInterface that forwards the completion / failure status * to the @c DirectiveProcessor from which it originated. @@ -189,14 +195,14 @@ class DirectiveProcessor { bool processCancelingQueueLocked(std::unique_lock& lock); /** - * Process (handle) the next @c AVSDirective in @c m_handlingQueue. + * Process (handle) all the items in @c m_handlingQueue which are not blocked. * @note This method must only be called by threads that have acquired @c m_mutex. * * @param lock A @c unique_lock on m_mutex from the callers context, allowing this method to release * (and re-acquire) the lock around callbacks that need to be invoked. * @return Whether an @c AVSDirective from m_handlingQueue was processed. */ - bool handleDirectiveLocked(std::unique_lock& lock); + bool handleQueuedDirectivesLocked(std::unique_lock& lock); /** * Set the current @c dialogRequestId. This cancels the processing of any @c AVSDirectives with a non-empty @@ -223,6 +229,47 @@ class DirectiveProcessor { */ void queueAllDirectivesForCancellationLocked(); + /** + * Save a pointer to the given directive as a directive which is being handled. + * A pointer to the directive is saved per @c BlockingPolicy::Channel used by the policy. + * This pointer can be used later to indicate which @c BlockingPolicy::Channel + * is blocked by which @c AVSDirective. + * + * @param directive The @c AVSDirective being handled. + * @param policy The @c BlockingPolicy assiciated with the given directive. + */ + void setDirectiveBeingHandledLocked( + const std::shared_ptr& directive, + const avsCommon::avs::BlockingPolicy policy); + + /** + * Clear the pointer to the directive being handled. + * @note See the above @c saveDirectiveBeingHandledLocked comment + * for further explanation. + * + * @param policy The @c BlockingPolicy with which the saved directive is associated. + */ + void clearDirectiveBeingHandledLocked(const avsCommon::avs::BlockingPolicy policy); + + /** + * Clear the pointer to the directive being handled. + * + * @note See the above @c saveDirectiveBeingHandledLocked comment + * for further explanation. + * @param shouldClear Matcher to indicate which saved directive we should free. + * + * @return An @c std::set of the freed directives. + */ + std::set> clearDirectiveBeingHandledLocked( + std::function&)> shouldClear); + + /** + * Get the next unblocked @c DirectiveAndPolicy form the handling queue. + * + * @return An @c std::iterator to the next unblocked @c DirectiveAndPolicy. + */ + std::deque::iterator getNextUnblockedDirectiveLocked(); + /// Handle value identifying this instance. int m_handle; @@ -247,11 +294,12 @@ class DirectiveProcessor { /// The directive (if any) for which a preHandleDirective() call is in progress. std::shared_ptr m_directiveBeingPreHandled; - /// Queue of @c AVSDirectives waiting to be handled. - std::deque> m_handlingQueue; - - /// Whether @c handleDirective() has been called for the directive at the @c front() of @c m_handlingQueue. - bool m_isHandlingDirective; + /** + * Queue of @c AVSDirectives waiting to be handled. + * @Note: A queue per channel would be more efficient, but as we don't expect more than few + * directives on the queue, we prefer simplicity. + */ + std::deque m_handlingQueue; /// Condition variable used to wake @c processingLoop() when it is waiting. std::condition_variable m_wakeProcessingLoop; @@ -273,6 +321,15 @@ class DirectiveProcessor { /// Next available @c ProcessorHandle value. static ProcessorHandle m_nextProcessorHandle; + + /** + * The directives which are being handled on various channels. A directive is consider as 'being handled' + * while the @c handleDirective method of the directive handler is running, and, if the directive is blocking + * until directive handling has been completed. i.e. @c DirectiveHandlerResult::setCompleted + * or @c DirectiveHandlerResult::setFailed have been called. + */ + std::array, avsCommon::avs::BlockingPolicy::Medium::COUNT> + m_directivesBeingHandled; }; } // namespace adsl diff --git a/ADSL/include/ADSL/DirectiveRouter.h b/ADSL/include/ADSL/DirectiveRouter.h index 6d09aae16b..3f1c6b2c20 100644 --- a/ADSL/include/ADSL/DirectiveRouter.h +++ b/ADSL/include/ADSL/DirectiveRouter.h @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVEROUTER_H_ #define ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVEROUTER_H_ +#include #include #include @@ -63,15 +64,6 @@ class DirectiveRouter : public avsCommon::utils::RequiresShutdown { */ bool handleDirectiveImmediately(std::shared_ptr directive); - /** - * Check if the directive handler's blocking policy is HANDLE_IMMEDIATELY for this directive, if so invoke @c - * handleDirectiveImmediately() on the handler registered for the given @c AVSDirective. - * - * @param directive The directive to be handled immediately. - * @return Whether or not the handler was invoked. - */ - bool handleDirectiveWithPolicyHandleImmediately(std::shared_ptr directive); - /** * Invoke @c preHandleDirective() on the handler registered for the given @c AVSDirective. * @@ -88,14 +80,10 @@ class DirectiveRouter : public avsCommon::utils::RequiresShutdown { * Invoke @c handleDirective() on the handler registered for the given @c AVSDirective. * * @param directive The directive to be handled. - * @param[out] policyOut If this method returns @c true, @c policyOut is set to the @c BlockingPolicy value that - * was configured when @c handleDirective() was called. * @return @c true if the the registered handler returned @c true. @c false if there was no registered handler * or the registered handler returned @c false (indicating that the directive was not recognized. */ - bool handleDirective( - std::shared_ptr directive, - avsCommon::avs::BlockingPolicy* policyOut); + bool handleDirective(const std::shared_ptr& directive); /** * Invoke cancelDirective() on the handler registered for the given @c AVSDirective. @@ -105,6 +93,14 @@ class DirectiveRouter : public avsCommon::utils::RequiresShutdown { */ bool cancelDirective(std::shared_ptr directive); + /** + * Get the policy associated with the given directive. + * + * @param directive The directive for which the policy is required. + * @return The corresponding @c BlockingPolicy value for the directive. + */ + avsCommon::avs::BlockingPolicy getPolicy(const std::shared_ptr& directive); + private: void doShutdown() override; @@ -150,13 +146,24 @@ class DirectiveRouter : public avsCommon::utils::RequiresShutdown { }; /** - * Look up the @c HandlerAndPolicy value for the specified @c AVSDirective. + * Look up the configured @c HandlerAndPolicy value for the specified @c AVSDirective. * @note The calling thread must have already acquired @c m_mutex. * * @param directive The directive to look up a value for. * @return The corresponding @c HandlerAndPolicy value for the specified directive. */ - avsCommon::avs::HandlerAndPolicy getHandlerAndPolicyLocked(std::shared_ptr directive); + avsCommon::avs::HandlerAndPolicy getdHandlerAndPolicyLocked( + const std::shared_ptr& directive); + + /** + * Get the @c DirectiveHandler for this directive. + * @note The calling thread must have already acquired @c m_mutex. + * + * @param directive The @c AVSDirective for which we're looking for handler. + * @return The directive handler for success. @c nullptr in failure. + */ + std::shared_ptr getHandlerLocked( + std::shared_ptr directive); /** * Increment the reference count for the specified handler. diff --git a/ADSL/include/ADSL/MessageInterpreter.h b/ADSL/include/ADSL/MessageInterpreter.h index fb67ace53f..1873b91a04 100644 --- a/ADSL/include/ADSL/MessageInterpreter.h +++ b/ADSL/include/ADSL/MessageInterpreter.h @@ -48,20 +48,10 @@ class MessageInterpreter : public avsCommon::sdkInterfaces::MessageObserverInter void receive(const std::string& contextId, const std::string& message) override; private: - /** - * Logs an error and sends an exception to AVS. This signifies that an error occurred when attempting to - * parse a value with a particular @c key. - * - * @param key They key whose value could not be parsed. - * @param json The JSON message that was being parsed. - */ - void sendParseValueException(const std::string& key, const std::string& json); - /// Object that manages sending exceptions encountered messages to AVS. std::shared_ptr m_exceptionEncounteredSender; /// Object to which we will send @c AVSDirectives. std::shared_ptr m_directiveSequencer; - // The attachment manager. std::shared_ptr m_attachmentManager; }; diff --git a/ADSL/src/DirectiveProcessor.cpp b/ADSL/src/DirectiveProcessor.cpp index e5b8fc5a66..d6f956ccd9 100644 --- a/ADSL/src/DirectiveProcessor.cpp +++ b/ADSL/src/DirectiveProcessor.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -47,8 +47,7 @@ std::unordered_map Dir DirectiveProcessor::DirectiveProcessor(DirectiveRouter* directiveRouter) : m_directiveRouter{directiveRouter}, m_isShuttingDown{false}, - m_isEnabled{true}, - m_isHandlingDirective{false} { + m_isEnabled{true} { std::lock_guard lock(m_handleMapMutex); m_handle = ++m_nextProcessorHandle; m_handleMap[m_handle] = this; @@ -69,6 +68,7 @@ bool DirectiveProcessor::onDirective(std::shared_ptr directive) { ACSDK_ERROR(LX("onDirectiveFailed").d("action", "ignored").d("reason", "nullptrDirective")); return false; } + std::lock_guard onDirectiveLock(m_onDirectiveMutex); std::unique_lock lock(m_mutex); if (m_isShuttingDown || !m_isEnabled) { @@ -78,7 +78,22 @@ bool DirectiveProcessor::onDirective(std::shared_ptr directive) { .d("reason", m_isShuttingDown ? "shuttingDown" : "disabled")); return false; } - if (!directive->getDialogRequestId().empty() && directive->getDialogRequestId() != m_dialogRequestId) { + + // TODO: ACSDK-2218 + // This additional check is made as a temporary solution to fix the problem with InteractionModel.NewDialogRequest + // directives having a dialogRequestID in the payload even though it should be handled immediately. This additional + // checking should be removed when a new InteractionModel version has been implemented which fixes the problem in + // the directive. + bool bypassDialogRequestIDCheck = + directive->getName() == "NewDialogRequest" && directive->getNamespace() == "InteractionModel"; + + if (bypassDialogRequestIDCheck) { + ACSDK_INFO(LX("onDirective") + .d("action", "bypassingDialogRequestIdCheck") + .d("reason", "routinesDirectiveContainsDialogRequestId") + .d("directiveNamespace", "InteractionModel") + .d("directiveName", "NewDialogRequest")); + } else if (!directive->getDialogRequestId().empty() && directive->getDialogRequestId() != m_dialogRequestId) { ACSDK_INFO(LX("onDirective") .d("messageId", directive->getMessageId()) .d("action", "dropped") @@ -87,20 +102,31 @@ bool DirectiveProcessor::onDirective(std::shared_ptr directive) { .d("dialogRequestId", m_dialogRequestId)); return true; } + + auto policy = m_directiveRouter->getPolicy(directive); + m_directiveBeingPreHandled = directive; + lock.unlock(); - auto handled = m_directiveRouter->preHandleDirective( - directive, alexaClientSDK::avsCommon::utils::memory::make_unique(m_handle, directive)); + auto preHandled = m_directiveRouter->preHandleDirective( + directive, utils::memory::make_unique(m_handle, directive)); lock.lock(); - if (m_directiveBeingPreHandled) { - m_directiveBeingPreHandled.reset(); - if (handled) { - m_handlingQueue.push_back(directive); - m_wakeProcessingLoop.notify_one(); - } + + if (!m_directiveBeingPreHandled && preHandled) { + return true; } - return handled; + m_directiveBeingPreHandled.reset(); + + if (!preHandled) { + return false; + } + + auto item = std::make_pair(directive, policy); + m_handlingQueue.push_back(item); + m_wakeProcessingLoop.notify_one(); + + return true; } void DirectiveProcessor::shutdown() { @@ -184,6 +210,9 @@ void DirectiveProcessor::onHandlingFailed(std::shared_ptr directiv void DirectiveProcessor::removeDirectiveLocked(std::shared_ptr directive) { auto matches = [directive](std::shared_ptr item) { return item == directive; }; + auto handlingMatches = [directive](std::pair, BlockingPolicy> directiveAndPolicy) { + return directiveAndPolicy.first == directive; + }; m_cancelingQueue.erase( std::remove_if(m_cancelingQueue.begin(), m_cancelingQueue.end(), matches), m_cancelingQueue.end()); @@ -192,38 +221,146 @@ void DirectiveProcessor::removeDirectiveLocked(std::shared_ptr dir m_directiveBeingPreHandled.reset(); } - if (m_isHandlingDirective && !m_handlingQueue.empty() && matches(m_handlingQueue.front())) { - m_isHandlingDirective = false; - m_handlingQueue.pop_front(); + m_handlingQueue.erase( + std::remove_if(m_handlingQueue.begin(), m_handlingQueue.end(), handlingMatches), m_handlingQueue.end()); + + if (m_directivesBeingHandled[BlockingPolicy::Medium::AUDIO] && + matches(m_directivesBeingHandled[BlockingPolicy::Medium::AUDIO])) { + m_directivesBeingHandled[BlockingPolicy::Medium::AUDIO].reset(); } - m_handlingQueue.erase( - std::remove_if(m_handlingQueue.begin(), m_handlingQueue.end(), matches), m_handlingQueue.end()); + if (m_directivesBeingHandled[BlockingPolicy::Medium::VISUAL] && + matches(m_directivesBeingHandled[BlockingPolicy::Medium::VISUAL])) { + m_directivesBeingHandled[BlockingPolicy::Medium::VISUAL].reset(); + } if (!m_cancelingQueue.empty() || !m_handlingQueue.empty()) { m_wakeProcessingLoop.notify_one(); } } +void DirectiveProcessor::setDirectiveBeingHandledLocked( + const std::shared_ptr& directive, + const avsCommon::avs::BlockingPolicy policy) { + if (policy.getMediums()[BlockingPolicy::Medium::AUDIO]) { + m_directivesBeingHandled[BlockingPolicy::Medium::AUDIO] = directive; + } + + if (policy.getMediums()[BlockingPolicy::Medium::VISUAL]) { + m_directivesBeingHandled[BlockingPolicy::Medium::VISUAL] = directive; + } +} + +void DirectiveProcessor::clearDirectiveBeingHandledLocked(const avsCommon::avs::BlockingPolicy policy) { + if ((policy.getMediums()[BlockingPolicy::Medium::AUDIO]) && + m_directivesBeingHandled[BlockingPolicy::Medium::AUDIO]) { + m_directivesBeingHandled[BlockingPolicy::Medium::AUDIO].reset(); + } + + if ((policy.getMediums()[BlockingPolicy::Medium::VISUAL]) && + m_directivesBeingHandled[BlockingPolicy::Medium::VISUAL]) { + m_directivesBeingHandled[BlockingPolicy::Medium::VISUAL].reset(); + } +} + +std::set> DirectiveProcessor::clearDirectiveBeingHandledLocked( + std::function&)> shouldClear) { + std::set> freed; + + auto directive = m_directivesBeingHandled[BlockingPolicy::Medium::AUDIO]; + if (directive && shouldClear(directive)) { + freed.insert(directive); + m_directivesBeingHandled[BlockingPolicy::Medium::AUDIO].reset(); + } + + directive = m_directivesBeingHandled[BlockingPolicy::Medium::VISUAL]; + if (directive && shouldClear(directive)) { + freed.insert(directive); + m_directivesBeingHandled[BlockingPolicy::Medium::VISUAL].reset(); + } + + return freed; +} + +std::deque::iterator DirectiveProcessor::getNextUnblockedDirectiveLocked() { + // A medium is considered blocked if a previous blocking directive hasn't been completed yet. + std::array blockedMediums; + + // Mark mediums used by blocking directives being handled as blocked. + blockedMediums[BlockingPolicy::Medium::AUDIO] = + (m_directivesBeingHandled[BlockingPolicy::Medium::AUDIO] != nullptr); + blockedMediums[BlockingPolicy::Medium::VISUAL] = + (m_directivesBeingHandled[BlockingPolicy::Medium::VISUAL] != nullptr); + + for (auto it = m_handlingQueue.begin(); it != m_handlingQueue.end(); it++) { + auto policy = it->second; + bool currentUsingAudio = false; + bool currentUsingVisual = false; + + if (policy.getMediums()[BlockingPolicy::Medium::AUDIO]) { + currentUsingAudio = true; + } + + if (policy.getMediums()[BlockingPolicy::Medium::VISUAL]) { + currentUsingVisual = true; + } + + if ((currentUsingAudio && blockedMediums[BlockingPolicy::Medium::AUDIO]) || + (currentUsingVisual && blockedMediums[BlockingPolicy::Medium::VISUAL])) { + // if the current directive is blocking, block its Mediums. + if (it->second.isBlocking()) { + blockedMediums[BlockingPolicy::Medium::AUDIO] = + (blockedMediums[BlockingPolicy::Medium::AUDIO] || currentUsingAudio); + blockedMediums[BlockingPolicy::Medium::VISUAL] = + (blockedMediums[BlockingPolicy::Medium::VISUAL] || currentUsingVisual); + } + } else { + return it; + } + } + + return m_handlingQueue.end(); +} + void DirectiveProcessor::processingLoop() { - auto wake = [this]() { - return !m_cancelingQueue.empty() || (!m_handlingQueue.empty() && !m_isHandlingDirective) || m_isShuttingDown; + ACSDK_DEBUG9(LX("processingLoop")); + + auto haveUnblockedDirectivesToHandle = [this] { + return getNextUnblockedDirectiveLocked() != m_handlingQueue.end(); + }; + + auto wake = [this, haveUnblockedDirectivesToHandle]() { + return !m_cancelingQueue.empty() || (!m_handlingQueue.empty() && haveUnblockedDirectivesToHandle()) || + m_isShuttingDown; }; - while (true) { - std::unique_lock lock(m_mutex); + std::unique_lock lock(m_mutex); + do { m_wakeProcessingLoop.wait(lock, wake); - if (!processCancelingQueueLocked(lock) && !handleDirectiveLocked(lock) && m_isShuttingDown) { - break; - } - } + + bool cancelHandled = false; + bool queuedHandled = false; + + /* + * Loop to make sure all waiting directives have been + * handled before exiting thread, in case m_isShuttingDown is true. + */ + do { + cancelHandled = processCancelingQueueLocked(lock); + queuedHandled = handleQueuedDirectivesLocked(lock); + } while (cancelHandled || queuedHandled); + + } while (!m_isShuttingDown); } bool DirectiveProcessor::processCancelingQueueLocked(std::unique_lock& lock) { + ACSDK_DEBUG9(LX("processCancelingQueueLocked").d("size", m_cancelingQueue.size())); + if (m_cancelingQueue.empty()) { return false; } - std::deque> temp(std::move(m_cancelingQueue)); + std::deque> temp; + std::swap(m_cancelingQueue, temp); lock.unlock(); for (auto directive : temp) { m_directiveRouter->cancelDirective(directive); @@ -232,34 +369,51 @@ bool DirectiveProcessor::processCancelingQueueLocked(std::unique_lock& lock) { +bool DirectiveProcessor::handleQueuedDirectivesLocked(std::unique_lock& lock) { if (m_handlingQueue.empty()) { return false; } - if (m_isHandlingDirective) { - return true; - } - auto directive = m_handlingQueue.front(); - m_isHandlingDirective = true; - lock.unlock(); - auto policy = BlockingPolicy::NONE; - auto handled = m_directiveRouter->handleDirective(directive, &policy); - lock.lock(); - if (!handled || BlockingPolicy::BLOCKING != policy) { - m_isHandlingDirective = false; - if (!m_handlingQueue.empty() && m_handlingQueue.front() == directive) { - m_handlingQueue.pop_front(); - } else if (!handled) { - ACSDK_ERROR(LX("handlingDirectiveLockedFailed") - .d("expected", directive->getMessageId()) - .d("front", m_handlingQueue.empty() ? "(empty)" : m_handlingQueue.front()->getMessageId()) - .d("reason", "handlingQueueFrontChangedWithoutBeingHandled")); + bool handleDirectiveCalled = false; + + ACSDK_DEBUG9(LX("handleQueuedDirectivesLocked").d("queue size", m_handlingQueue.size())); + + while (!m_handlingQueue.empty()) { + auto it = getNextUnblockedDirectiveLocked(); + + // All directives are blocked - exit loop. + if (it == m_handlingQueue.end()) { + ACSDK_DEBUG9(LX("handleQueuedDirectivesLocked").m("all queued directives are blocked")); + break; + } + auto directive = it->first; + auto policy = it->second; + + setDirectiveBeingHandledLocked(directive, policy); + m_handlingQueue.erase((it)); + + ACSDK_DEBUG9(LX("handleQueuedDirectivesLocked") + .d("proceeding with directive", directive->getMessageId()) + .d("policy", policy)); + + handleDirectiveCalled = true; + lock.unlock(); + + auto handleDirectiveSucceeded = m_directiveRouter->handleDirective(directive); + + lock.lock(); + + // if handle failed or directive is not blocking + if (!handleDirectiveSucceeded || !policy.isBlocking()) { + clearDirectiveBeingHandledLocked(policy); + } + + if (!handleDirectiveSucceeded) { + ACSDK_ERROR(LX("handleDirectiveFailed").d("message id", directive->getMessageId())); + scrubDialogRequestIdLocked(directive->getDialogRequestId()); } } - if (!handled) { - scrubDialogRequestIdLocked(directive->getDialogRequestId()); - } - return true; + + return handleDirectiveCalled; } void DirectiveProcessor::setDialogRequestIdLocked(const std::string& dialogRequestId) { @@ -294,26 +448,29 @@ void DirectiveProcessor::scrubDialogRequestIdLocked(const std::string& dialogReq } } - // If a mathcing directive in the midst of a handleDirective() call, reset m_isHandlingDirective - // so we won't block processing subsequent directives. This directive is already in m_handlingQueue - // and will be moved to m_cancelingQueue, below. - if (m_isHandlingDirective && !m_handlingQueue.empty()) { - auto id = m_handlingQueue.front()->getDialogRequestId(); - if (!id.empty() && id == dialogRequestId) { - m_isHandlingDirective = false; - changed = true; - } + /* + * If a matching directive is in the midst of a handleDirective() call or a blocking directive which + * hasn't been completed, reset m_directivesBeinHandle so we won't block processing subsequent directives. + * This directive is moved to m_cancelingQueue, below. + */ + auto freed = clearDirectiveBeingHandledLocked([dialogRequestId](const std::shared_ptr& directive) { + return directive->getDialogRequestId() == dialogRequestId; + }); + + if (!freed.empty()) { + m_cancelingQueue.insert(m_cancelingQueue.end(), freed.begin(), freed.end()); + changed = true; } // Filter matching directives from m_handlingQueue and put them in m_cancelingQueue. - std::deque> temp; - for (auto directive : m_handlingQueue) { - auto id = directive->getDialogRequestId(); + std::deque temp; + for (const auto& directiveAndPolicy : m_handlingQueue) { + auto id = (directiveAndPolicy.first)->getDialogRequestId(); if (!id.empty() && id == dialogRequestId) { - m_cancelingQueue.push_back(directive); + m_cancelingQueue.push_back(directiveAndPolicy.first); changed = true; } else { - temp.push_back(directive); + temp.push_back(directiveAndPolicy); } } std::swap(temp, m_handlingQueue); @@ -325,22 +482,46 @@ void DirectiveProcessor::scrubDialogRequestIdLocked(const std::string& dialogReq // If there were any changes, wake up the processing loop. if (changed) { + ACSDK_DEBUG9(LX("notifyingProcessingLoop").d("size:", m_cancelingQueue.size())); m_wakeProcessingLoop.notify_one(); } } void DirectiveProcessor::queueAllDirectivesForCancellationLocked() { + ACSDK_DEBUG9(LX("queueAllDirectivesForCancellationLocked")); + + bool changed = false; + m_dialogRequestId.clear(); - if (m_directiveBeingPreHandled) { - m_handlingQueue.push_back(m_directiveBeingPreHandled); - m_directiveBeingPreHandled.reset(); + + auto freed = clearDirectiveBeingHandledLocked([](const std::shared_ptr& directive) { return true; }); + + if (!freed.empty()) { + changed = true; + m_cancelingQueue.insert(m_cancelingQueue.end(), freed.begin(), freed.end()); } + if (!m_handlingQueue.empty()) { - m_cancelingQueue.insert(m_cancelingQueue.end(), m_handlingQueue.begin(), m_handlingQueue.end()); + std::transform( + m_handlingQueue.begin(), + m_handlingQueue.end(), + std::back_inserter(m_cancelingQueue), + [](DirectiveAndPolicy directiveAndPolicy) { return directiveAndPolicy.first; }); + m_handlingQueue.clear(); + changed = true; + } + + if (m_directiveBeingPreHandled) { + m_cancelingQueue.push_back(m_directiveBeingPreHandled); + m_directiveBeingPreHandled.reset(); + } + + if (changed) { + ACSDK_DEBUG9(LX("notifyingProcessingLoop")); + m_wakeProcessingLoop.notify_one(); } - m_isHandlingDirective = false; } } // namespace adsl diff --git a/ADSL/src/DirectiveRouter.cpp b/ADSL/src/DirectiveRouter.cpp index ea63126d29..3d745ed2ec 100644 --- a/ADSL/src/DirectiveRouter.cpp +++ b/ADSL/src/DirectiveRouter.cpp @@ -57,7 +57,7 @@ bool DirectiveRouter::addDirectiveHandler(std::shared_ptrgetConfiguration(); for (auto item : configuration) { - if (BlockingPolicy::NONE == item.second) { + if (!(item.second.isValid())) { ACSDK_ERROR(LX("addDirectiveHandlersFailed").d("reason", "nonePolicy")); } auto it = m_configuration.find(item.first); @@ -143,7 +143,7 @@ bool DirectiveRouter::removeDirectiveHandler(std::shared_ptr directive) { std::unique_lock lock(m_mutex); - auto handlerAndPolicy = getHandlerAndPolicyLocked(directive); + auto handlerAndPolicy = getdHandlerAndPolicyLocked(directive); if (!handlerAndPolicy) { ACSDK_WARN(LX("handleDirectiveImmediatelyFailed") .d("messageId", directive->getMessageId()) @@ -156,67 +156,37 @@ bool DirectiveRouter::handleDirectiveImmediately(std::shared_ptr directive) { - std::unique_lock lock(m_mutex); - auto handlerAndPolicy = getHandlerAndPolicyLocked(directive); - if (!handlerAndPolicy) { - ACSDK_WARN(LX("handleDirectiveWithPolicyHandleImmediatelyFailed") - .d("messageId", directive->getMessageId()) - .d("reason", "noHandlerRegistered")); - return false; - } - if (BlockingPolicy::HANDLE_IMMEDIATELY != handlerAndPolicy.policy) { - return false; - } - ACSDK_INFO(LX("handleDirectiveWithPolicyHandleImmediately") - .d("messageId", directive->getMessageId()) - .d("action", "calling")); - HandlerCallScope scope(lock, this, handlerAndPolicy.handler); - handlerAndPolicy.handler->handleDirectiveImmediately(directive); - return true; -} - bool DirectiveRouter::preHandleDirective( std::shared_ptr directive, std::unique_ptr result) { std::unique_lock lock(m_mutex); - auto handlerAndPolicy = getHandlerAndPolicyLocked(directive); - if (!handlerAndPolicy) { + auto handler = getHandlerLocked(directive); + if (!handler) { ACSDK_WARN(LX("preHandleDirectiveFailed") .d("messageId", directive->getMessageId()) .d("reason", "noHandlerRegistered")); return false; } ACSDK_INFO(LX("preHandleDirective").d("messageId", directive->getMessageId()).d("action", "calling")); - HandlerCallScope scope(lock, this, handlerAndPolicy.handler); - handlerAndPolicy.handler->preHandleDirective(directive, std::move(result)); + HandlerCallScope scope(lock, this, handler); + handler->preHandleDirective(directive, std::move(result)); return true; } -bool DirectiveRouter::handleDirective( - std::shared_ptr directive, - BlockingPolicy* policyOut) { - if (!policyOut) { - ACSDK_ERROR( - LX("handleDirectiveFailed").d("messageId", directive->getMessageId()).d("reason", "nullptrPolicyOut")); - return false; - } +bool DirectiveRouter::handleDirective(const std::shared_ptr& directive) { std::unique_lock lock(m_mutex); - auto handlerAndPolicy = getHandlerAndPolicyLocked(directive); - if (!handlerAndPolicy) { + auto handler = getHandlerLocked(directive); + if (!handler) { ACSDK_WARN( LX("handleDirectiveFailed").d("messageId", directive->getMessageId()).d("reason", "noHandlerRegistered")); return false; } ACSDK_INFO(LX("handleDirective").d("messageId", directive->getMessageId()).d("action", "calling")); - HandlerCallScope scope(lock, this, handlerAndPolicy.handler); - auto result = handlerAndPolicy.handler->handleDirective(directive->getMessageId()); - if (result) { - *policyOut = handlerAndPolicy.policy; - } else { + HandlerCallScope scope(lock, this, handler); + auto result = handler->handleDirective(directive->getMessageId()); + if (!result) { ACSDK_WARN(LX("messageIdNotRecognized") - .d("handler", handlerAndPolicy.handler.get()) + .d("handler", handler.get()) .d("messageId", directive->getMessageId()) .d("reason", "handleDirectiveReturnedFalse")); } @@ -225,15 +195,15 @@ bool DirectiveRouter::handleDirective( bool DirectiveRouter::cancelDirective(std::shared_ptr directive) { std::unique_lock lock(m_mutex); - auto handlerAndPolicy = getHandlerAndPolicyLocked(directive); - if (!handlerAndPolicy) { + auto handler = getHandlerLocked(directive); + if (!handler) { ACSDK_WARN( LX("cancelDirectiveFailed").d("messageId", directive->getMessageId()).d("reason", "noHandlerRegistered")); return false; } ACSDK_INFO(LX("cancelDirective").d("messageId", directive->getMessageId()).d("action", "calling")); - HandlerCallScope scope(lock, this, handlerAndPolicy.handler); - handlerAndPolicy.handler->cancelDirective(directive->getMessageId()); + HandlerCallScope scope(lock, this, handler); + handler->cancelDirective(directive->getMessageId()); return true; } @@ -280,18 +250,42 @@ DirectiveRouter::HandlerCallScope::~HandlerCallScope() { m_router->decrementHandlerReferenceCountLocked(m_lock, m_handler); } -HandlerAndPolicy DirectiveRouter::getHandlerAndPolicyLocked(std::shared_ptr directive) { +BlockingPolicy DirectiveRouter::getPolicy(const std::shared_ptr& directive) { + std::unique_lock lock(m_mutex); + + return getdHandlerAndPolicyLocked(directive).policy; +} + +HandlerAndPolicy DirectiveRouter::getdHandlerAndPolicyLocked(const std::shared_ptr& directive) { if (!directive) { - ACSDK_WARN(LX("getHandlerAndPolicyLockedFailed").d("reason", "nullptrDirective")); + ACSDK_ERROR(LX("getConfiguredHandlerAndPolicyLockedFailed").d("reason", "nullptrDirective")); return HandlerAndPolicy(); } + + // First, look for an exact match. If not found, then look for a wildcard handler for the AVS namespace. auto it = m_configuration.find(NamespaceAndName(directive->getNamespace(), directive->getName())); + if (m_configuration.end() == it) { + it = m_configuration.find(NamespaceAndName(directive->getNamespace(), "*")); + } + if (m_configuration.end() == it) { return HandlerAndPolicy(); } + return it->second; } +std::shared_ptr DirectiveRouter::getHandlerLocked(std::shared_ptr directive) { + auto handlerAndPolicy = getdHandlerAndPolicyLocked(directive); + if (!handlerAndPolicy) { + ACSDK_DEBUG0( + LX("noHandlerFoundForDirective").d("namespace", directive->getNamespace()).d("name", directive->getName())); + return nullptr; + } + + return handlerAndPolicy.handler; +} + void DirectiveRouter::incrementHandlerReferenceCountLocked(std::shared_ptr handler) { const auto it = m_handlerReferenceCounts.find(handler); if (it != m_handlerReferenceCounts.end()) { diff --git a/ADSL/src/DirectiveSequencer.cpp b/ADSL/src/DirectiveSequencer.cpp index d9e09f44f7..473c25a296 100644 --- a/ADSL/src/DirectiveSequencer.cpp +++ b/ADSL/src/DirectiveSequencer.cpp @@ -160,16 +160,10 @@ void DirectiveSequencer::receiveDirectiveLocked(std::unique_lock& lo if (directive->getDialogRequestId().empty()) { handled = m_directiveRouter.handleDirectiveImmediately(directive); } else { - handled = m_directiveRouter.handleDirectiveWithPolicyHandleImmediately(directive); - if (!handled) { - handled = m_directiveProcessor->onDirective(directive); - } - } -#else - handled = m_directiveRouter.handleDirectiveWithPolicyHandleImmediately(directive); - if (!handled) { handled = m_directiveProcessor->onDirective(directive); } +#else + handled = m_directiveProcessor->onDirective(directive); #endif if (!handled) { diff --git a/ADSL/src/MessageInterpreter.cpp b/ADSL/src/MessageInterpreter.cpp index b63cab2193..0179a88b52 100644 --- a/ADSL/src/MessageInterpreter.cpp +++ b/ADSL/src/MessageInterpreter.cpp @@ -15,10 +15,6 @@ #include "ADSL/MessageInterpreter.h" -#include -#include -#include -#include #include #include @@ -30,9 +26,7 @@ using namespace avsCommon; using namespace avsCommon::avs; using namespace avsCommon::avs::attachment; using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::utils::json::jsonUtils; using namespace avsCommon::utils; -using namespace rapidjson; /// String to identify log entries originating from this file. static const std::string TAG("MessageInterpreter"); @@ -44,36 +38,6 @@ static const std::string TAG("MessageInterpreter"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) -/// JSON key to get the directive object of a message. -static const std::string JSON_MESSAGE_DIRECTIVE_KEY = "directive"; -/// JSON key to get the header object of a message. -static const std::string JSON_MESSAGE_HEADER_KEY = "header"; -/// JSON key to get the namespace value of a header. -static const std::string JSON_MESSAGE_NAMESPACE_KEY = "namespace"; -/// JSON key to get the name value of a header. -static const std::string JSON_MESSAGE_NAME_KEY = "name"; -/// JSON key to get the messageId value of a header. -static const std::string JSON_MESSAGE_ID_KEY = "messageId"; -/// JSON key to get the dialogRequestId value of a header. -static const std::string JSON_MESSAGE_DIALOG_REQUEST_ID_KEY = "dialogRequestId"; -/// JSON key to get the payload object of a message. -static const std::string JSON_MESSAGE_PAYLOAD_KEY = "payload"; - -/** - * Utility function to handle sending exceptions encountered messages back to AVS. - * - * @param exceptionEncounteredSender The sender that can send exceptions encountered messages to AVS. - * @param unparsedMessage The JSON message sent from AVS which we were unable to process. - * @param errorDescription The description of the error encountered. - */ -static void sendExceptionEncounteredHelper( - std::shared_ptr exceptionEncounteredSender, - const std::string& unparsedMessage, - const std::string& errorDescription) { - exceptionEncounteredSender->sendExceptionEncountered( - unparsedMessage, ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED, errorDescription); -} - MessageInterpreter::MessageInterpreter( std::shared_ptr exceptionEncounteredSender, std::shared_ptr directiveSequencer, @@ -84,69 +48,19 @@ MessageInterpreter::MessageInterpreter( } void MessageInterpreter::receive(const std::string& contextId, const std::string& message) { - Document document; - - if (!parseJSON(message, &document)) { - const std::string error = "Parsing JSON Document failed"; - sendExceptionEncounteredHelper(m_exceptionEncounteredSender, message, error); - return; - } - - // Get iterator to child nodes - Value::ConstMemberIterator directiveIt; - if (!findNode(document, JSON_MESSAGE_DIRECTIVE_KEY, &directiveIt)) { - sendParseValueException(JSON_MESSAGE_DIRECTIVE_KEY, message); - return; - } - - Value::ConstMemberIterator headerIt; - if (!findNode(directiveIt->value, JSON_MESSAGE_HEADER_KEY, &headerIt)) { - sendParseValueException(JSON_MESSAGE_HEADER_KEY, message); - return; - } - - // Retrieve values - std::string payload; - if (!retrieveValue(directiveIt->value, JSON_MESSAGE_PAYLOAD_KEY, &payload)) { - sendParseValueException(JSON_MESSAGE_PAYLOAD_KEY, message); - return; - } - - std::string avsNamespace; - if (!retrieveValue(headerIt->value, JSON_MESSAGE_NAMESPACE_KEY, &avsNamespace)) { - sendParseValueException(JSON_MESSAGE_NAMESPACE_KEY, message); - return; - } - - std::string avsName; - if (!retrieveValue(headerIt->value, JSON_MESSAGE_NAME_KEY, &avsName)) { - sendParseValueException(JSON_MESSAGE_NAME_KEY, message); - return; - } - - std::string avsMessageId; - if (!retrieveValue(headerIt->value, JSON_MESSAGE_ID_KEY, &avsMessageId)) { - sendParseValueException(JSON_MESSAGE_ID_KEY, message); - return; - } - - // This is an optional header field - it's ok if it is not present. - // Avoid jsonUtils::retrieveValue because it logs a missing value as an ERROR. - std::string avsDialogRequestId; - auto it = headerIt->value.FindMember(JSON_MESSAGE_DIALOG_REQUEST_ID_KEY); - if (it != headerIt->value.MemberEnd()) { - convertToValue(it->value, &avsDialogRequestId); - } else { - ACSDK_DEBUG(LX("receive").d("messageId", avsMessageId).m("No dialogRequestId attached to message.")); - } - - auto avsMessageHeader = std::make_shared(avsNamespace, avsName, avsMessageId, avsDialogRequestId); - std::shared_ptr avsDirective = - AVSDirective::create(message, avsMessageHeader, payload, m_attachmentManager, contextId); + auto createResult = AVSDirective::create(message, m_attachmentManager, contextId); + std::shared_ptr avsDirective{std::move(createResult.first)}; if (!avsDirective) { - const std::string errorDescription = "AVSDirective is nullptr, failed to send to DirectiveSequencer"; - ACSDK_ERROR(LX("receiveFailed").d("reason", "createAvsDirectiveFailed")); - sendExceptionEncounteredHelper(m_exceptionEncounteredSender, message, errorDescription); + if (m_exceptionEncounteredSender) { + const std::string errorDescription = + "Unable to parse Directive - JSON error:" + avsDirectiveParseStatusToString(createResult.second); + ACSDK_ERROR(LX("receiveFailed").m(errorDescription)); + m_exceptionEncounteredSender->sendExceptionEncountered( + message, ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED, errorDescription); + } else { + ACSDK_ERROR(LX("receiveFailed").m("unable to send AVS Exception due to nullptr sender.")); + } + return; } @@ -157,11 +71,5 @@ void MessageInterpreter::receive(const std::string& contextId, const std::string m_directiveSequencer->onDirective(avsDirective); } -void MessageInterpreter::sendParseValueException(const std::string& key, const std::string& json) { - const std::string errorMessage = "reason=valueRetrievalFailed,key=" + key + ",payload=" + json; - ACSDK_ERROR(LX("messageParsingFailed").m(errorMessage)); - sendExceptionEncounteredHelper(m_exceptionEncounteredSender, json, errorMessage); -} - } // namespace adsl } // namespace alexaClientSDK diff --git a/ADSL/test/DirectiveProcessorTest.cpp b/ADSL/test/DirectiveProcessorTest.cpp index c09dcc2fbb..95b22d5b79 100644 --- a/ADSL/test/DirectiveProcessorTest.cpp +++ b/ADSL/test/DirectiveProcessorTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -45,6 +45,12 @@ static const std::string MESSAGE_ID_0_0("Message_0_0"); /// Generic MessageId used for test. static const std::string MESSAGE_ID_0_1("Message_0_1"); +/// Generic MessageId used for test. +static const std::string MESSAGE_ID_0_2("Message_0_2"); + +/// Generic MessageId used for test. +static const std::string MESSAGE_ID_0_3("Message_0_3"); + /// Generic MessageId used for tests. static const std::string MESSAGE_ID_1_0("Message_1_0"); @@ -72,6 +78,12 @@ static const std::string NAME_0("name_0"); /// A generic name string for tests. static const std::string NAME_1("name_1"); +/// A generic name string for tests. +static const std::string NAME_2("name_2"); + +/// A generic name string for tests. +static const std::string NAME_3("name_3"); + static const std::string TEST_ATTACHMENT_CONTEXT_ID("TEST_ATTACHMENT_CONTEXT_ID"); /// Namespace and name combination for tests. @@ -80,9 +92,43 @@ static const std::string TEST_ATTACHMENT_CONTEXT_ID("TEST_ATTACHMENT_CONTEXT_ID" /// Namespace and name combination (changing name this time) for tests. #define NAMESPACE_AND_NAME_0_1 NAMESPACE_0, NAME_1 +/// Namespace and name combination (changing name this time) for tests. +#define NAMESPACE_AND_NAME_0_2 NAMESPACE_0, NAME_2 + +/// Namespace and name combination (changing name this time) for tests. +#define NAMESPACE_AND_NAME_0_3 NAMESPACE_0, NAME_3 + /// Namespace and name combination (changing namespace this time) for tests. #define NAMESPACE_AND_NAME_1_0 NAMESPACE_1, NAME_0 +/// An InteractionModel.NewDialogRequest v1.0 directive. The v1.0 directive has a dialogRequestID in the header and +/// payload. +// clang-format off +static const std::string NEW_DIALOG_REQUEST_DIRECTIVE_V0 = R"delim( +{ + "directive": { + "header": { + "namespace": "InteractionModel", + "name": "NewDialogRequest", + "messageId": "2120215c-d803-4800-8773-e9505d16354a", + "dialogRequestId": ")delim" + DIALOG_REQUEST_ID_0 + R"delim(" + }, + "payload": { + "dialogRequestId": ")delim" + DIALOG_REQUEST_ID_0 + R"delim(" + } + } +})delim"; +// clang-format on + +/// A null context ID string +static const std::string NO_CONTEXT = ""; + +/// The NewDialogRequest directive signature. +static const NamespaceAndName NEW_DIALOG_REQUEST_SIGNATURE{"InteractionModel", "NewDialogRequest"}; + +/// Test timeout. +static const auto TEST_TIMEOUT = std::chrono::seconds(5); + /** * DirectiveRouterTest */ @@ -105,6 +151,12 @@ class DirectiveProcessorTest : public ::testing::Test { /// Generic @c AVSDirective for tests. std::shared_ptr m_directive_0_1; + /// Generic @c AVSDirective for tests. + std::shared_ptr m_directive_0_2; + + /// Generic @c AVSDirective for tests. + std::shared_ptr m_directive_0_3; + /// Generic @c AVSDirective for tests. std::shared_ptr m_directive_1_0; }; @@ -118,10 +170,22 @@ void DirectiveProcessorTest::SetUp() { std::make_shared(NAMESPACE_AND_NAME_0_0, MESSAGE_ID_0_0, DIALOG_REQUEST_ID_0); m_directive_0_0 = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader_0_0, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); + auto avsMessageHeader_0_1 = std::make_shared(NAMESPACE_AND_NAME_0_1, MESSAGE_ID_0_1, DIALOG_REQUEST_ID_0); m_directive_0_1 = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader_0_1, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); + + auto avsMessageHeader_0_2 = + std::make_shared(NAMESPACE_AND_NAME_0_2, MESSAGE_ID_0_2, DIALOG_REQUEST_ID_0); + m_directive_0_2 = AVSDirective::create( + UNPARSED_DIRECTIVE, avsMessageHeader_0_2, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); + + auto avsMessageHeader_0_3 = + std::make_shared(NAMESPACE_AND_NAME_0_3, MESSAGE_ID_0_3, DIALOG_REQUEST_ID_0); + m_directive_0_3 = AVSDirective::create( + UNPARSED_DIRECTIVE, avsMessageHeader_0_3, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); + auto avsMessageHeader_1_0 = std::make_shared(NAMESPACE_AND_NAME_1_0, MESSAGE_ID_1_0, DIALOG_REQUEST_ID_1); m_directive_1_0 = AVSDirective::create( @@ -131,7 +195,7 @@ void DirectiveProcessorTest::SetUp() { /** * Send a nullptr @c AVSDirective. Expect that it is ignored and a failure status (false) is returned. */ -TEST_F(DirectiveProcessorTest, testNullptrDirective) { +TEST_F(DirectiveProcessorTest, test_nullptrDirective) { ASSERT_FALSE(m_processor->onDirective(nullptr)); } @@ -141,9 +205,9 @@ TEST_F(DirectiveProcessorTest, testNullptrDirective) { * returns true (because the handler was registered) but that none of the handler methods are called * (because directives with the wrong @c dialogRequestID are dropped). */ -TEST_F(DirectiveProcessorTest, testWrongDialogRequestId) { +TEST_F(DirectiveProcessorTest, test_wrongDialogRequestId) { DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy::NON_BLOCKING; + handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); std::shared_ptr handler0 = MockDirectiveHandler::create(handler0Config); ASSERT_TRUE(m_router->addDirectiveHandler(handler0)); @@ -158,12 +222,12 @@ TEST_F(DirectiveProcessorTest, testWrongDialogRequestId) { } /** - * Register a @c NON_BLOCKING @c DirectiveHandler. Send an @c AVSDirective that matches the registered handler. + * Register an @c AUDIO_NON_BLOCKING @c DirectiveHandler. Send an @c AVSDirective that matches the registered handler. * Expect that @c preHandleDirective() and @c handleDirective() are called. */ -TEST_F(DirectiveProcessorTest, testSendNonBlocking) { +TEST_F(DirectiveProcessorTest, test_sendNonBlocking) { DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy::NON_BLOCKING; + handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); std::shared_ptr handler0 = MockDirectiveHandler::create(handler0Config); ASSERT_TRUE(m_router->addDirectiveHandler(handler0)); @@ -176,20 +240,20 @@ TEST_F(DirectiveProcessorTest, testSendNonBlocking) { m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0); ASSERT_TRUE(m_processor->onDirective(m_directive_0_0)); - ASSERT_TRUE(handler0->waitUntilCompleted()); + ASSERT_TRUE(handler0->waitUntilCompleted(std::chrono::milliseconds(1500000))); } /** * Test sending a blocking and then a non-blocking directive. Expect that @c preHandleDirective() and * @c handleDirective() is called for each. */ -TEST_F(DirectiveProcessorTest, testSendBlockingThenNonBlocking) { +TEST_F(DirectiveProcessorTest, test_sendBlockingThenNonBlocking) { DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy::BLOCKING; + handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUMS_AUDIO_AND_VISUAL, true); std::shared_ptr handler0 = MockDirectiveHandler::create(handler0Config); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); std::shared_ptr handler1 = MockDirectiveHandler::create(handler1Config); ASSERT_TRUE(m_router->addDirectiveHandler(handler0)); @@ -220,13 +284,13 @@ TEST_F(DirectiveProcessorTest, testSendBlockingThenNonBlocking) { * has been called, set the @c dialogRequestId and send an @c AVSDirective for which the other handler was * registered. Expect that the last directive is handled as well. */ -TEST_F(DirectiveProcessorTest, testOnUnregisteredDirective) { +TEST_F(DirectiveProcessorTest, test_onUnregisteredDirective) { DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); std::shared_ptr handler1 = MockDirectiveHandler::create(handler1Config); DirectiveHandlerConfiguration handler2Config; - handler2Config[{NAMESPACE_AND_NAME_1_0}] = BlockingPolicy::NON_BLOCKING; + handler2Config[{NAMESPACE_AND_NAME_1_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); std::shared_ptr handler2 = MockDirectiveHandler::create(handler2Config); ASSERT_TRUE(m_router->addDirectiveHandler(handler1)); @@ -262,18 +326,19 @@ TEST_F(DirectiveProcessorTest, testOnUnregisteredDirective) { * @c dialogRequestId(). Expect the first two @c AVSDirectives to be cancelled and expect the * final @c AVSDirective to be processed normally. */ -TEST_F(DirectiveProcessorTest, testSetDialogRequestIdCancelsOutstandingDirectives) { +TEST_F(DirectiveProcessorTest, test_setDialogRequestIdCancelsOutstandingDirectives) { DirectiveHandlerConfiguration longRunningHandlerConfig; - longRunningHandlerConfig[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy::BLOCKING; + longRunningHandlerConfig[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto longRunningHandler = MockDirectiveHandler::create(longRunningHandlerConfig, MockDirectiveHandler::DEFAULT_DONE_TIMEOUT_MS); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy::NON_BLOCKING; + auto audioNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); + handler1Config[{NAMESPACE_AND_NAME_0_1}] = audioNonBlockingPolicy; auto handler1 = MockDirectiveHandler::create(handler1Config); DirectiveHandlerConfiguration handler2Config; - handler2Config[{NAMESPACE_AND_NAME_1_0}] = BlockingPolicy::NON_BLOCKING; + handler2Config[{NAMESPACE_AND_NAME_1_0}] = audioNonBlockingPolicy; auto handler2 = MockDirectiveHandler::create(handler2Config); ASSERT_TRUE(m_router->addDirectiveHandler(longRunningHandler)); @@ -305,12 +370,12 @@ TEST_F(DirectiveProcessorTest, testSetDialogRequestIdCancelsOutstandingDirective ASSERT_TRUE(handler2->waitUntilCompleted()); } -TEST_F(DirectiveProcessorTest, testAddDirectiveWhileDisabled) { +TEST_F(DirectiveProcessorTest, test_addDirectiveWhileDisabled) { m_processor->disable(); ASSERT_FALSE(m_processor->onDirective(m_directive_0_0)); } -TEST_F(DirectiveProcessorTest, testAddDirectiveAfterReEnabled) { +TEST_F(DirectiveProcessorTest, test_addDirectiveAfterReEnabled) { m_processor->disable(); ASSERT_FALSE(m_processor->onDirective(m_directive_0_0)); @@ -318,6 +383,351 @@ TEST_F(DirectiveProcessorTest, testAddDirectiveAfterReEnabled) { ASSERT_TRUE(m_processor->onDirective(m_directive_0_0)); } +/** + * Verify that an @c AVSDirective using @c MEDIUMS_AUDIO_AND_VISUAL + * is blocking @c MEDIUM_AUDIO but not blocking @c MEDIUMS_NONE. + */ +TEST_F(DirectiveProcessorTest, test_audioAndVisualIsBlockingAudio) { + auto& audioAndVisualBlockingDirective = m_directive_0_0; + DirectiveHandlerConfiguration audioAndVisualBlockingHandlerConfig; + auto audioBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUMS_AUDIO_AND_VISUAL, true); + audioAndVisualBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_0}] = audioBlockingPolicy; + auto audioAndVisualBlockingHandler = MockDirectiveHandler::create(audioAndVisualBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(audioAndVisualBlockingHandler)); + + auto& audioNonBlockingDirective = m_directive_0_1; + DirectiveHandlerConfiguration audioNonBlockingHandlerConfig; + auto audioNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); + audioNonBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_1}] = audioNonBlockingPolicy; + auto audioNonBlockingHandler = MockDirectiveHandler::create(audioNonBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(audioNonBlockingHandler)); + + auto& noneMediumsNoBlockingDirective = m_directive_0_2; + DirectiveHandlerConfiguration handler3Config; + auto noneMediums = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); + handler3Config[{NAMESPACE_AND_NAME_0_2}] = noneMediums; + auto noneMediumsNonBlockingHandler = MockDirectiveHandler::create(handler3Config); + ASSERT_TRUE(m_router->addDirectiveHandler(noneMediumsNonBlockingHandler)); + + // On audioAndVisualBlockingDirective, expect preHandle and handle. Then cancel, as it wasn't completed. + EXPECT_CALL(*(audioAndVisualBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(audioAndVisualBlockingHandler.get()), preHandleDirective(audioAndVisualBlockingDirective, _)) + .Times(1); + EXPECT_CALL(*(audioAndVisualBlockingHandler.get()), handleDirective(MESSAGE_ID_0_0)) + .Times(1) + .WillOnce(Return(true)); + EXPECT_CALL(*(audioAndVisualBlockingHandler.get()), cancelDirective(_)).Times(1); + + // On audioNonBlockingDirective, expect only preHandle as it's blocked. + EXPECT_CALL(*(audioNonBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(audioNonBlockingHandler.get()), preHandleDirective(audioNonBlockingDirective, _)).Times(1); + EXPECT_CALL(*(audioNonBlockingHandler.get()), handleDirective(MESSAGE_ID_0_1)).Times(0); + EXPECT_CALL(*(audioNonBlockingHandler.get()), cancelDirective(_)).Times(1); + + // On noneMediumsNoBlockingDirective, expect completion. + EXPECT_CALL(*(noneMediumsNonBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(noneMediumsNonBlockingHandler.get()), preHandleDirective(noneMediumsNoBlockingDirective, _)).Times(1); + EXPECT_CALL(*(noneMediumsNonBlockingHandler.get()), handleDirective(MESSAGE_ID_0_2)).Times(1); + EXPECT_CALL(*(noneMediumsNonBlockingHandler.get()), cancelDirective(_)).Times(0); + + m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0); + + ASSERT_TRUE(m_processor->onDirective(audioAndVisualBlockingDirective)); + ASSERT_TRUE(m_processor->onDirective(audioNonBlockingDirective)); + ASSERT_TRUE(m_processor->onDirective(noneMediumsNoBlockingDirective)); + + noneMediumsNonBlockingHandler->waitUntilCompleted(); +} + +/** + * Verify that a blocking @c AVSDirective is not blocking + * a future @c AVSDirective on a different @c Medium. + */ +TEST_F(DirectiveProcessorTest, test_differentMediums) { + auto& audioBlockingDirective = m_directive_0_0; + DirectiveHandlerConfiguration audioBlockingHandlerConfig; + audioBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); + auto audioBlockingHandler = MockDirectiveHandler::create(audioBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(audioBlockingHandler)); + + auto& visualBlockingDirective = m_directive_0_1; + DirectiveHandlerConfiguration visualBlockingHandlerConfig; + visualBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy(BlockingPolicy::MEDIUM_VISUAL, true); + auto visualBlockingHandler = MockDirectiveHandler::create(visualBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(visualBlockingHandler)); + + // On audioBlockingDirective, expect preHandle and handle. Then cancel as it wasn't completed. + EXPECT_CALL(*(audioBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(audioBlockingHandler.get()), preHandleDirective(audioBlockingDirective, _)).Times(1); + EXPECT_CALL(*(audioBlockingHandler.get()), handleDirective(MESSAGE_ID_0_0)).Times(1).WillOnce(Return(true)); + EXPECT_CALL(*(audioBlockingHandler.get()), cancelDirective(_)).Times(1); + + // On visualBlockingDirective, expect completion. + EXPECT_CALL(*(visualBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(visualBlockingHandler.get()), preHandleDirective(visualBlockingDirective, _)).Times(1); + EXPECT_CALL(*(visualBlockingHandler.get()), handleDirective(MESSAGE_ID_0_1)).Times(1); + EXPECT_CALL(*(visualBlockingHandler.get()), cancelDirective(_)).Times(0); + + m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0); + + ASSERT_TRUE(m_processor->onDirective(audioBlockingDirective)); + ASSERT_TRUE(m_processor->onDirective(visualBlockingDirective)); + + visualBlockingHandler->waitUntilCompleted(); +} + +/** + * Verify that when @c MEDIUM_AUDIO and @c MEDIUM_VISUAL + * have been blocked by two different @c AVSDirectives, + * When one of the blocking has been completed, only its @c Medium is + * released. + */ +TEST_F(DirectiveProcessorTest, test_releaseOneMedium) { + // 4 directives: blocking audio, blocking visual, using audio and using visual + auto& audioBlockingDirective = m_directive_0_0; + DirectiveHandlerConfiguration audioBlockingHandlerConfig; + audioBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); + auto audioBlockingHandler = MockDirectiveHandler::create(audioBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(audioBlockingHandler)); + + auto& visualBlockingDirective = m_directive_0_1; + DirectiveHandlerConfiguration visualBlockingHandlerConfig; + visualBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy(BlockingPolicy::MEDIUM_VISUAL, true); + auto visualBlockingHandler = MockDirectiveHandler::create(visualBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(visualBlockingHandler)); + + auto& audioBlockingDirective2 = m_directive_0_2; + DirectiveHandlerConfiguration audioBlockingHandler2Config; + audioBlockingHandler2Config[{NAMESPACE_AND_NAME_0_2}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); + auto audioBlockingHandler2 = MockDirectiveHandler::create(audioBlockingHandler2Config); + ASSERT_TRUE(m_router->addDirectiveHandler(audioBlockingHandler2)); + + auto& visualBlockingDirective2 = m_directive_0_3; + DirectiveHandlerConfiguration visualBlockingHandler2Config; + visualBlockingHandler2Config[{NAMESPACE_AND_NAME_0_3}] = BlockingPolicy(BlockingPolicy::MEDIUM_VISUAL, true); + auto visualBlockingHandler2 = MockDirectiveHandler::create(visualBlockingHandler2Config); + ASSERT_TRUE(m_router->addDirectiveHandler(visualBlockingHandler2)); + + // On audioBlockingDirective, expect preHandle and handle. Then cancel as it wasn't completed. + EXPECT_CALL(*(audioBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(audioBlockingHandler.get()), preHandleDirective(audioBlockingDirective, _)).Times(1); + EXPECT_CALL(*(audioBlockingHandler.get()), handleDirective(MESSAGE_ID_0_0)).Times(1).WillOnce(Return(true)); + EXPECT_CALL(*(audioBlockingHandler.get()), cancelDirective(_)).Times(1); + + // On visualBlockingDirective, expect completion. + EXPECT_CALL(*(visualBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(visualBlockingHandler.get()), preHandleDirective(visualBlockingDirective, _)).Times(1); + EXPECT_CALL(*(visualBlockingHandler.get()), handleDirective(MESSAGE_ID_0_1)).Times(1); + EXPECT_CALL(*(visualBlockingHandler.get()), cancelDirective(_)).Times(0); + + // On audioBlockingDirective2, expect preHandle and handle. Then cancel as it wasn't completed because it's blocked. + EXPECT_CALL(*(audioBlockingHandler2.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(audioBlockingHandler2.get()), preHandleDirective(audioBlockingDirective2, _)).Times(1); + EXPECT_CALL(*(audioBlockingHandler2.get()), handleDirective(MESSAGE_ID_0_2)).Times(0); + EXPECT_CALL(*(audioBlockingHandler2.get()), cancelDirective(_)).Times(1); + + // On visualBlockingDirective2, expect completion. + EXPECT_CALL(*(visualBlockingHandler2.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(visualBlockingHandler2.get()), preHandleDirective(visualBlockingDirective2, _)).Times(1); + EXPECT_CALL(*(visualBlockingHandler2.get()), handleDirective(MESSAGE_ID_0_3)).Times(1); + EXPECT_CALL(*(visualBlockingHandler2.get()), cancelDirective(_)).Times(0); + + m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0); + + ASSERT_TRUE(m_processor->onDirective(audioBlockingDirective)); + ASSERT_TRUE(m_processor->onDirective(visualBlockingDirective)); + ASSERT_TRUE(m_processor->onDirective(audioBlockingDirective2)); + ASSERT_TRUE(m_processor->onDirective(visualBlockingDirective2)); + + visualBlockingHandler->waitUntilCompleted(); + visualBlockingHandler2->waitUntilCompleted(); +} + +/** + * Verify that a blocked directive with isBlocking=true on the queue is blocking + * subsequent directives using the same @c Medium. + */ +TEST_F(DirectiveProcessorTest, test_blockingQueuedDirectivIsBlocking) { + auto& audioBlockingDirective = m_directive_0_0; + DirectiveHandlerConfiguration audioBlockingHandlerConfig; + audioBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); + auto audioBlockingHandler = + MockDirectiveHandler::create(audioBlockingHandlerConfig, MockDirectiveHandler::DEFAULT_DONE_TIMEOUT_MS); + ASSERT_TRUE(m_router->addDirectiveHandler(audioBlockingHandler)); + + auto& audioAndVisualBlocking = m_directive_0_1; + DirectiveHandlerConfiguration audioAndVisualBlockingHandlerConfig; + audioAndVisualBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_1}] = + BlockingPolicy(BlockingPolicy::MEDIUMS_AUDIO_AND_VISUAL, true); + auto audioAndVisualBlockingHandler = MockDirectiveHandler::create(audioAndVisualBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(audioAndVisualBlockingHandler)); + + auto& visualBlockingDirective = m_directive_0_2; + DirectiveHandlerConfiguration visualBlockingHandlerConfig; + visualBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_2}] = BlockingPolicy(BlockingPolicy::MEDIUM_VISUAL, true); + auto visualBlockingHandler = MockDirectiveHandler::create(visualBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(visualBlockingHandler)); + + /* + * create a none mediums directive to make sure visualBlockingDirective was waiting in the + * queue after audioAndVisualBlocking + */ + auto& noneMediumsDirective = m_directive_0_3; + DirectiveHandlerConfiguration noneMediumsHandlerConfig; + noneMediumsHandlerConfig[{NAMESPACE_AND_NAME_0_3}] = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); + auto noneMediumsHandler = MockDirectiveHandler::create(noneMediumsHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(noneMediumsHandler)); + + EXPECT_CALL(*(audioBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(audioBlockingHandler.get()), preHandleDirective(audioBlockingDirective, _)).Times(1); + EXPECT_CALL(*(audioBlockingHandler.get()), cancelDirective(_)).Times(0); + + EXPECT_CALL(*(audioAndVisualBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(audioAndVisualBlockingHandler.get()), preHandleDirective(audioAndVisualBlocking, _)).Times(1); + EXPECT_CALL(*(audioAndVisualBlockingHandler.get()), cancelDirective(_)).Times(0); + + EXPECT_CALL(*(visualBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(visualBlockingHandler.get()), preHandleDirective(visualBlockingDirective, _)).Times(1); + EXPECT_CALL(*(visualBlockingHandler.get()), cancelDirective(_)).Times(0); + + EXPECT_CALL(*(noneMediumsHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(noneMediumsHandler.get()), preHandleDirective(noneMediumsDirective, _)).Times(1); + EXPECT_CALL(*(noneMediumsHandler.get()), cancelDirective(_)).Times(0); + + /* + * Expect the directives to be handles in the following order: + * 1. audioBlockingDirective - arrived first + * 2. noneMediumsHandler - not bloecked by any of the others. + * 3. audioAndVisualBlocking - waiting for audioBlockingDirective to complete. + * 4. visualBlockingHandler - waiting for audioAndVisualBlocking to complete + */ + ::testing::Sequence s1; + EXPECT_CALL(*(audioBlockingHandler.get()), handleDirective(MESSAGE_ID_0_0)).Times(1).InSequence(s1); + EXPECT_CALL(*(noneMediumsHandler.get()), handleDirective(MESSAGE_ID_0_3)).Times(1).InSequence(s1); + EXPECT_CALL(*(audioAndVisualBlockingHandler.get()), handleDirective(MESSAGE_ID_0_1)).Times(1).InSequence(s1); + EXPECT_CALL(*(visualBlockingHandler.get()), handleDirective(MESSAGE_ID_0_2)).Times(1).InSequence(s1); + + m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0); + + ASSERT_TRUE(m_processor->onDirective(audioBlockingDirective)); + audioBlockingHandler->waitUntilHandling(); + + ASSERT_TRUE(m_processor->onDirective(audioAndVisualBlocking)); + audioAndVisualBlockingHandler->waitUntilPreHandling(); + + ASSERT_TRUE(m_processor->onDirective(visualBlockingDirective)); + visualBlockingHandler->waitUntilPreHandling(); + + // wait until noneMediumsDirective is handled to make sure we have visited the procesingLoop with all of them + ASSERT_TRUE(m_processor->onDirective(noneMediumsDirective)); + noneMediumsHandler->waitUntilHandling(); + + // Now, audioBlockingDirective can be completed. + audioBlockingHandler->doHandlingCompleted(); + + audioAndVisualBlockingHandler->waitUntilCompleted(); + visualBlockingHandler->waitUntilCompleted(); +} + +/** + * Verify that a blocked directive with isBlocking=false on the queue is NOT blocking + * subsequent directives using the same @c Medium. + */ +TEST_F(DirectiveProcessorTest, test_nonBlockingQueuedDirectivIsNotBlocking) { + auto& audioBlockingDirective = m_directive_0_0; + DirectiveHandlerConfiguration audioBlockingHandlerConfig; + audioBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); + auto audioBlockingHandler = + MockDirectiveHandler::create(audioBlockingHandlerConfig, MockDirectiveHandler::DEFAULT_DONE_TIMEOUT_MS); + ASSERT_TRUE(m_router->addDirectiveHandler(audioBlockingHandler)); + + auto& audioAndVisualNonBlocking = m_directive_0_1; + DirectiveHandlerConfiguration audioAndVisualNonBlockingHandlerConfig; + audioAndVisualNonBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_1}] = + BlockingPolicy(BlockingPolicy::MEDIUMS_AUDIO_AND_VISUAL, false); + auto audioAndVisualNonBlockingHandler = MockDirectiveHandler::create(audioAndVisualNonBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(audioAndVisualNonBlockingHandler)); + + auto& visualBlockingDirective = m_directive_0_2; + DirectiveHandlerConfiguration visualBlockingHandlerConfig; + visualBlockingHandlerConfig[{NAMESPACE_AND_NAME_0_2}] = BlockingPolicy(BlockingPolicy::MEDIUM_VISUAL, true); + auto visualBlockingHandler = MockDirectiveHandler::create(visualBlockingHandlerConfig); + ASSERT_TRUE(m_router->addDirectiveHandler(visualBlockingHandler)); + + // Expect completion on all the directives. + EXPECT_CALL(*(audioBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(audioBlockingHandler.get()), preHandleDirective(audioBlockingDirective, _)).Times(1); + EXPECT_CALL(*(audioBlockingHandler.get()), cancelDirective(_)).Times(0); + + EXPECT_CALL(*(audioAndVisualNonBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(audioAndVisualNonBlockingHandler.get()), preHandleDirective(audioAndVisualNonBlocking, _)).Times(1); + EXPECT_CALL(*(audioAndVisualNonBlockingHandler.get()), cancelDirective(_)).Times(0); + + EXPECT_CALL(*(visualBlockingHandler.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(visualBlockingHandler.get()), preHandleDirective(visualBlockingDirective, _)).Times(1); + EXPECT_CALL(*(visualBlockingHandler.get()), cancelDirective(_)).Times(0); + + /* + * Expect the third directive to be handled before the second, because it is neither blocked by the first one + * - no common mediums. and nor by the second, queued directive, since the second is not a blocking directive. + */ + ::testing::Sequence s1; + EXPECT_CALL(*(audioBlockingHandler.get()), handleDirective(MESSAGE_ID_0_0)).Times(1).InSequence(s1); + EXPECT_CALL(*(visualBlockingHandler.get()), handleDirective(MESSAGE_ID_0_2)).Times(1).InSequence(s1); + EXPECT_CALL(*(audioAndVisualNonBlockingHandler.get()), handleDirective(MESSAGE_ID_0_1)).Times(1).InSequence(s1); + + m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0); + + ASSERT_TRUE(m_processor->onDirective(audioBlockingDirective)); + audioBlockingHandler->waitUntilHandling(); + + ASSERT_TRUE(m_processor->onDirective(audioAndVisualNonBlocking)); + ASSERT_TRUE(m_processor->onDirective(visualBlockingDirective)); + + // make sure audioAndVisualNonBlocking has been enqueued. + audioAndVisualNonBlockingHandler->waitUntilPreHandling(); + + /* + * visualBlockingDirective should be handled as it's not blocked by audioBlockingDirective and + * audioAndVisualNonBlocking is not blocking. + */ + visualBlockingHandler->waitUntilHandling(); + + // Now, audioBlockingDirective can be completed. + audioBlockingHandler->doHandlingCompleted(); + + audioAndVisualNonBlockingHandler->waitUntilCompleted(); + visualBlockingHandler->waitUntilCompleted(); +} + +/** + * Test if the workaround in @c DirectiveProcessor allows the handling of InteractionModel.NewDialogRequest v1.0 + * directive. + * TODO: ACSDK-2218: This test should be removed when the workaround is removed. + */ +TEST_F(DirectiveProcessorTest, test_newDialogRequestHandling) { + auto directivePair = AVSDirective::create(NEW_DIALOG_REQUEST_DIRECTIVE_V0, nullptr, NO_CONTEXT); + std::shared_ptr newDialogRequestDirective = std::move(directivePair.first); + + DirectiveHandlerConfiguration handler0Config; + handler0Config[NEW_DIALOG_REQUEST_SIGNATURE] = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); + std::shared_ptr handler0 = MockDirectiveHandler::create(handler0Config); + + ASSERT_TRUE(m_router->addDirectiveHandler(handler0)); + + // The NewDialogRequest directive should be handled by the capability agent. + EXPECT_CALL(*(handler0.get()), handleDirective(_)).Times(1); + + // Set a different dialogRequestID from the one in the directive. + m_processor->setDialogRequestId(DIALOG_REQUEST_ID_1); + + // Handle the directive. + ASSERT_TRUE(m_processor->onDirective(newDialogRequestDirective)); + + // Directive should have been handled by CA. + ASSERT_TRUE(handler0->waitUntilCompleted(TEST_TIMEOUT)); +} + } // namespace test } // namespace adsl } // namespace alexaClientSDK diff --git a/ADSL/test/DirectiveRouterTest.cpp b/ADSL/test/DirectiveRouterTest.cpp index 0905dc77c9..b1dc0f4f55 100644 --- a/ADSL/test/DirectiveRouterTest.cpp +++ b/ADSL/test/DirectiveRouterTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -43,9 +43,15 @@ static const std::string MESSAGE_ID_0_0("Message_0_0"); /// Generic MessageId used for test. static const std::string MESSAGE_ID_0_1("Message_0_1"); +/// Generic MessageId used for test. +static const std::string MESSAGE_ID_0_2("Message_0_2"); + /// Generic MessageId used for tests. static const std::string MESSAGE_ID_1_0("Message_1_0"); +/// Generic MessageId used for test. +static const std::string MESSAGE_ID_2_0("Message_2_0"); + /// Generic DialogRequestId used for tests. static const std::string DIALOG_REQUEST_ID_0("DialogRequestId_0"); @@ -61,12 +67,21 @@ static const std::string NAMESPACE_0("namespace_0"); /// A generic namespace string for tests. static const std::string NAMESPACE_1("namespace_1"); +/// A generic namespace string for tests. +static const std::string NAMESPACE_2("namespace_2"); + /// A generic name string for tests. static const std::string NAME_0("name_0"); /// A generic name string for tests. static const std::string NAME_1("name_1"); +/// A generic name string for tests. +static const std::string NAME_2("name_2"); + +/// A generic 'any name' string for tests. +static const std::string NAME_ANY("*"); + static const std::string TEST_ATTACHMENT_CONTEXT_ID("TEST_ATTACHMENT_CONTEXT_ID"); /// Namespace and name combination for tests. @@ -75,9 +90,21 @@ static const std::string TEST_ATTACHMENT_CONTEXT_ID("TEST_ATTACHMENT_CONTEXT_ID" /// Namespace and name combination (changing name this time) for tests. #define NAMESPACE_AND_NAME_0_1 NAMESPACE_0, NAME_1 +/// Namespace and name combination (changing name this time) for tests. +#define NAMESPACE_AND_NAME_0_2 NAMESPACE_0, NAME_2 + +/// Namespace and name combination ('any name' this time) for tests. +#define NAMESPACE_AND_NAME_0_ANY NAMESPACE_0, NAME_ANY + /// Namespace and name combination (changing namespace this time) for tests. #define NAMESPACE_AND_NAME_1_0 NAMESPACE_1, NAME_0 +/// Namespace and name combination (changing namespace this time) for tests. +#define NAMESPACE_AND_NAME_2_0 NAMESPACE_2, NAME_0 + +/// Namespace and name combination (changing namespace this time) for tests. +#define NAMESPACE_AND_NAME_2_ANY NAMESPACE_2, NAME_ANY + /// Long timeout we only reach when a concurrency test fails. static const std::chrono::seconds LONG_TIMEOUT(15); @@ -101,8 +128,14 @@ class DirectiveRouterTest : public ::testing::Test { /// Generic @c AVSDirective for tests. std::shared_ptr m_directive_0_1; + /// Generic @c AVSDirective for tests. + std::shared_ptr m_directive_0_2; + /// Generic @c AVSDirective for tests. std::shared_ptr m_directive_1_0; + + /// Generic @c AVSDirective for tests. + std::shared_ptr m_directive_2_0; }; void DirectiveRouterTest::SetUp() { @@ -116,10 +149,18 @@ void DirectiveRouterTest::SetUp() { std::make_shared(NAMESPACE_AND_NAME_0_1, MESSAGE_ID_0_1, DIALOG_REQUEST_ID_0); m_directive_0_1 = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader_0_1, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); + auto avsMessageHeader_0_2 = + std::make_shared(NAMESPACE_AND_NAME_0_2, MESSAGE_ID_0_2, DIALOG_REQUEST_ID_0); + m_directive_0_2 = AVSDirective::create( + UNPARSED_DIRECTIVE, avsMessageHeader_0_2, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); auto avsMessageHeader_1_0 = std::make_shared(NAMESPACE_AND_NAME_1_0, MESSAGE_ID_1_0, DIALOG_REQUEST_ID_0); m_directive_1_0 = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader_1_0, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); + auto avsMessageHeader_2_0 = + std::make_shared(NAMESPACE_AND_NAME_2_0, MESSAGE_ID_2_0, DIALOG_REQUEST_ID_0); + m_directive_2_0 = AVSDirective::create( + UNPARSED_DIRECTIVE, avsMessageHeader_2_0, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); } void DirectiveRouterTest::TearDown() { @@ -129,7 +170,7 @@ void DirectiveRouterTest::TearDown() { /** * Check that an un-registered @c AVDirective will not be routed. */ -TEST_F(DirectiveRouterTest, testUnroutedDirective) { +TEST_F(DirectiveRouterTest, test_unroutedDirective) { ASSERT_FALSE(m_router.handleDirectiveImmediately(m_directive_0_0)); } @@ -137,9 +178,9 @@ TEST_F(DirectiveRouterTest, testUnroutedDirective) { * Register an @c AVSDirective for routing. Exercise routing via @c handleDirectiveImmediately(). * Expect that the @c AVSDirective is routed. */ -TEST_F(DirectiveRouterTest, testSettingADirectiveHandler) { +TEST_F(DirectiveRouterTest, test_settingADirectiveHandler) { DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy::NON_BLOCKING; + handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); std::shared_ptr handler0 = MockDirectiveHandler::create(handler0Config); ASSERT_TRUE(m_router.addDirectiveHandler(handler0)); @@ -156,22 +197,33 @@ TEST_F(DirectiveRouterTest, testSettingADirectiveHandler) { * Register @c AVSDirectives to be routed to different handlers. Exercise routing via @c preHandleDirective(). * Expect that the @c AVSDirectives make it to their registered handler. */ -TEST_F(DirectiveRouterTest, testRegisteringMultipeHandler) { +TEST_F(DirectiveRouterTest, test_registeringMultipeHandler) { DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy::NON_BLOCKING; + auto audioNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); + handler0Config[{NAMESPACE_AND_NAME_0_0}] = audioNonBlockingPolicy; std::shared_ptr handler0 = MockDirectiveHandler::create(handler0Config); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_AND_NAME_0_1}] = audioNonBlockingPolicy; std::shared_ptr handler1 = MockDirectiveHandler::create(handler1Config); DirectiveHandlerConfiguration handler2Config; - handler2Config[{NAMESPACE_AND_NAME_1_0}] = BlockingPolicy::NON_BLOCKING; + handler2Config[{NAMESPACE_AND_NAME_0_ANY}] = audioNonBlockingPolicy; std::shared_ptr handler2 = MockDirectiveHandler::create(handler2Config); + DirectiveHandlerConfiguration handler3Config; + handler3Config[{NAMESPACE_AND_NAME_1_0}] = audioNonBlockingPolicy; + std::shared_ptr handler3 = MockDirectiveHandler::create(handler3Config); + + DirectiveHandlerConfiguration handler4Config; + handler4Config[{NAMESPACE_AND_NAME_2_ANY}] = audioNonBlockingPolicy; + std::shared_ptr handler4 = MockDirectiveHandler::create(handler4Config); + ASSERT_TRUE(m_router.addDirectiveHandler(handler0)); ASSERT_TRUE(m_router.addDirectiveHandler(handler1)); ASSERT_TRUE(m_router.addDirectiveHandler(handler2)); + ASSERT_TRUE(m_router.addDirectiveHandler(handler3)); + ASSERT_TRUE(m_router.addDirectiveHandler(handler4)); EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(_)).Times(0); EXPECT_CALL(*(handler0.get()), preHandleDirective(m_directive_0_0, _)).Times(1); @@ -185,15 +237,31 @@ TEST_F(DirectiveRouterTest, testRegisteringMultipeHandler) { EXPECT_CALL(*(handler1.get()), cancelDirective(_)).Times(0); EXPECT_CALL(*(handler1.get()), onDeregistered()).Times(1); + // Test that wildcard handler2 will get a directive, while other handlers exist within the same namespace EXPECT_CALL(*(handler2.get()), handleDirectiveImmediately(_)).Times(0); - EXPECT_CALL(*(handler2.get()), preHandleDirective(m_directive_1_0, _)).Times(1); + EXPECT_CALL(*(handler2.get()), preHandleDirective(m_directive_0_2, _)).Times(1); EXPECT_CALL(*(handler2.get()), handleDirective(_)).Times(0); EXPECT_CALL(*(handler2.get()), cancelDirective(_)).Times(0); EXPECT_CALL(*(handler2.get()), onDeregistered()).Times(1); + EXPECT_CALL(*(handler3.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(handler3.get()), preHandleDirective(m_directive_1_0, _)).Times(1); + EXPECT_CALL(*(handler3.get()), handleDirective(_)).Times(0); + EXPECT_CALL(*(handler3.get()), cancelDirective(_)).Times(0); + EXPECT_CALL(*(handler3.get()), onDeregistered()).Times(1); + + // Test that wildcard handler4 will get a directive, when no other handlers exist within the same namespace + EXPECT_CALL(*(handler4.get()), handleDirectiveImmediately(_)).Times(0); + EXPECT_CALL(*(handler4.get()), preHandleDirective(m_directive_2_0, _)).Times(1); + EXPECT_CALL(*(handler4.get()), handleDirective(_)).Times(0); + EXPECT_CALL(*(handler4.get()), cancelDirective(_)).Times(0); + EXPECT_CALL(*(handler4.get()), onDeregistered()).Times(1); + ASSERT_TRUE(m_router.preHandleDirective(m_directive_0_0, nullptr)); ASSERT_TRUE(m_router.preHandleDirective(m_directive_0_1, nullptr)); + ASSERT_TRUE(m_router.preHandleDirective(m_directive_0_2, nullptr)); ASSERT_TRUE(m_router.preHandleDirective(m_directive_1_0, nullptr)); + ASSERT_TRUE(m_router.preHandleDirective(m_directive_2_0, nullptr)); } /** @@ -203,21 +271,23 @@ TEST_F(DirectiveRouterTest, testRegisteringMultipeHandler) { * were last assigned to and that false and a @c BlockingPolicy of NONE is returned for the directive whose handler * was removed. */ -TEST_F(DirectiveRouterTest, testRemovingChangingAndNotChangingHandlers) { +TEST_F(DirectiveRouterTest, test_removingChangingAndNotChangingHandlers) { DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy::NON_BLOCKING; + auto audioBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); + auto audioNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); + handler0Config[{NAMESPACE_AND_NAME_0_0}] = audioNonBlockingPolicy; std::shared_ptr handler0 = MockDirectiveHandler::create(handler0Config); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_AND_NAME_0_1}] = audioNonBlockingPolicy; std::shared_ptr handler1 = MockDirectiveHandler::create(handler1Config); DirectiveHandlerConfiguration handler2Config; - handler2Config[{NAMESPACE_AND_NAME_1_0}] = BlockingPolicy::NON_BLOCKING; + handler2Config[{NAMESPACE_AND_NAME_1_0}] = audioNonBlockingPolicy; std::shared_ptr handler2 = MockDirectiveHandler::create(handler2Config); DirectiveHandlerConfiguration handler3Config; - handler3Config[{NAMESPACE_AND_NAME_1_0}] = BlockingPolicy::BLOCKING; + handler3Config[{NAMESPACE_AND_NAME_1_0}] = audioBlockingPolicy; std::shared_ptr handler3 = MockDirectiveHandler::create(handler3Config); EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(_)).Times(0); @@ -255,27 +325,29 @@ TEST_F(DirectiveRouterTest, testRemovingChangingAndNotChangingHandlers) { ASSERT_TRUE(m_router.addDirectiveHandler(handler1)); ASSERT_TRUE(m_router.addDirectiveHandler(handler3)); - auto policy = BlockingPolicy::NONE; - ASSERT_FALSE(m_router.handleDirective(m_directive_0_0, &policy)); - ASSERT_EQ(policy, BlockingPolicy::NONE); - ASSERT_TRUE(m_router.handleDirective(m_directive_0_1, &policy)); - ASSERT_EQ(policy, BlockingPolicy::NON_BLOCKING); - ASSERT_TRUE(m_router.handleDirective(m_directive_1_0, &policy)); - ASSERT_EQ(policy, BlockingPolicy::BLOCKING); + auto policy = m_router.getPolicy(m_directive_0_0); + ASSERT_FALSE(m_router.handleDirective(m_directive_0_0)); + ASSERT_FALSE(policy.isValid()); + policy = m_router.getPolicy(m_directive_0_1); + ASSERT_TRUE(m_router.handleDirective(m_directive_0_1)); + ASSERT_EQ(policy, audioNonBlockingPolicy); + policy = m_router.getPolicy(m_directive_1_0); + ASSERT_TRUE(m_router.handleDirective(m_directive_1_0)); + ASSERT_EQ(policy, audioBlockingPolicy); } /** * Register two @c AVSDirectives to be routed to different handlers with different blocking policies. Configure the * mock handlers to return false from @c handleDirective(). Exercise routing via handleDirective(). Expect that - * @c DirectiveRouter::handleDirective() returns @c false and BlockingPolicy::NONE to indicate failure. + * @c DirectiveRouter::handleDirective() returns @c false and BlockingPolicy::nonePolicy() to indicate failure. */ -TEST_F(DirectiveRouterTest, testResultOfHandleDirectiveFailure) { +TEST_F(DirectiveRouterTest, test_resultOfHandleDirectiveFailure) { DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy::NON_BLOCKING; + handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); std::shared_ptr handler0 = MockDirectiveHandler::create(handler0Config); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy::BLOCKING; + handler1Config[{NAMESPACE_AND_NAME_0_1}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); std::shared_ptr handler1 = MockDirectiveHandler::create(handler1Config); ASSERT_TRUE(m_router.addDirectiveHandler(handler0)); @@ -293,11 +365,8 @@ TEST_F(DirectiveRouterTest, testResultOfHandleDirectiveFailure) { EXPECT_CALL(*(handler1.get()), cancelDirective(_)).Times(0); EXPECT_CALL(*(handler1.get()), onDeregistered()).Times(1); - auto policy = BlockingPolicy::NONE; - ASSERT_FALSE(m_router.handleDirective(m_directive_0_0, &policy)); - ASSERT_EQ(policy, BlockingPolicy::NONE); - ASSERT_FALSE(m_router.handleDirective(m_directive_0_1, &policy)); - ASSERT_EQ(policy, BlockingPolicy::NONE); + ASSERT_FALSE(m_router.handleDirective(m_directive_0_0)); + ASSERT_FALSE(m_router.handleDirective(m_directive_0_1)); } /** @@ -305,9 +374,9 @@ TEST_F(DirectiveRouterTest, testResultOfHandleDirectiveFailure) { * until a subsequent invocation of @c handleDirective() has started. Expect the blocked call to preHandleDirective() * to complete quickly. */ -TEST_F(DirectiveRouterTest, testHandlerMethodsCanRunConcurrently) { +TEST_F(DirectiveRouterTest, test_handlerMethodsCanRunConcurrently) { DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy::BLOCKING; + handler0Config[{NAMESPACE_AND_NAME_0_0}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); std::shared_ptr handler0 = MockDirectiveHandler::create(handler0Config); ASSERT_TRUE(m_router.addDirectiveHandler(handler0)); @@ -332,9 +401,9 @@ TEST_F(DirectiveRouterTest, testHandlerMethodsCanRunConcurrently) { EXPECT_CALL(*(handler0.get()), onDeregistered()).Times(1); std::thread sleeperThread([this]() { ASSERT_TRUE(m_router.preHandleDirective(m_directive_0_0, nullptr)); }); - auto policy = BlockingPolicy::NONE; - ASSERT_TRUE(m_router.handleDirective(m_directive_0_0, &policy)); - ASSERT_EQ(policy, BlockingPolicy::BLOCKING); + ASSERT_TRUE(m_router.handleDirective(m_directive_0_0)); + auto policy = m_router.getPolicy(m_directive_0_0); + ASSERT_EQ(policy, BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true)); sleeperThread.join(); } diff --git a/ADSL/test/DirectiveSequencerTest.cpp b/ADSL/test/DirectiveSequencerTest.cpp index 5dbd00d7a5..95c903ea3d 100644 --- a/ADSL/test/DirectiveSequencerTest.cpp +++ b/ADSL/test/DirectiveSequencerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -152,7 +152,7 @@ class DirectiveSequencerTest : public ::testing::Test { void DirectiveSequencerTest::SetUp() { DirectiveHandlerConfiguration config; - config[{NAMESPACE_TEST, NAME_DONE}] = BlockingPolicy::BLOCKING; + config[{NAMESPACE_TEST, NAME_DONE}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); m_doneHandler = MockDirectiveHandler::create(config, LONG_HANDLING_TIME_MS); m_attachmentManager = std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS); m_exceptionEncounteredSender = std::make_shared>(); @@ -184,7 +184,7 @@ void DirectiveSequencerTest::TearDown() { /** * Test DirectiveSequencer::create() with a nullptr @c ExceptionEncounteredSender. Expect create to fail. */ -TEST_F(DirectiveSequencerTest, testNullptrExceptionSender) { +TEST_F(DirectiveSequencerTest, test_nullptrExceptionSender) { ASSERT_TRUE(m_sequencer); auto sequencer = DirectiveSequencer::create(nullptr); ASSERT_FALSE(sequencer); @@ -193,14 +193,14 @@ TEST_F(DirectiveSequencerTest, testNullptrExceptionSender) { /** * Verify core DirectiveSequencerTest. Expect a new non-null instance of m_sequencer. */ -TEST_F(DirectiveSequencerTest, testCreateAndDoneTrigger) { +TEST_F(DirectiveSequencerTest, test_createAndDoneTrigger) { ASSERT_TRUE(m_sequencer); } /** * Exercise sending a @c nullptr to @c onDirective. Expect that false is returned. */ -TEST_F(DirectiveSequencerTest, testNullptrDirective) { +TEST_F(DirectiveSequencerTest, test_nullptrDirective) { ASSERT_FALSE(m_sequencer->onDirective(nullptr)); } @@ -208,7 +208,7 @@ TEST_F(DirectiveSequencerTest, testNullptrDirective) { * Exercise sending a @c AVSDirective for which no handler has been registered. Expect that * m_exceptionEncounteredSender will receive a request to send the ExceptionEncountered message. */ -TEST_F(DirectiveSequencerTest, testUnhandledDirective) { +TEST_F(DirectiveSequencerTest, test_unhandledDirective) { auto avsMessageHeader = std::make_shared(NAMESPACE_SPEAKER, NAME_SET_VOLUME, MESSAGE_ID_0); std::shared_ptr directive = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); @@ -220,12 +220,12 @@ TEST_F(DirectiveSequencerTest, testUnhandledDirective) { * Send a directive with an empty DialogRequestId. * Expect a call to handleDirectiveImmediately(). */ -TEST_F(DirectiveSequencerTest, testEmptyDialogRequestId) { +TEST_F(DirectiveSequencerTest, test_emptyDialogRequestId) { auto avsMessageHeader = std::make_shared(NAMESPACE_SPEAKER, NAME_SET_VOLUME, MESSAGE_ID_0); std::shared_ptr directive = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration config; - config[{NAMESPACE_SPEAKER, NAME_SET_VOLUME}] = BlockingPolicy::NON_BLOCKING; + config[{NAMESPACE_SPEAKER, NAME_SET_VOLUME}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler = MockDirectiveHandler::create(config); EXPECT_CALL(*(handler.get()), handleDirectiveImmediately(directive)).Times(0); EXPECT_CALL(*(handler.get()), preHandleDirective(_, _)).Times(1); @@ -240,16 +240,16 @@ TEST_F(DirectiveSequencerTest, testEmptyDialogRequestId) { * Send a directive with a DialogRequestId but with HANDLE_IMMEDIATELY policy in its handlier. * Expect a call to handleDirectiveImmediately(). */ -TEST_F(DirectiveSequencerTest, testHandleImmediatelyHandler) { +TEST_F(DirectiveSequencerTest, test_handleImmediatelyHandler) { auto avsMessageHeader = std::make_shared(NAMESPACE_TEST, NAME_HANDLE_IMMEDIATELY, MESSAGE_ID_0); std::shared_ptr directive = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration config; - config[{NAMESPACE_TEST, NAME_HANDLE_IMMEDIATELY}] = BlockingPolicy::HANDLE_IMMEDIATELY; + config[{NAMESPACE_TEST, NAME_HANDLE_IMMEDIATELY}] = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); auto handler = MockDirectiveHandler::create(config); - EXPECT_CALL(*(handler.get()), handleDirectiveImmediately(directive)).Times(1); - EXPECT_CALL(*(handler.get()), preHandleDirective(_, _)).Times(0); - EXPECT_CALL(*(handler.get()), handleDirective(_)).Times(0); + EXPECT_CALL(*(handler.get()), handleDirectiveImmediately(directive)).Times(0); + EXPECT_CALL(*(handler.get()), preHandleDirective(_, _)).Times(1); + EXPECT_CALL(*(handler.get()), handleDirective(_)).Times(1); EXPECT_CALL(*(handler.get()), cancelDirective(_)).Times(0); ASSERT_TRUE(m_sequencer->addDirectiveHandler(handler)); m_sequencer->onDirective(directive); @@ -261,7 +261,7 @@ TEST_F(DirectiveSequencerTest, testHandleImmediatelyHandler) { * for each of the NamespaceAndName values. Expect that the directive with no mapping is not seen by a handler and * that the one that still has a handler is handled. */ -TEST_F(DirectiveSequencerTest, testRemovingAndChangingHandlers) { +TEST_F(DirectiveSequencerTest, test_removingAndChangingHandlers) { auto avsMessageHeader0 = std::make_shared(NAMESPACE_SPEAKER, NAME_SET_VOLUME, MESSAGE_ID_0); std::shared_ptr directive0 = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); @@ -270,15 +270,15 @@ TEST_F(DirectiveSequencerTest, testRemovingAndChangingHandlers) { UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_SPEAKER, NAME_SET_VOLUME}] = BlockingPolicy::NON_BLOCKING; + handler0Config[{NAMESPACE_SPEAKER, NAME_SET_VOLUME}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler0 = MockDirectiveHandler::create(handler0Config); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_TEST, NAME_NON_BLOCKING}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_TEST, NAME_NON_BLOCKING}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler1 = MockDirectiveHandler::create(handler1Config); DirectiveHandlerConfiguration handler2Config; - handler2Config[{NAMESPACE_TEST, NAME_NON_BLOCKING}] = BlockingPolicy::NON_BLOCKING; + handler2Config[{NAMESPACE_TEST, NAME_NON_BLOCKING}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler2 = MockDirectiveHandler::create(handler2Config); EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(directive1)).Times(0); @@ -312,14 +312,14 @@ TEST_F(DirectiveSequencerTest, testRemovingAndChangingHandlers) { * @c preHandleDirective() and a call to @c handleDirective(). The @c AVSDirective is the cancelled, triggering * a call to cancelDirective() to close out the test. */ -TEST_F(DirectiveSequencerTest, testBlockingDirective) { +TEST_F(DirectiveSequencerTest, test_blockingDirective) { auto avsMessageHeader = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handlerConfig; - handlerConfig[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handlerConfig[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler = MockDirectiveHandler::create(handlerConfig, LONG_HANDLING_TIME_MS); EXPECT_CALL(*(handler.get()), handleDirectiveImmediately(_)).Times(0); @@ -338,7 +338,7 @@ TEST_F(DirectiveSequencerTest, testBlockingDirective) { /** * Send a long running directive with an non-empty @c DialogRequestId and a BLOCKING policy. */ -TEST_F(DirectiveSequencerTest, testBlockingThenNonDialogDirective) { +TEST_F(DirectiveSequencerTest, test_blockingThenNonDialogDirective) { auto avsMessageHeader0 = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive0 = AVSDirective::create( @@ -349,11 +349,11 @@ TEST_F(DirectiveSequencerTest, testBlockingThenNonDialogDirective) { UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler0 = MockDirectiveHandler::create(handler0Config, LONG_HANDLING_TIME_MS); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_SPEAKER, NAME_SET_VOLUME}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_SPEAKER, NAME_SET_VOLUME}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler1 = MockDirectiveHandler::create(handler1Config); EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(_)).Times(0); @@ -385,14 +385,14 @@ TEST_F(DirectiveSequencerTest, testBlockingThenNonDialogDirective) { * @c preHandleDirective(@cAVSDirective) a call to @c handleDirective(@c MessageId, @c DirectiveHandlingResult), * and a call to @c cancelDirective(@c MessageId). */ -TEST_F(DirectiveSequencerTest, testBargeIn) { +TEST_F(DirectiveSequencerTest, test_bargeIn) { auto avsMessageHeader = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handlerConfig; - handlerConfig[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handlerConfig[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler = MockDirectiveHandler::create(handlerConfig, std::chrono::milliseconds(LONG_HANDLING_TIME_MS)); EXPECT_CALL(*(handler.get()), handleDirectiveImmediately(_)).Times(0); @@ -415,7 +415,7 @@ TEST_F(DirectiveSequencerTest, testBargeIn) { * Along the way we set the DialogRequestId to the same value to verify that that setting it to the * current value does not cancel queued directives. */ -TEST_F(DirectiveSequencerTest, testBlockingThenNonBockingOnSameDialogId) { +TEST_F(DirectiveSequencerTest, testTimer_blockingThenNonBockingOnSameDialogId) { auto avsMessageHeader0 = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive0 = AVSDirective::create( @@ -430,15 +430,15 @@ TEST_F(DirectiveSequencerTest, testBlockingThenNonBockingOnSameDialogId) { UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler0 = MockDirectiveHandler::create(handler0Config); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler1 = MockDirectiveHandler::create(handler1Config); DirectiveHandlerConfiguration handler2Config; - handler2Config[{NAMESPACE_TEST, NAME_NON_BLOCKING}] = BlockingPolicy::NON_BLOCKING; + handler2Config[{NAMESPACE_TEST, NAME_NON_BLOCKING}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler2 = MockDirectiveHandler::create(handler2Config); ASSERT_TRUE(m_sequencer->addDirectiveHandler(handler0)); @@ -477,7 +477,7 @@ TEST_F(DirectiveSequencerTest, testBlockingThenNonBockingOnSameDialogId) { * the first two directives will be cancelled and the third one will be handled (and then cancelled at the * end by setting the dialogRequestId to close out the test). */ -TEST_F(DirectiveSequencerTest, testThatBargeInDropsSubsequentDirectives) { +TEST_F(DirectiveSequencerTest, test_thatBargeInDropsSubsequentDirectives) { auto avsMessageHeader0 = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive0 = AVSDirective::create( @@ -492,15 +492,15 @@ TEST_F(DirectiveSequencerTest, testThatBargeInDropsSubsequentDirectives) { UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler0 = MockDirectiveHandler::create(handler0Config, LONG_HANDLING_TIME_MS); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler1 = MockDirectiveHandler::create(handler1Config); DirectiveHandlerConfiguration handler2Config; - handler2Config[{NAMESPACE_TEST, NAME_BLOCKING}] = BlockingPolicy::BLOCKING; + handler2Config[{NAMESPACE_TEST, NAME_BLOCKING}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler2 = MockDirectiveHandler::create(handler2Config, LONG_HANDLING_TIME_MS); ASSERT_TRUE(m_sequencer->addDirectiveHandler(handler0)); @@ -540,7 +540,7 @@ TEST_F(DirectiveSequencerTest, testThatBargeInDropsSubsequentDirectives) { * Expect that the first @c AVSDirective will not be cancelled and that the second @c AVSDirective will be dropped * entirely. */ -TEST_F(DirectiveSequencerTest, testPreHandleDirectiveError) { +TEST_F(DirectiveSequencerTest, test_preHandleDirectiveError) { auto avsMessageHeader0 = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive0 = AVSDirective::create( @@ -551,11 +551,11 @@ TEST_F(DirectiveSequencerTest, testPreHandleDirectiveError) { UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler0 = MockDirectiveHandler::create(handler0Config, LONG_HANDLING_TIME_MS); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler1 = MockDirectiveHandler::create(handler1Config); ASSERT_TRUE(m_sequencer->addDirectiveHandler(handler0)); @@ -584,7 +584,7 @@ TEST_F(DirectiveSequencerTest, testPreHandleDirectiveError) { * Expect that the first @c AVSDirective will not be cancelled and that the second @c AVSDirective may be * dropped before @c preHandleDirective() is called, and that if not, it will be cancelled. */ -TEST_F(DirectiveSequencerTest, testHandleDirectiveError) { +TEST_F(DirectiveSequencerTest, test_handleDirectiveError) { auto avsMessageHeader0 = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive0 = AVSDirective::create( @@ -595,11 +595,11 @@ TEST_F(DirectiveSequencerTest, testHandleDirectiveError) { UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler0 = MockDirectiveHandler::create(handler0Config, LONG_HANDLING_TIME_MS); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy::NON_BLOCKING; + handler1Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler1 = MockDirectiveHandler::create(handler1Config); ASSERT_TRUE(m_sequencer->addDirectiveHandler(handler0)); @@ -635,7 +635,7 @@ TEST_F(DirectiveSequencerTest, testHandleDirectiveError) { * subsequent directives with the same dialogRequestId. Along the way, call @c addDirectiveHandler() while * inside cancelDirective() to verify that that operation is refused. */ -TEST_F(DirectiveSequencerTest, testAddDirectiveHandlersWhileHandlingDirectives) { +TEST_F(DirectiveSequencerTest, test_addDirectiveHandlersWhileHandlingDirectives) { auto avsMessageHeader0 = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive0 = AVSDirective::create( @@ -650,23 +650,23 @@ TEST_F(DirectiveSequencerTest, testAddDirectiveHandlersWhileHandlingDirectives) UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler0 = MockDirectiveHandler::create(handler0Config, LONG_HANDLING_TIME_MS); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handler1Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler1 = MockDirectiveHandler::create(handler1Config); DirectiveHandlerConfiguration handler2Config; - handler2Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy::NON_BLOCKING; + handler2Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler2 = MockDirectiveHandler::create(handler2Config); DirectiveHandlerConfiguration handler3Config; - handler3Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy::NON_BLOCKING; + handler3Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler3 = MockDirectiveHandler::create(handler3Config); DirectiveHandlerConfiguration handler4Config; - handler4Config[{NAMESPACE_TEST, NAME_NON_BLOCKING}] = BlockingPolicy::NON_BLOCKING; + handler4Config[{NAMESPACE_TEST, NAME_NON_BLOCKING}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler4 = MockDirectiveHandler::create(handler4Config); auto cancelDirectiveFunction = [this, &handler1, &handler3, &handler4](const std::string& messageId) { @@ -731,7 +731,7 @@ TEST_F(DirectiveSequencerTest, testAddDirectiveHandlersWhileHandlingDirectives) * @c preHandleDirective(@c AVSDirective) and a call to @c handleDirective() for the @c AVSDirective that are not * @c HANDLE_IMMEDIATELY. And for the one with @c HANDLE_IMMEDIATELY, only @c handleDirectiveImmediately() is called. */ -TEST_F(DirectiveSequencerTest, testHandleBlockingThenImmediatelyThenNonBockingOnSameDialogId) { +TEST_F(DirectiveSequencerTest, test_handleBlockingThenImmediatelyThenNonBockingOnSameDialogId) { auto avsMessageHeader0 = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive0 = AVSDirective::create( @@ -746,37 +746,46 @@ TEST_F(DirectiveSequencerTest, testHandleBlockingThenImmediatelyThenNonBockingOn UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handler0Config; - handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handler0Config[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler0 = MockDirectiveHandler::create(handler0Config); DirectiveHandlerConfiguration handler1Config; - handler1Config[{NAMESPACE_TEST, NAME_HANDLE_IMMEDIATELY}] = BlockingPolicy::HANDLE_IMMEDIATELY; + handler1Config[{NAMESPACE_TEST, NAME_HANDLE_IMMEDIATELY}] = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); auto handler1 = MockDirectiveHandler::create(handler1Config); DirectiveHandlerConfiguration handler2Config; - handler2Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy::NON_BLOCKING; + handler2Config[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler2 = MockDirectiveHandler::create(handler2Config); ASSERT_TRUE(m_sequencer->addDirectiveHandler(handler0)); ASSERT_TRUE(m_sequencer->addDirectiveHandler(handler1)); ASSERT_TRUE(m_sequencer->addDirectiveHandler(handler2)); - // Enforce the sequence. - InSequence dummy; + /* + * InSequence dummy; + * Enforce the sequence: + * All directive0 methods should be called first. + * preHandle1 before preHandle2. + * handle1 before handle2. + * The order of preHandle2 and handle1 is not consistent. + */ + Sequence s1, s2; EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(_)).Times(0); - EXPECT_CALL(*(handler0.get()), preHandleDirective(directive0, _)).Times(1); - EXPECT_CALL(*(handler0.get()), handleDirective(MESSAGE_ID_0)).Times(1); + EXPECT_CALL(*(handler0.get()), preHandleDirective(directive0, _)).Times(1).InSequence(s1, s2); + EXPECT_CALL(*(handler0.get()), handleDirective(MESSAGE_ID_0)).Times(1).InSequence(s1, s2); EXPECT_CALL(*(handler0.get()), cancelDirective(_)).Times(0); - EXPECT_CALL(*(handler1.get()), handleDirectiveImmediately(directive1)).Times(1); - EXPECT_CALL(*(handler1.get()), preHandleDirective(_, _)).Times(0); - EXPECT_CALL(*(handler1.get()), handleDirective(_)).Times(0); + EXPECT_CALL(*(handler1.get()), handleDirectiveImmediately(directive1)).Times(0); + EXPECT_CALL(*(handler1.get()), preHandleDirective(directive1, _)).Times(1).InSequence(s1, s2); + EXPECT_CALL(*(handler2.get()), preHandleDirective(directive2, _)).Times(1).InSequence(s2); + + EXPECT_CALL(*(handler1.get()), handleDirective(_)).Times(1).InSequence(s2); EXPECT_CALL(*(handler1.get()), cancelDirective(_)).Times(0); EXPECT_CALL(*(handler2.get()), handleDirectiveImmediately(_)).Times(0); - EXPECT_CALL(*(handler2.get()), preHandleDirective(directive2, _)).Times(1); - EXPECT_CALL(*(handler2.get()), handleDirective(MESSAGE_ID_2)).Times(1); + + EXPECT_CALL(*(handler2.get()), handleDirective(MESSAGE_ID_2)).Times(1).InSequence(s1, s2); EXPECT_CALL(*(handler2.get()), cancelDirective(_)).Times(0); m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0); @@ -790,14 +799,14 @@ TEST_F(DirectiveSequencerTest, testHandleBlockingThenImmediatelyThenNonBockingOn /** * Check that the @ DirectiveSequencer does not handle directives when it is disabled */ -TEST_F(DirectiveSequencerTest, testAddDirectiveAfterDisabled) { +TEST_F(DirectiveSequencerTest, test_addDirectiveAfterDisabled) { auto avsMessageHeader = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handlerConfig; - handlerConfig[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handlerConfig[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler = MockDirectiveHandler::create(handlerConfig, LONG_HANDLING_TIME_MS); EXPECT_CALL(*handler, handleDirectiveImmediately(_)).Times(0); @@ -817,14 +826,14 @@ TEST_F(DirectiveSequencerTest, testAddDirectiveAfterDisabled) { /** * Check that the @ DirectiveSequencer.disable() cancel directive being handled */ -TEST_F(DirectiveSequencerTest, testDisableCancelsDirective) { +TEST_F(DirectiveSequencerTest, test_disableCancelsDirective) { auto avsMessageHeader = std::make_shared(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive = AVSDirective::create( UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handlerConfig; - handlerConfig[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy::BLOCKING; + handlerConfig[{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); auto handler = MockDirectiveHandler::create(handlerConfig, LONG_HANDLING_TIME_MS); EXPECT_CALL(*handler, handleDirectiveImmediately(_)).Times(0); @@ -849,7 +858,7 @@ TEST_F(DirectiveSequencerTest, testDisableCancelsDirective) { /** * Check that the @ DirectiveSequencer can handle directives after being re-enabled */ -TEST_F(DirectiveSequencerTest, testAddDirectiveAfterReEnabled) { +TEST_F(DirectiveSequencerTest, test_addDirectiveAfterReEnabled) { auto avsMessageHeader0 = std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_0, DIALOG_REQUEST_ID_0); std::shared_ptr directive0 = AVSDirective::create( @@ -864,7 +873,7 @@ TEST_F(DirectiveSequencerTest, testAddDirectiveAfterReEnabled) { "anotherIgnored", avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, TEST_ATTACHMENT_CONTEXT_ID); DirectiveHandlerConfiguration handlerConfig; - handlerConfig[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy::NON_BLOCKING; + handlerConfig[{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}] = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); auto handler = MockDirectiveHandler::create(handlerConfig); // No handle calls are expected diff --git a/ADSL/test/MessageInterpreterTest.cpp b/ADSL/test/MessageInterpreterTest.cpp index d61d9d6f70..0614ba370f 100644 --- a/ADSL/test/MessageInterpreterTest.cpp +++ b/ADSL/test/MessageInterpreterTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -208,7 +208,7 @@ class MessageIntepreterTest : public ::testing::Test { * Test when the content of message is invalid JSON format. The AVSDirective shouldn't be created and * and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS. */ -TEST_F(MessageIntepreterTest, messageIsInValidJSON) { +TEST_F(MessageIntepreterTest, test_messageIsInValidJSON) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(1); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)).Times(0); m_messageInterpreter->receive(TEST_ATTACHMENT_CONTEXT_ID, INVALID_JSON); @@ -218,7 +218,7 @@ TEST_F(MessageIntepreterTest, messageIsInValidJSON) { * Test when the message doesn't contain the directive key in JSON content. The AVSDirective shouldn't be created and * and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS. */ -TEST_F(MessageIntepreterTest, messageHasInvalidDirectiveKey) { +TEST_F(MessageIntepreterTest, test_messageHasInvalidDirectiveKey) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(1); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)).Times(0); m_messageInterpreter->receive(TEST_ATTACHMENT_CONTEXT_ID, DIRECTIVE_INVALID_DIRECTIVE_KEY); @@ -228,7 +228,7 @@ TEST_F(MessageIntepreterTest, messageHasInvalidDirectiveKey) { * Test when the message doesn't contain the header key in JSON content. The AVSDirective shouldn't be created and * and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS. */ -TEST_F(MessageIntepreterTest, messageHasInvalidHeaderKey) { +TEST_F(MessageIntepreterTest, test_messageHasInvalidHeaderKey) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(1); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)).Times(0); m_messageInterpreter->receive(TEST_ATTACHMENT_CONTEXT_ID, DIRECTIVE_INVALID_HEADER_KEY); @@ -238,7 +238,7 @@ TEST_F(MessageIntepreterTest, messageHasInvalidHeaderKey) { * Test when the message doesn't contain the namespace key in JSON content. The AVSDirective shouldn't be created and * and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS. */ -TEST_F(MessageIntepreterTest, messageHasInvalidNamespaceKey) { +TEST_F(MessageIntepreterTest, test_messageHasInvalidNamespaceKey) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(1); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)).Times(0); m_messageInterpreter->receive(TEST_ATTACHMENT_CONTEXT_ID, DIRECTIVE_INVALID_NAMESPACE_KEY); @@ -248,7 +248,7 @@ TEST_F(MessageIntepreterTest, messageHasInvalidNamespaceKey) { * Test when the message doesn't contain the name key in JSON content. The AVSDirective shouldn't be created and * and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS. */ -TEST_F(MessageIntepreterTest, messageHasInvalidNameKey) { +TEST_F(MessageIntepreterTest, test_messageHasInvalidNameKey) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(1); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)).Times(0); m_messageInterpreter->receive(TEST_ATTACHMENT_CONTEXT_ID, DIRECTIVE_INVALID_NAME_KEY); @@ -258,7 +258,7 @@ TEST_F(MessageIntepreterTest, messageHasInvalidNameKey) { * Test when the message doesn't contain the messageId key in JSON content. The AVSDirective shouldn't be created and * and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS. */ -TEST_F(MessageIntepreterTest, messageHasInvalidMessageIdKey) { +TEST_F(MessageIntepreterTest, test_messageHasInvalidMessageIdKey) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(1); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)).Times(0); m_messageInterpreter->receive(TEST_ATTACHMENT_CONTEXT_ID, DIRECTIVE_INVALID_MESSAGEID_KEY); @@ -268,7 +268,7 @@ TEST_F(MessageIntepreterTest, messageHasInvalidMessageIdKey) { * Test when the message doesn't contain the dialogRequestId key in JSON content. DialogRequestId is optional, so * AVSDirective should be created and passed to the directive sequencer. */ -TEST_F(MessageIntepreterTest, messageHasNoDialogRequestIdKey) { +TEST_F(MessageIntepreterTest, test_messageHasNoDialogRequestIdKey) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(0); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)) .Times(1) @@ -286,7 +286,7 @@ TEST_F(MessageIntepreterTest, messageHasNoDialogRequestIdKey) { * Test when the message doesn't contain the payload key in JSON content. The AVSDirective shouldn't be created and * and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS. */ -TEST_F(MessageIntepreterTest, messageHasNoPayloadKey) { +TEST_F(MessageIntepreterTest, test_messageHasNoPayloadKey) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(1); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)).Times(0); m_messageInterpreter->receive(TEST_ATTACHMENT_CONTEXT_ID, DIRECTIVE_NO_PAYLOAD); @@ -296,7 +296,7 @@ TEST_F(MessageIntepreterTest, messageHasNoPayloadKey) { * Test when the message contains an invalid payload key in JSON content. The AVSDirective shouldn't be created and * and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS. */ -TEST_F(MessageIntepreterTest, messageHasInvalidPayloadKey) { +TEST_F(MessageIntepreterTest, test_messageHasInvalidPayloadKey) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(1); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)).Times(0); m_messageInterpreter->receive(TEST_ATTACHMENT_CONTEXT_ID, DIRECTIVE_INVALID_PAYLOAD_KEY); @@ -306,7 +306,7 @@ TEST_F(MessageIntepreterTest, messageHasInvalidPayloadKey) { * Test when the message is valid JSON content with all keys required in the header. An AVSDirective should be created * and passed to the directive sequencer. */ -TEST_F(MessageIntepreterTest, messageIsValidDirective) { +TEST_F(MessageIntepreterTest, test_messageIsValidDirective) { EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(0); EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_)) .Times(1) diff --git a/AFML/CMakeLists.txt b/AFML/CMakeLists.txt index bda6e0d60e..c8b056b3f8 100644 --- a/AFML/CMakeLists.txt +++ b/AFML/CMakeLists.txt @@ -4,4 +4,4 @@ project(AFML LANGUAGES CXX) include(../build/BuildDefaults.cmake) add_subdirectory("src") -acsdk_add_test_subdirectory_if_allowed() +add_subdirectory("test") diff --git a/AFML/include/AFML/AudioActivityTracker.h b/AFML/include/AFML/AudioActivityTracker.h index f1c1d99ed2..7cc9928058 100644 --- a/AFML/include/AFML/AudioActivityTracker.h +++ b/AFML/include/AFML/AudioActivityTracker.h @@ -22,6 +22,8 @@ #include #include +#include +#include #include #include #include @@ -41,6 +43,7 @@ namespace afml { class AudioActivityTracker : public avsCommon::utils::RequiresShutdown , public ActivityTrackerInterface + , public avsCommon::sdkInterfaces::CapabilityConfigurationInterface , public avsCommon::sdkInterfaces::StateProviderInterface { public: /** @@ -63,6 +66,11 @@ class AudioActivityTracker void notifyOfActivityUpdates(const std::vector& channelStates) override; /// @} + /// @name CapabilityConfigurationInterface Functions + /// @{ + std::unordered_set> getCapabilityConfigurations() override; + /// @} + private: /** * Constructor. @@ -120,6 +128,9 @@ class AudioActivityTracker std::unordered_map m_channelNamesInLowerCase; /// @} + /// Set of capability configurations that will get published using the Capabilities API + std::unordered_set> m_capabilityConfigurations; + /** * @c Executor which queues up operations from asynchronous API calls. * diff --git a/AFML/include/AFML/FocusManager.h b/AFML/include/AFML/FocusManager.h index cac2aa2a89..4d0cdbfee0 100644 --- a/AFML/include/AFML/FocusManager.h +++ b/AFML/include/AFML/FocusManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AFML_INCLUDE_AFML_FOCUSMANAGER_H_ #define ALEXA_CLIENT_SDK_AFML_INCLUDE_AFML_FOCUSMANAGER_H_ +#include #include #include #include @@ -84,12 +85,6 @@ class FocusManager : public avsCommon::sdkInterfaces::FocusManagerInterface { unsigned int priority; }; - /// The default @c ChannelConfiguration for AVS audio channels. - static const std::vector DEFAULT_AUDIO_CHANNELS; - - /// The default @c ChannelConfiguration for AVS visual channels. - static const std::vector DEFAULT_VISUAL_CHANNELS; - /** * This constructor creates Channels based on the provided configurations. * @@ -100,7 +95,7 @@ class FocusManager : public avsCommon::sdkInterfaces::FocusManagerInterface { * been updated. */ FocusManager( - const std::vector& channelConfigurations, + const std::vector channelConfigurations, std::shared_ptr activityTrackerInterface = nullptr); bool acquireChannel( @@ -114,12 +109,30 @@ class FocusManager : public avsCommon::sdkInterfaces::FocusManagerInterface { void stopForegroundActivity() override; + void stopAllActivities() override; + void addObserver(const std::shared_ptr& observer) override; void removeObserver( const std::shared_ptr& observer) override; + /** + * Retrieves the default @c ChannelConfiguration for AVS audio channels. + * + * @return the default @c ChannelConfiguration for AVS audio channels. + */ + static const std::vector getDefaultAudioChannels(); + + /** + * Retrieves the default @c ChannelConfiguration for AVS visual channels. + * + * @return the default @c ChannelConfiguration for AVS visual channels. + */ + static const std::vector getDefaultVisualChannels(); + private: + /// A Map for mapping a @c Channel to its owner. + using ChannelsToInterfaceNamesMap = std::map, std::string>; /** * Functor so that we can compare Channel objects via shared_ptr. */ @@ -185,6 +198,17 @@ class FocusManager : public avsCommon::sdkInterfaces::FocusManagerInterface { std::shared_ptr foregroundChannel, std::string foregroundChannelInterface); + /** + * Stops all channels specified in @c channelsOwnersMap. + * A channel will get stopped if it's currently owned by the interface mapped to the channel + * in @c channelsOwnersMap. + * This function provides the full implementation which the public method will + * call. + * + * @param channelsOwnersMap Mapping of channel to owning interfaces + */ + void stopAllActivitiesHelper(const ChannelsToInterfaceNamesMap& channelsOwnersMap); + /** * Finds the channel from the given channel name. * diff --git a/AFML/include/AFML/VisualActivityTracker.h b/AFML/include/AFML/VisualActivityTracker.h index 1494188941..6c6a8f0eba 100644 --- a/AFML/include/AFML/VisualActivityTracker.h +++ b/AFML/include/AFML/VisualActivityTracker.h @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include #include @@ -39,6 +41,7 @@ namespace afml { class VisualActivityTracker : public avsCommon::utils::RequiresShutdown , public ActivityTrackerInterface + , public avsCommon::sdkInterfaces::CapabilityConfigurationInterface , public avsCommon::sdkInterfaces::StateProviderInterface { public: /** @@ -61,6 +64,11 @@ class VisualActivityTracker void notifyOfActivityUpdates(const std::vector& channelStates) override; /// @} + /// @name CapabilityConfigurationInterface Functions + /// @{ + std::unordered_set> getCapabilityConfigurations() override; + /// @} + private: /** * Constructor. @@ -97,6 +105,9 @@ class VisualActivityTracker Channel::State m_channelState; /// @} + /// Set of capability configurations that will get published using the Capabilities API + std::unordered_set> m_capabilityConfigurations; + /** * @c Executor which queues up operations from asynchronous API calls. * diff --git a/AFML/src/AudioActivityTracker.cpp b/AFML/src/AudioActivityTracker.cpp index 8fd84279ad..e12d40284d 100644 --- a/AFML/src/AudioActivityTracker.cpp +++ b/AFML/src/AudioActivityTracker.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,14 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; using namespace avsCommon::avs; +/// AudioActivityTracker capability constants +/// AudioActivityTracker interface type +static const std::string AUDIOACTIVITYTRACKER_CAPABILITY_INTERFACE_TYPE = "AlexaInterface"; +/// AudioActivityTracker interface name +static const std::string AUDIOACTIVITYTRACKER_CAPABILITY_INTERFACE_NAME = "AudioActivityTracker"; +/// AudioActivityTracker interface version +static const std::string AUDIOACTIVITYTRACKER_CAPABILITY_INTERFACE_VERSION = "1.0"; + /// String to identify log entries originating from this file. static const std::string TAG("AudioActivityTracker"); @@ -50,6 +59,13 @@ static const char IDLE_TIME_KEY[] = "idleTimeInMilliseconds"; /// The interface key used in the AudioActivityTracker context. static const char INTERFACE_KEY[] = "interface"; +/** + * Creates the AudioActivityTracker capability configuration. + * + * @return The AudioActivityTracker capability configuration. + */ +static std::shared_ptr getAudioActivityTrackerCapabilityConfiguration(); + std::shared_ptr AudioActivityTracker::create( std::shared_ptr contextManager) { if (!contextManager) { @@ -80,6 +96,16 @@ AudioActivityTracker::AudioActivityTracker( std::shared_ptr contextManager) : RequiresShutdown{"AudioActivityTracker"}, m_contextManager{contextManager} { + m_capabilityConfigurations.insert(getAudioActivityTrackerCapabilityConfiguration()); +} + +std::shared_ptr getAudioActivityTrackerCapabilityConfiguration() { + std::unordered_map configMap; + configMap.insert({CAPABILITY_INTERFACE_TYPE_KEY, AUDIOACTIVITYTRACKER_CAPABILITY_INTERFACE_TYPE}); + configMap.insert({CAPABILITY_INTERFACE_NAME_KEY, AUDIOACTIVITYTRACKER_CAPABILITY_INTERFACE_NAME}); + configMap.insert({CAPABILITY_INTERFACE_VERSION_KEY, AUDIOACTIVITYTRACKER_CAPABILITY_INTERFACE_VERSION}); + + return std::make_shared(configMap); } void AudioActivityTracker::doShutdown() { @@ -121,7 +147,7 @@ void AudioActivityTracker::executeProvideState(unsigned int stateRequestToken) { } contextJson.AddMember(INTERFACE_KEY, channelContext.interfaceName, payload.GetAllocator()); - contextJson.AddMember(IDLE_TIME_KEY, idleTime.count(), payload.GetAllocator()); + contextJson.AddMember(IDLE_TIME_KEY, static_cast(idleTime.count()), payload.GetAllocator()); payload.AddMember( rapidjson::StringRef(executeChannelNameInLowerCase(it->first)), contextJson, payload.GetAllocator()); @@ -157,5 +183,10 @@ const std::string& AudioActivityTracker::executeChannelNameInLowerCase(const std return it->second; } +std::unordered_set> AudioActivityTracker:: + getCapabilityConfigurations() { + return m_capabilityConfigurations; +} + } // namespace afml } // namespace alexaClientSDK diff --git a/AFML/src/FocusManager.cpp b/AFML/src/FocusManager.cpp index 74660da491..4b42532760 100644 --- a/AFML/src/FocusManager.cpp +++ b/AFML/src/FocusManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -33,16 +33,8 @@ static const std::string TAG("FocusManager"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) -const std::vector FocusManager::DEFAULT_AUDIO_CHANNELS = { - {FocusManagerInterface::DIALOG_CHANNEL_NAME, FocusManagerInterface::DIALOG_CHANNEL_PRIORITY}, - {FocusManagerInterface::ALERTS_CHANNEL_NAME, FocusManagerInterface::ALERTS_CHANNEL_PRIORITY}, - {FocusManagerInterface::CONTENT_CHANNEL_NAME, FocusManagerInterface::CONTENT_CHANNEL_PRIORITY}}; - -const std::vector FocusManager::DEFAULT_VISUAL_CHANNELS = { - {FocusManagerInterface::VISUAL_CHANNEL_NAME, FocusManagerInterface::VISUAL_CHANNEL_PRIORITY}}; - FocusManager::FocusManager( - const std::vector& channelConfigurations, + const std::vector channelConfigurations, std::shared_ptr activityTrackerInterface) : m_activityTracker{activityTrackerInterface} { for (auto config : channelConfigurations) { @@ -116,6 +108,26 @@ void FocusManager::stopForegroundActivity() { }); } +void FocusManager::stopAllActivities() { + ACSDK_DEBUG5(LX(__func__)); + + if (m_activeChannels.empty()) { + ACSDK_DEBUG5(LX(__func__).m("no active channels")); + return; + } + + ChannelsToInterfaceNamesMap channelOwnersCapture; + std::unique_lock lock(m_mutex); + + for (const auto& channel : m_activeChannels) { + channelOwnersCapture.insert(std::pair, std::string>(channel, channel->getInterface())); + } + + lock.unlock(); + + m_executor.submitToFront([this, channelOwnersCapture]() { stopAllActivitiesHelper(channelOwnersCapture); }); +} + void FocusManager::addObserver(const std::shared_ptr& observer) { std::lock_guard lock(m_mutex); m_observers.insert(observer); @@ -213,6 +225,35 @@ void FocusManager::stopForegroundActivityHelper( notifyActivityTracker(); } +void FocusManager::stopAllActivitiesHelper(const ChannelsToInterfaceNamesMap& channelsOwnersMap) { + ACSDK_DEBUG3(LX(__func__)); + + std::set> channelsToClear; + + std::unique_lock lock(m_mutex); + + for (const auto& channelAndInterface : channelsOwnersMap) { + if (channelAndInterface.first->getInterface() == channelAndInterface.second) { + m_activeChannels.erase(channelAndInterface.first); + channelsToClear.insert(channelAndInterface.first); + } else { + ACSDK_INFO(LX(__func__) + .d("reason", "channel has other ownership") + .d("channel", channelAndInterface.first->getName()) + .d("currentInterface", channelAndInterface.first->getInterface()) + .d("originalInterface", channelAndInterface.second)); + } + } + + lock.unlock(); + + for (const auto& channel : channelsToClear) { + setChannelFocus(channel, FocusState::NONE); + } + foregroundHighestPriorityActiveChannel(); + notifyActivityTracker(); +} + std::shared_ptr FocusManager::getChannel(const std::string& channelName) const { auto search = m_allChannels.find(channelName); if (search != m_allChannels.end()) { @@ -246,7 +287,6 @@ bool FocusManager::doesChannelPriorityExist(const unsigned int priority) const { } void FocusManager::foregroundHighestPriorityActiveChannel() { - // Lock here to update internal state which stopForegroundActivity may concurrently access. std::unique_lock lock(m_mutex); std::shared_ptr channelToForeground = getHighestPriorityActiveChannelLocked(); lock.unlock(); @@ -263,5 +303,22 @@ void FocusManager::notifyActivityTracker() { m_activityUpdates.clear(); } +const std::vector FocusManager::getDefaultAudioChannels() { + static const std::vector defaultAudioChannels = { + {FocusManagerInterface::DIALOG_CHANNEL_NAME, FocusManagerInterface::DIALOG_CHANNEL_PRIORITY}, + {FocusManagerInterface::ALERT_CHANNEL_NAME, FocusManagerInterface::ALERT_CHANNEL_PRIORITY}, + {FocusManagerInterface::COMMUNICATIONS_CHANNEL_NAME, FocusManagerInterface::COMMUNICATIONS_CHANNEL_PRIORITY}, + {FocusManagerInterface::CONTENT_CHANNEL_NAME, FocusManagerInterface::CONTENT_CHANNEL_PRIORITY}}; + + return defaultAudioChannels; +} + +const std::vector FocusManager::getDefaultVisualChannels() { + static const std::vector defaultVisualChannels = { + {FocusManagerInterface::VISUAL_CHANNEL_NAME, FocusManagerInterface::VISUAL_CHANNEL_PRIORITY}}; + + return defaultVisualChannels; +} + } // namespace afml } // namespace alexaClientSDK diff --git a/AFML/src/VisualActivityTracker.cpp b/AFML/src/VisualActivityTracker.cpp index 8296722738..3882e99f06 100644 --- a/AFML/src/VisualActivityTracker.cpp +++ b/AFML/src/VisualActivityTracker.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -28,6 +29,14 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; using namespace avsCommon::avs; +/// VisualActivityTracker capability constants +/// VisualActivityTracker interface type +static const std::string VISUALACTIVITYTRACKER_CAPABILITY_INTERFACE_TYPE = "AlexaInterface"; +/// VisualActivityTracker interface name +static const std::string VISUALACTIVITYTRACKER_CAPABILITY_INTERFACE_NAME = "VisualActivityTracker"; +/// VisualActivityTracker interface version +static const std::string VISUALACTIVITYTRACKER_CAPABILITY_INTERFACE_VERSION = "1.0"; + /// String to identify log entries originating from this file. static const std::string TAG("VisualActivityTracker"); @@ -47,6 +56,13 @@ static const char FOCUSED_KEY[] = "focused"; /// The interface key used in the VisualActivityTracker context. static const char INTERFACE_KEY[] = "interface"; +/** + * Creates the VisualActivityTracker capability configuration. + * + * @return The VisualActivityTracker capability configuration. + */ +static std::shared_ptr getVisualActivityTrackerCapabilityConfiguration(); + std::shared_ptr VisualActivityTracker::create( std::shared_ptr contextManager) { if (!contextManager) { @@ -97,6 +113,16 @@ VisualActivityTracker::VisualActivityTracker( std::shared_ptr contextManager) : RequiresShutdown{"VisualActivityTracker"}, m_contextManager{contextManager} { + m_capabilityConfigurations.insert(getVisualActivityTrackerCapabilityConfiguration()); +} + +std::shared_ptr getVisualActivityTrackerCapabilityConfiguration() { + std::unordered_map configMap; + configMap.insert({CAPABILITY_INTERFACE_TYPE_KEY, VISUALACTIVITYTRACKER_CAPABILITY_INTERFACE_TYPE}); + configMap.insert({CAPABILITY_INTERFACE_NAME_KEY, VISUALACTIVITYTRACKER_CAPABILITY_INTERFACE_NAME}); + configMap.insert({CAPABILITY_INTERFACE_VERSION_KEY, VISUALACTIVITYTRACKER_CAPABILITY_INTERFACE_VERSION}); + + return std::make_shared(configMap); } void VisualActivityTracker::doShutdown() { @@ -135,5 +161,10 @@ void VisualActivityTracker::executeProvideState(unsigned int stateRequestToken) } } +std::unordered_set> VisualActivityTracker:: + getCapabilityConfigurations() { + return m_capabilityConfigurations; +} + } // namespace afml } // namespace alexaClientSDK diff --git a/AFML/test/AudioActivityTrackerTest.cpp b/AFML/test/AudioActivityTrackerTest.cpp index f3aa38e6ca..523285a0c4 100644 --- a/AFML/test/AudioActivityTrackerTest.cpp +++ b/AFML/test/AudioActivityTrackerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -216,7 +216,7 @@ SetStateResult AudioActivityTrackerTest::wakeOnSetState() { } /// Test if there's no activity updates, AudioActivityTracker will return an empty context. -TEST_F(AudioActivityTrackerTest, noActivityUpdate) { +TEST_F(AudioActivityTrackerTest, test_noActivityUpdate) { EXPECT_CALL( *(m_mockContextManager.get()), setState(NAMESPACE_AND_NAME_STATE, "", StateRefreshPolicy::SOMETIMES, PROVIDE_STATE_TOKEN_TEST)) @@ -228,7 +228,7 @@ TEST_F(AudioActivityTrackerTest, noActivityUpdate) { } /// Test if there's an empty set of activity updates, AudioActivityTracker will return an empty context. -TEST_F(AudioActivityTrackerTest, emptyActivityUpdate) { +TEST_F(AudioActivityTrackerTest, test_emptyActivityUpdate) { const std::vector channels; EXPECT_CALL( *(m_mockContextManager.get()), @@ -242,7 +242,7 @@ TEST_F(AudioActivityTrackerTest, emptyActivityUpdate) { } /// Test if there's an activityUpdate for one active channel, context will be reported correctly. -TEST_F(AudioActivityTrackerTest, oneActiveChannel) { +TEST_F(AudioActivityTrackerTest, test_oneActiveChannel) { std::vector channels; m_dialogChannel->setFocus(FocusState::FOREGROUND); channels.push_back(m_dialogChannel->getState()); @@ -253,7 +253,7 @@ TEST_F(AudioActivityTrackerTest, oneActiveChannel) { * Test if there's an activityUpdate for one Dialog channel with "SpeechRecognizer" as an interface, * AudioActivityTracker will ignore it and report empty context. */ -TEST_F(AudioActivityTrackerTest, oneActiveChannelWithAIPAsInterface) { +TEST_F(AudioActivityTrackerTest, test_oneActiveChannelWithAIPAsInterface) { std::vector channels; m_dialogChannel->setInterface(AIP_INTERFACE_NAME); m_dialogChannel->setFocus(FocusState::FOREGROUND); @@ -275,7 +275,7 @@ TEST_F(AudioActivityTrackerTest, oneActiveChannelWithAIPAsInterface) { * interface, AudioActivityTracker will ignore the "SpeechRecognizer" interface going active but report * "SpeechSynthesizer" with idleTime not equal to zero. */ -TEST_F(AudioActivityTrackerTest, oneActiveChannelWithDefaultAndAIPAsInterfaces) { +TEST_F(AudioActivityTrackerTest, test_oneActiveChannelWithDefaultAndAIPAsInterfaces) { std::vector channels; m_dialogChannel->setFocus(FocusState::FOREGROUND); channels.push_back(m_dialogChannel->getState()); @@ -283,7 +283,7 @@ TEST_F(AudioActivityTrackerTest, oneActiveChannelWithDefaultAndAIPAsInterfaces) } /// Test if there's an activityUpdate for two active channels, context will be reported correctly. -TEST_F(AudioActivityTrackerTest, twoActiveChannels) { +TEST_F(AudioActivityTrackerTest, test_twoActiveChannels) { std::vector channels; m_dialogChannel->setFocus(FocusState::FOREGROUND); m_contentChannel->setFocus(FocusState::BACKGROUND); @@ -293,7 +293,7 @@ TEST_F(AudioActivityTrackerTest, twoActiveChannels) { } /// Test if there's an activityUpdate for one active and one idle channels, context will be reported correctly. -TEST_F(AudioActivityTrackerTest, oneActiveOneIdleChannels) { +TEST_F(AudioActivityTrackerTest, test_oneActiveOneIdleChannels) { std::vector channels; m_dialogChannel->setFocus(FocusState::FOREGROUND); m_contentChannel->setFocus(FocusState::BACKGROUND); diff --git a/AFML/test/FocusManagerTest.cpp b/AFML/test/FocusManagerTest.cpp index 215e18e1bf..6798968a62 100644 --- a/AFML/test/FocusManagerTest.cpp +++ b/AFML/test/FocusManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -275,12 +275,12 @@ class FocusManagerTest }; /// Tests acquireChannel with an invalid Channel name, expecting no focus changes to be made. -TEST_F(FocusManagerTest, acquireInvalidChannelName) { +TEST_F(FocusManagerTest, test_acquireInvalidChannelName) { ASSERT_FALSE(m_focusManager->acquireChannel(INCORRECT_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); } /// Tests acquireChannel, expecting to get Foreground status since no other Channels are active. -TEST_F(FocusManagerTest, acquireChannelWithNoOtherChannelsActive) { +TEST_F(FocusManagerTest, test_acquireChannelWithNoOtherChannelsActive) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); } @@ -289,7 +289,7 @@ TEST_F(FocusManagerTest, acquireChannelWithNoOtherChannelsActive) { * Tests acquireChannel with two Channels. The lower priority Channel should get Background focus and the higher * priority Channel should get Foreground focus. */ -TEST_F(FocusManagerTest, acquireLowerPriorityChannelWithOneHigherPriorityChannelTaken) { +TEST_F(FocusManagerTest, test_acquireLowerPriorityChannelWithOneHigherPriorityChannelTaken) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -300,7 +300,7 @@ TEST_F(FocusManagerTest, acquireLowerPriorityChannelWithOneHigherPriorityChannel * Tests acquireChannel with three Channels. The two lowest priority Channels should get Background focus while the * highest priority Channel should be Foreground focused. */ -TEST_F(FocusManagerTest, aquireLowerPriorityChannelWithTwoHigherPriorityChannelsTaken) { +TEST_F(FocusManagerTest, test_aquireLowerPriorityChannelWithTwoHigherPriorityChannelsTaken) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_INTERFACE_NAME)); ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); @@ -314,7 +314,7 @@ TEST_F(FocusManagerTest, aquireLowerPriorityChannelWithTwoHigherPriorityChannels * Channel should at first be Foreground focused and then get a change to Background focus while the higher priority * should be Foreground focused. */ -TEST_F(FocusManagerTest, acquireHigherPriorityChannelWithOneLowerPriorityChannelTaken) { +TEST_F(FocusManagerTest, test_acquireHigherPriorityChannelWithOneLowerPriorityChannelTaken) { ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); assertFocusChange(contentClient, FocusState::FOREGROUND); @@ -327,7 +327,7 @@ TEST_F(FocusManagerTest, acquireHigherPriorityChannelWithOneLowerPriorityChannel * Tests acquireChannel with a single Channel. The original observer should be notified to stop and the new observer * should obtain Foreground focus. */ -TEST_F(FocusManagerTest, kickOutActivityOnSameChannel) { +TEST_F(FocusManagerTest, test_kickOutActivityOnSameChannel) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -340,7 +340,7 @@ TEST_F(FocusManagerTest, kickOutActivityOnSameChannel) { /** * Tests releaseChannel with a single Channel. The observer should be notified to stop. */ -TEST_F(FocusManagerTest, simpleReleaseChannel) { +TEST_F(FocusManagerTest, test_simpleReleaseChannel) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -351,7 +351,7 @@ TEST_F(FocusManagerTest, simpleReleaseChannel) { /** * Tests releaseChannel on a Channel with an incorrect observer. The client should not receive any callback. */ -TEST_F(FocusManagerTest, simpleReleaseChannelWithIncorrectObserver) { +TEST_F(FocusManagerTest, test_simpleReleaseChannelWithIncorrectObserver) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -367,7 +367,7 @@ TEST_F(FocusManagerTest, simpleReleaseChannelWithIncorrectObserver) { * focused Channel should be notified to come to the Foreground while the originally Foreground focused Channel should * be notified to stop. */ -TEST_F(FocusManagerTest, releaseForegroundChannelWhileBackgroundChannelTaken) { +TEST_F(FocusManagerTest, test_releaseForegroundChannelWhileBackgroundChannelTaken) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -382,7 +382,7 @@ TEST_F(FocusManagerTest, releaseForegroundChannelWhileBackgroundChannelTaken) { /** * Tests stopForegroundActivity with a single Channel. The observer should be notified to stop. */ -TEST_F(FocusManagerTest, simpleNonTargetedStop) { +TEST_F(FocusManagerTest, test_simpleNonTargetedStop) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -394,7 +394,7 @@ TEST_F(FocusManagerTest, simpleNonTargetedStop) { * Tests stopForegroundActivity with a three active Channels. The Foreground Channel observer should be notified to * stop each time and the next highest priority background Channel should be brought to the foreground each time. */ -TEST_F(FocusManagerTest, threeNonTargetedStopsWithThreeActivitiesHappening) { +TEST_F(FocusManagerTest, test_threeNonTargetedStopsWithThreeActivitiesHappening) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -420,7 +420,7 @@ TEST_F(FocusManagerTest, threeNonTargetedStopsWithThreeActivitiesHappening) { * Tests stopForegroundActivity with a single Channel. The next client to request a different Channel should be given * foreground focus. */ -TEST_F(FocusManagerTest, stopForegroundActivityAndAcquireDifferentChannel) { +TEST_F(FocusManagerTest, test_stopForegroundActivityAndAcquireDifferentChannel) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -435,7 +435,7 @@ TEST_F(FocusManagerTest, stopForegroundActivityAndAcquireDifferentChannel) { * Tests stopForegroundActivity with a single Channel. The next client to request the same Channel should be given * foreground focus. */ -TEST_F(FocusManagerTest, stopForegroundActivityAndAcquireSameChannel) { +TEST_F(FocusManagerTest, test_stopForegroundActivityAndAcquireSameChannel) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -446,11 +446,53 @@ TEST_F(FocusManagerTest, stopForegroundActivityAndAcquireSameChannel) { assertFocusChange(dialogClient, FocusState::FOREGROUND); } +/** + * Test stopAllActivities with a single channel. + * Expect focus change only on that channel and reacquiring the same channel should resulted in foreground focus. + */ +TEST_F(FocusManagerTest, test_stopAllActivitiesWithSingleChannel) { + ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); + assertFocusChange(dialogClient, FocusState::FOREGROUND); + + m_focusManager->stopAllActivities(); + assertFocusChange(dialogClient, FocusState::NONE); + + assertNoFocusChange(contentClient); + assertNoFocusChange(alertsClient); + + ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); + assertFocusChange(dialogClient, FocusState::FOREGROUND); +} + +/** + * Test stopAllActivities with three channels. + * Expect focus change to none for all channels and a channel should resulted in foreground focus. + */ +TEST_F(FocusManagerTest, test_stopAllActivitiesWithThreeChannels) { + ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); + assertFocusChange(dialogClient, FocusState::FOREGROUND); + + ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_INTERFACE_NAME)); + assertFocusChange(contentClient, FocusState::BACKGROUND); + + ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_INTERFACE_NAME)); + assertFocusChange(alertsClient, FocusState::BACKGROUND); + + m_focusManager->stopAllActivities(); + + assertFocusChange(dialogClient, FocusState::NONE); + assertFocusChange(contentClient, FocusState::NONE); + assertFocusChange(alertsClient, FocusState::NONE); + + ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); + assertFocusChange(dialogClient, FocusState::FOREGROUND); +} + /** * Tests releaseChannel with the background Channel while there is a foreground Channel. The foreground Channel * should remain foregrounded while the background Channel's observer should be notified to stop. */ -TEST_F(FocusManagerTest, releaseBackgroundChannelWhileTwoChannelsTaken) { +TEST_F(FocusManagerTest, test_releaseBackgroundChannelWhileTwoChannelsTaken) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -468,7 +510,7 @@ TEST_F(FocusManagerTest, releaseBackgroundChannelWhileTwoChannelsTaken) { * observer of the foreground be notified to stop and the new observer of the Channel will be notified that it has * Foreground focus. The originally backgrounded Channel should not change focus. */ -TEST_F(FocusManagerTest, kickOutActivityOnSameChannelWhileOtherChannelsActive) { +TEST_F(FocusManagerTest, test_kickOutActivityOnSameChannelWhileOtherChannelsActive) { ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_INTERFACE_NAME)); assertFocusChange(dialogClient, FocusState::FOREGROUND); @@ -484,7 +526,7 @@ TEST_F(FocusManagerTest, kickOutActivityOnSameChannelWhileOtherChannelsActive) { } /// Tests that multiple observers can be added, and that they are notified of all focus changes. -TEST_F(FocusManagerTest, addObserver) { +TEST_F(FocusManagerTest, test_addObserver) { // These are all the observers that will be added. std::vector> observers; observers.push_back(std::make_shared()); @@ -524,7 +566,7 @@ TEST_F(FocusManagerTest, addObserver) { } /// Tests that observers can be removed, and that they are no longer notified of focus changes after removal. -TEST_F(FocusManagerTest, removeObserver) { +TEST_F(FocusManagerTest, test_removeObserver) { // These are all the observers that will ever be added. std::vector> allObservers; @@ -582,7 +624,7 @@ TEST_F(FocusManagerTest, removeObserver) { /** * Tests activityTracker with three Channels and make sure notifyOfActivityUpdates() is called correctly. */ -TEST_F(FocusManagerTest, activityTracker) { +TEST_F(FocusManagerTest, test_activityTracker) { // Acquire Content channel and expect notifyOfActivityUpdates() to notify activities on the Content channel. const std::vector test1 = { {CONTENT_CHANNEL_NAME, CONTENT_INTERFACE_NAME, FocusState::FOREGROUND}}; @@ -657,17 +699,17 @@ class ChannelTest }; /// Tests that the getName method of Channel works properly. -TEST_F(ChannelTest, getName) { +TEST_F(ChannelTest, test_getName) { ASSERT_EQ(testChannel->getName(), DIALOG_CHANNEL_NAME); } /// Tests that the getPriority method of Channel works properly. -TEST_F(ChannelTest, getPriority) { +TEST_F(ChannelTest, test_getPriority) { ASSERT_EQ(testChannel->getPriority(), DIALOG_CHANNEL_PRIORITY); } /// Tests that the observer properly gets notified of focus changes. -TEST_F(ChannelTest, setObserverThenSetFocus) { +TEST_F(ChannelTest, test_setObserverThenSetFocus) { testChannel->setObserver(clientA); ASSERT_TRUE(testChannel->setFocus(FocusState::FOREGROUND)); @@ -683,7 +725,7 @@ TEST_F(ChannelTest, setObserverThenSetFocus) { } /// Tests that Channels are compared properly -TEST_F(ChannelTest, priorityComparison) { +TEST_F(ChannelTest, test_priorityComparison) { std::shared_ptr lowerPriorityChannel = std::make_shared(CONTENT_CHANNEL_NAME, CONTENT_CHANNEL_PRIORITY); ASSERT_TRUE(*testChannel > *lowerPriorityChannel); @@ -691,7 +733,7 @@ TEST_F(ChannelTest, priorityComparison) { } /// Tests that a Channel correctly reports whether it has an observer. -TEST_F(ChannelTest, hasObserver) { +TEST_F(ChannelTest, test_hasObserver) { ASSERT_FALSE(testChannel->hasObserver()); testChannel->setObserver(clientA); ASSERT_TRUE(testChannel->hasObserver()); @@ -705,7 +747,7 @@ TEST_F(ChannelTest, hasObserver) { * Tests that the timeAtIdle only gets updated when channel goes to idle and not when channel goes to Foreground or * Background. */ -TEST_F(ChannelTest, getTimeAtIdle) { +TEST_F(ChannelTest, test_getTimeAtIdle) { auto startTime = testChannel->getState().timeAtIdle; ASSERT_TRUE(testChannel->setFocus(FocusState::FOREGROUND)); auto afterForegroundTime = testChannel->getState().timeAtIdle; diff --git a/AFML/test/VisualActivityTrackerTest.cpp b/AFML/test/VisualActivityTrackerTest.cpp index a4e572c158..e9780ec66b 100644 --- a/AFML/test/VisualActivityTrackerTest.cpp +++ b/AFML/test/VisualActivityTrackerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -206,7 +206,7 @@ SetStateResult VisualActivityTrackerTest::wakeOnSetState() { } /// Test if there's no activity updates, VisualActivityTracker will return an empty context. -TEST_F(VisualActivityTrackerTest, noActivityUpdate) { +TEST_F(VisualActivityTrackerTest, test_noActivityUpdate) { EXPECT_CALL( *(m_mockContextManager.get()), setState(NAMESPACE_AND_NAME_STATE, "", StateRefreshPolicy::SOMETIMES, PROVIDE_STATE_TOKEN_TEST)) @@ -218,13 +218,13 @@ TEST_F(VisualActivityTrackerTest, noActivityUpdate) { } /// Test if there's an empty vector of activity updates, VisualActivityTracker will return an empty context. -TEST_F(VisualActivityTrackerTest, emptyActivityUpdate) { +TEST_F(VisualActivityTrackerTest, test_emptyActivityUpdate) { std::vector channels; provideUpdate(channels); } /// Test if there's an activityUpdate for one idle channel, VisualActivityTracker will return an empty context. -TEST_F(VisualActivityTrackerTest, oneIdleChannel) { +TEST_F(VisualActivityTrackerTest, test_oneIdleChannel) { std::vector channels; m_visualChannel->setFocus(FocusState::NONE); channels.push_back(m_visualChannel->getState()); @@ -232,7 +232,7 @@ TEST_F(VisualActivityTrackerTest, oneIdleChannel) { } /// Test if there's an activityUpdate for one active channel, context will be reported correctly. -TEST_F(VisualActivityTrackerTest, oneActiveChannel) { +TEST_F(VisualActivityTrackerTest, test_oneActiveChannel) { std::vector channels; m_visualChannel->setFocus(FocusState::FOREGROUND); channels.push_back(m_visualChannel->getState()); @@ -243,7 +243,7 @@ TEST_F(VisualActivityTrackerTest, oneActiveChannel) { * Test if there's an vector of activity updates with one valid and one invalid channel, VisualActivityTracker will * return an empty context. */ -TEST_F(VisualActivityTrackerTest, invalidChannelActivityUpdate) { +TEST_F(VisualActivityTrackerTest, test_invalidChannelActivityUpdate) { std::vector channels; auto invalidChannel = std::make_shared(INVALID_CHANNEL_NAME, INVALID_CHANNEL_PRIORITY); m_visualChannel->setFocus(FocusState::FOREGROUND); @@ -256,7 +256,7 @@ TEST_F(VisualActivityTrackerTest, invalidChannelActivityUpdate) { * Test if there's an vector of activity updates with one valid channel, VisualActivityTracker take the state from the * last element of the vector. */ -TEST_F(VisualActivityTrackerTest, validChannelTwoActivityUpdates) { +TEST_F(VisualActivityTrackerTest, test_validChannelTwoActivityUpdates) { std::vector channels; m_visualChannel->setFocus(FocusState::FOREGROUND); channels.push_back(m_visualChannel->getState()); @@ -267,4 +267,4 @@ TEST_F(VisualActivityTrackerTest, validChannelTwoActivityUpdates) { } // namespace test } // namespace afml -} // namespace alexaClientSDK \ No newline at end of file +} // namespace alexaClientSDK diff --git a/AVSCommon/AVS/CMakeLists.txt b/AVSCommon/AVS/CMakeLists.txt index ec82c64620..0130ac18bf 100644 --- a/AVSCommon/AVS/CMakeLists.txt +++ b/AVSCommon/AVS/CMakeLists.txt @@ -1 +1 @@ -acsdk_add_test_subdirectory_if_allowed() +add_subdirectory("test") diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/AVSDirective.h b/AVSCommon/AVS/include/AVSCommon/AVS/AVSDirective.h index 7e29406589..6399511e6a 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/AVSDirective.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/AVSDirective.h @@ -16,8 +16,10 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_AVSDIRECTIVE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_AVSDIRECTIVE_H_ +#include #include #include +#include #include "Attachment/AttachmentManagerInterface.h" #include "AVSMessage.h" @@ -32,11 +34,54 @@ namespace avs { class AVSDirective : public AVSMessage { public: /** - * Create an AVSDirective object with the given @c avsMessageHeader, @c payload and @c attachmentManager. + * An enum to indicate the status of parsing an AVS Directive from a JSON string representation. + */ + enum class ParseStatus { + /// The parse was successful. + SUCCESS, + + /// The parse failed due to invalid JSON formatting. + ERROR_INVALID_JSON, + + /// The parse failed due to the directive key being missing. + ERROR_MISSING_DIRECTIVE_KEY, + + /// The parse failed due to the header key being missing. + ERROR_MISSING_HEADER_KEY, + + /// The parse failed due to the namespace key being missing. + ERROR_MISSING_NAMESPACE_KEY, + + /// The parse failed due to the name key being missing. + ERROR_MISSING_NAME_KEY, + + /// The parse failed due to the message id key being missing. + ERROR_MISSING_MESSAGE_ID_KEY, + + /// The parse failed due to the message payload key being missing. + ERROR_MISSING_PAYLOAD_KEY + }; + + /** + * Creates an AVSDirective. * - * @param unparsedDirective The unparsed directive JSON string from AVS. - * @param avsMessageHeader The header fields of the directive. - * @param payload The payload of the directive. + * @param unparsedDirective The unparsed AVS Directive JSON string. + * @param attachmentManager The attachment manager. + * @param attachmentContextId The contextId required to get attachments from the AttachmentManager. + * @return A pair of an AVSDirective pointer and a parse status. If the AVSDirective is nullptr, the status will + * express the parse error. + */ + static std::pair, ParseStatus> create( + const std::string& unparsedDirective, + std::shared_ptr attachmentManager, + const std::string& attachmentContextId); + + /** + * Creates an AVSDirective. + * + * @param unparsedDirective The unparsed AVS Directive JSON string. + * @param avsMessageHeader The header fields of the Directive. + * @param payload The payload of the Directive. * @param attachmentManager The attachment manager. * @param attachmentContextId The contextId required to get attachments from the AttachmentManager. * @return The created AVSDirective object or @c nullptr if creation failed. @@ -89,6 +134,45 @@ class AVSDirective : public AVSMessage { std::string m_attachmentContextId; }; +/** + * This function converts the provided @c ParseStatus to a string. + * + * @param status The @c ParseStatus to convert to a string. + * @return The string conversion of the @c ParseStatus. + */ +inline std::string avsDirectiveParseStatusToString(AVSDirective::ParseStatus status) { + switch (status) { + case AVSDirective::ParseStatus::SUCCESS: + return "SUCCESS"; + case AVSDirective::ParseStatus::ERROR_INVALID_JSON: + return "ERROR_INVALID_JSON"; + case AVSDirective::ParseStatus::ERROR_MISSING_DIRECTIVE_KEY: + return "ERROR_MISSING_DIRECTIVE_KEY"; + case AVSDirective::ParseStatus::ERROR_MISSING_HEADER_KEY: + return "ERROR_MISSING_HEADER_KEY"; + case AVSDirective::ParseStatus::ERROR_MISSING_NAMESPACE_KEY: + return "ERROR_MISSING_NAMESPACE_KEY"; + case AVSDirective::ParseStatus::ERROR_MISSING_NAME_KEY: + return "ERROR_MISSING_NAME_KEY"; + case AVSDirective::ParseStatus::ERROR_MISSING_MESSAGE_ID_KEY: + return "ERROR_MISSING_MESSAGE_ID_KEY"; + case AVSDirective::ParseStatus::ERROR_MISSING_PAYLOAD_KEY: + return "ERROR_MISSING_PAYLOAD_KEY"; + } + return "UNKNOWN_STATUS"; +} + +/** + * Write a @c ParseStatus value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param status The @c ParseStatus value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const AVSDirective::ParseStatus& status) { + return stream << avsDirectiveParseStatusToString(status); +} + } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/AbstractConnection.h b/AVSCommon/AVS/include/AVSCommon/AVS/AbstractAVSConnectionManager.h similarity index 75% rename from AVSCommon/AVS/include/AVSCommon/AVS/AbstractConnection.h rename to AVSCommon/AVS/include/AVSCommon/AVS/AbstractAVSConnectionManager.h index 7b39a5d673..e216878740 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/AbstractConnection.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/AbstractAVSConnectionManager.h @@ -13,13 +13,14 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ABSTRACTCONNECTION_H_ -#define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ABSTRACTCONNECTION_H_ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ABSTRACTAVSCONNECTIONMANAGER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ABSTRACTAVSCONNECTIONMANAGER_H_ #include #include #include +#include #include namespace alexaClientSDK { @@ -27,9 +28,9 @@ namespace avsCommon { namespace avs { /** - * This class reflects a connection to AVS and how it may be observed. + * This class provides a partial implementation of the AVSConnectionManagerInterface. */ -class AbstractConnection { +class AbstractAVSConnectionManager : public sdkInterfaces::AVSConnectionManagerInterface { public: /// Type alias for brevity. using ConnectionStatusObserverInterface = avsCommon::sdkInterfaces::ConnectionStatusObserverInterface; @@ -37,36 +38,20 @@ class AbstractConnection { /** * Constructor. */ - AbstractConnection( + AbstractAVSConnectionManager( std::unordered_set> observers = std::unordered_set>()); /** * Destructor. */ - virtual ~AbstractConnection() = default; + virtual ~AbstractAVSConnectionManager() = default; - /** - * Returns whether the AVS connection is established. If the connection is pending, @c false will be returned. - * - * @return Whether the AVS connection is established. - */ - virtual bool isConnected() const = 0; - - /** - * Adds an observer to be notified of connection status changes. The observer will be notified of the current - * connection status before this function returns. - * - * @param observer The observer to add. - */ - void addConnectionStatusObserver(std::shared_ptr observer); - - /** - * Removes an observer from being notified of connection status changes. - * - * @param observer The observer to remove. - */ - void removeConnectionStatusObserver(std::shared_ptr observer); + /// @name AVSConnectionManagerInterface method overrides. + /// @{ + void addConnectionStatusObserver(std::shared_ptr observer) override; + void removeConnectionStatusObserver(std::shared_ptr observer) override; + /// @} protected: /** @@ -106,4 +91,4 @@ class AbstractConnection { } // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ABSTRACTCONNECTION_H_ +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ABSTRACTAVSCONNECTIONMANAGER_H_ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/AttachmentReader.h b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/AttachmentReader.h index 9c7fd89466..aafd46ece1 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/AttachmentReader.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/AttachmentReader.h @@ -18,6 +18,7 @@ #include #include +#include #include "AVSCommon/Utils/SDS/ReaderPolicy.h" @@ -41,6 +42,9 @@ class AttachmentReader { OK_WOULDBLOCK, /// On a request for n bytes, less than n bytes were available on a blocking read. OK_TIMEDOUT, + /// The writer has overwritten the new data on reader's current position. Reader position is reset + /// to current writer position. + OK_OVERRUN_RESET, /// The underlying data representation is no longer readable. CLOSED, /// The writer has corrupted the reader data. The attachment is no longer valid. @@ -71,7 +75,7 @@ class AttachmentReader { * @param numBytes The size of the buffer in bytes. * @param[out] readStatus The out-parameter where the resulting state of the read will be expressed. * @param timeoutMs The timeout for this read call in milliseconds. This value is only used for the @c BLOCKING - * reader policy. + * reader policy. If this parameter is zero, there is no timeout and blocking reads will wait forever. * @return The number of bytes read as a result of this call. */ virtual std::size_t read( @@ -105,6 +109,43 @@ class AttachmentReader { virtual void close(ClosePoint closePoint = ClosePoint::AFTER_DRAINING_CURRENT_BUFFER) = 0; }; +/** + * Write an @c Attachment::ReadStatus value to the given stream. + * + * @param stream The stream to write the value to. + * @param status The value to write to the stream as a string. + * @return The stream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const AttachmentReader::ReadStatus& status) { + switch (status) { + case AttachmentReader::ReadStatus::OK: + stream << "OK"; + break; + case AttachmentReader::ReadStatus::OK_WOULDBLOCK: + stream << "OK_WOULDBLOCK"; + break; + case AttachmentReader::ReadStatus::OK_TIMEDOUT: + stream << "OK_TIMEDOUT"; + break; + case AttachmentReader::ReadStatus::OK_OVERRUN_RESET: + stream << "OK_OVERRUN_RESET"; + break; + case AttachmentReader::ReadStatus::CLOSED: + stream << "CLOSED"; + break; + case AttachmentReader::ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE: + stream << "ERROR_BYTES_LESS_THAN_WORD_SIZE"; + break; + case AttachmentReader::ReadStatus::ERROR_OVERRUN: + stream << "ERROR_OVERRUN"; + break; + case AttachmentReader::ReadStatus::ERROR_INTERNAL: + stream << "ERROR_INTERNAL"; + break; + } + return stream; +} + } // namespace attachment } // namespace avs } // namespace avsCommon diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/AttachmentUtils.h b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/AttachmentUtils.h new file mode 100644 index 0000000000..6cf3b21e04 --- /dev/null +++ b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/AttachmentUtils.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ATTACHMENT_ATTACHMENTUTILS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ATTACHMENT_ATTACHMENTUTILS_H_ + +#include +#include + +#include "AttachmentReader.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { +namespace attachment { + +/** + * A collection of util functions related to Attachment classes. + */ +class AttachmentUtils { +public: + /** + * Create an @c AttachmentReader with a preset content of a @c std::vector of @c char. + * + * @param srcBuffer The reader content. + * @return A new @c AttachmentReader or @c nullptr if the operation failed. + */ + static std::unique_ptr createAttachmentReader(const std::vector& srcBuffer); +}; + +} // namespace attachment +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_ATTACHMENT_ATTACHMENTUTILS_H_ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/InProcessAttachmentReader.h b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/InProcessAttachmentReader.h index fa4faee429..e024d9323b 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/InProcessAttachmentReader.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/Attachment/InProcessAttachmentReader.h @@ -47,6 +47,8 @@ class InProcessAttachmentReader : public AttachmentReader { * @param index If being constructed from an existing @c SharedDataStream, the index indicates where to read from. * @param reference The position in the stream @c offset is applied to. This parameter defaults to 0, indicating * no offset from the specified reference. + * @param resetOnOverrun If overrun is detected on @c read, whether to close the attachment (default behavior) or + * to reset the read position to where current write position is (and skip all the bytes in between). * @return Returns a new InProcessAttachmentReader, or nullptr if the operation failed. This parameter defaults * to @c ABSOLUTE, indicating offset is relative to the very beginning of the Attachment. */ @@ -54,7 +56,8 @@ class InProcessAttachmentReader : public AttachmentReader { SDSTypeReader::Policy policy, std::shared_ptr sds, SDSTypeIndex offset = 0, - SDSTypeReader::Reference reference = SDSTypeReader::Reference::ABSOLUTE); + SDSTypeReader::Reference reference = SDSTypeReader::Reference::ABSOLUTE, + bool resetOnOverrun = false); /** * Destructor. @@ -79,11 +82,16 @@ class InProcessAttachmentReader : public AttachmentReader { * * @param policy The @c ReaderPolicy of this object. * @param sds The underlying @c SharedDataStream which this object will use. + * @param resetOnOverrun If overrun is detected on @c read, whether to close the attachment (default behavior) or + * to reset the read position to where current write position is (and skip all the bytes in between). */ - InProcessAttachmentReader(SDSTypeReader::Policy policy, std::shared_ptr sds); + InProcessAttachmentReader(SDSTypeReader::Policy policy, std::shared_ptr sds, bool resetOnOverrun); /// The underlying @c SharedDataStream reader. std::shared_ptr m_reader; + + // On @c read overrun, Whether to close the attachment, or reset it to catch up with the write + bool m_resetOnOverrun; }; } // namespace attachment diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/BlockingPolicy.h b/AVSCommon/AVS/include/AVSCommon/AVS/BlockingPolicy.h index 6f978f2ac5..86efc43a3f 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/BlockingPolicy.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/BlockingPolicy.h @@ -16,36 +16,85 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_BLOCKINGPOLICY_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_BLOCKINGPOLICY_H_ +#include #include namespace alexaClientSDK { namespace avsCommon { namespace avs { +/** + * A @c Blocking policy is a way to express what mediums are required by the policy owner + * and whether the policy owner is blocking subsequent directives using the medium. + */ +class BlockingPolicy { +public: + /** + * A policy medium represents a resource the policy owner is using. + */ + struct Medium { + /// The medium enum. + enum MediumEnum { + /// Audio Medium. + AUDIO, + /// Visual Medium. + VISUAL, + /// Number of mediums. This MUST be the last enum member. + COUNT + }; + }; + + /// The mediums used by the policy owner + using Mediums = std::bitset; + + /// Policy uses @c AUDIO medium. + static const Mediums MEDIUM_AUDIO; + + /// Policy uses @c VISUAL medium. + static const Mediums MEDIUM_VISUAL; + + /// Policy uses @c AUDIO and @c VISUAL mediums. + static const Mediums MEDIUMS_AUDIO_AND_VISUAL; + + /** + * Policy uses no medium. + * This should be used for System of setting-type directives. + */ + static const Mediums MEDIUMS_NONE; -/// Enumeration of 'blocking policy' values to be associated with @c AVSDirectives. -enum class BlockingPolicy { /** - * Handling of an @c AVSDirective with this @c BlockingPolicy does NOT block the handling of - * subsequent @c AVSDirectives. + * Constructor + * + * @param mediums The @c Mediums used by the policy owner. + * @param isBlocking Should this policy block another usage of owned mediums until completion. */ - NON_BLOCKING, + BlockingPolicy(const Mediums& mediums = MEDIUMS_NONE, bool isBlocking = true); /** - * Handling of an @c AVSDirective with this @c BlockingPolicy blocks the handling of subsequent @c AVSDirectives - * that have the same @c DialogRequestId. + * Is the policy valid. + * + * @return @c true if the policy is valid, @c false otherwise. */ - BLOCKING, + bool isValid() const; /** - * Handling of an @c AVSDirective with this @c BlockingPolicy is done immediately and does NOT block the handling of - * subsequent @c AVSDirectives. + * Is this policy blocking a @c Medium. + * @return @c true if the given policy is blocking, @c false otherwise. */ - HANDLE_IMMEDIATELY, + bool isBlocking() const; /** - * BlockingPolicy not specified. + * What @c Mediums are used by this policy. + * + * @return The @c Mediums used by the policy */ - NONE + Mediums getMediums() const; + +private: + /// The mediums used by the policy owner. + Mediums m_mediums; + + /// Is this policy blocking other users of its mediums. + bool m_isBlocking; }; /** @@ -55,24 +104,44 @@ enum class BlockingPolicy { * @param policy The policy value to write to the @c ostream as a string. * @return The @c ostream that was passed in and written to. */ -inline std::ostream& operator<<(std::ostream& stream, BlockingPolicy policy) { - switch (policy) { - case BlockingPolicy::NON_BLOCKING: - stream << "NON_BLOCKING"; - break; - case BlockingPolicy::BLOCKING: - stream << "BLOCKING"; - break; - case BlockingPolicy::HANDLE_IMMEDIATELY: - stream << "HANDLE_IMMEDIATELY"; - break; - case BlockingPolicy::NONE: - stream << "NONE"; - break; +inline std::ostream& operator<<(std::ostream& stream, const BlockingPolicy& policy) { + stream << " Mediums:"; + auto mediums = policy.getMediums(); + if (BlockingPolicy::MEDIUM_AUDIO == mediums) { + stream << "MEDIUM_AUDIO"; + } else if (BlockingPolicy::MEDIUM_VISUAL == mediums) { + stream << "MEDIUM_VISUAL"; + } else if (BlockingPolicy::MEDIUMS_AUDIO_AND_VISUAL == mediums) { + stream << "MEDIUMS_AUDIO_AND_VISUAL"; + } else if (BlockingPolicy::MEDIUMS_NONE == mediums) { + stream << "MEDIUMS_NONE"; + } else { + stream << "Unknown"; } + + stream << policy.getMediums() << " .isBlocking:" << (policy.isBlocking() ? "True" : "False"); + return stream; } +/** + * Equal-to operator comparing two @c BlockingPolicys + * + * @param lhs The left side argument. + * @param rhs The right side argument. + * @return true if @c lhs and @c rhs are equal + */ +bool operator==(const BlockingPolicy& lhs, const BlockingPolicy& rhs); + +/** + * Not Equal-to operator comparing two @c BlockingPolicys * + * + * @param lhs The left side argument. + * @param rhs The right side argument. + * @return true if @c lhs and @c rhs are not equal + */ +bool operator!=(const BlockingPolicy& lhs, const BlockingPolicy& rhs); + } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityConfiguration.h b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityConfiguration.h new file mode 100644 index 0000000000..2facf4e068 --- /dev/null +++ b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityConfiguration.h @@ -0,0 +1,96 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CAPABILITYCONFIGURATION_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CAPABILITYCONFIGURATION_H_ + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { + +/// Key for interface type in the @c CapabilityConfiguration map +static const std::string CAPABILITY_INTERFACE_TYPE_KEY = "type"; +/// Key for interface name in the @c CapabilityConfiguration map +static const std::string CAPABILITY_INTERFACE_NAME_KEY = "interface"; +/// Key for interface version in the @c CapabilityConfiguration map +static const std::string CAPABILITY_INTERFACE_VERSION_KEY = "version"; +/// Key for interface configurations in the @c CapabilityConfiguration map +static const std::string CAPABILITY_INTERFACE_CONFIGURATIONS_KEY = "configurations"; + +/** + * Class to encapsulate the capability configuration implemented by a capability agent. + * This is entered as a key/value pair in the map. + * key: CAPABILITY_INTERFACE_TYPE_KEY, value: The interface type being implemented. + * key: CAPABILITY_INTERFACE_NAME_KEY, value: The name of the interface being implemented. + * key: CAPABILITY_INTERFACE_VERSION_KEY, value: The version of the interface being implemented. + * key: CAPABILITY_INTERFACE_CONFIGURATIONS_KEY, value: A json of the configuration values for the interface being + * implemented. + */ +class CapabilityConfiguration { +public: + /** + * Deleted default constructor. + */ + CapabilityConfiguration() = delete; + + /** + * Constructor to initialize with specific values. + * + * @param capabilityConfigIn The @c CapabilityConfiguration value for this instance. + */ + CapabilityConfiguration(const std::unordered_map& capabilityConfigurationIn); + + /// The @c CapabilityConfiguration value of this instance. + const std::unordered_map capabilityConfiguration; +}; + +/** + * Operator == for @c CapabilityConfiguration + * + * @param rhs The left hand side of the == operation. + * @param rhs The right hand side of the == operation. + * @return Whether or not this instance and @c rhs are equivalent. + */ +bool operator==(const CapabilityConfiguration& lhs, const CapabilityConfiguration& rhs); + +/** + * Operator != for @c CapabilityConfiguration + * + * @param rhs The left hand side of the != operation. + * @param rhs The right hand side of the != operation. + * @return Whether or not this instance and @c rhs are not equivalent. + */ +bool operator!=(const CapabilityConfiguration& lhs, const CapabilityConfiguration& rhs); + +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK + +namespace std { + +/** + * @ std::hash() specialization defined for @c CapabilityConfiguration + */ +template <> +struct hash { + size_t operator()(const alexaClientSDK::avsCommon::avs::CapabilityConfiguration& in) const; +}; + +} // namespace std + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CAPABILITYCONFIGURATION_H_ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h b/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h index 85b0d89e78..91614eb3ab 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_DIALOGUXSTATEAGGREGATOR_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_DIALOGUXSTATEAGGREGATOR_H_ +#include #include #include @@ -150,11 +151,12 @@ class DialogUXStateAggregator avsCommon::utils::threading::Executor m_executor; /// Contains the current state of the @c SpeechSynthesizer as reported by @c SpeechSynthesizerObserverInterface - alexaClientSDK::avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState + std::atomic m_speechSynthesizerState; /// Contains the current state of the @c AudioInputProcessor as reported by @c AudioInputProcessorObserverInterface - alexaClientSDK::avsCommon::sdkInterfaces::AudioInputProcessorObserverInterface::State m_audioInputProcessorState; + std::atomic + m_audioInputProcessorState; }; } // namespace avs diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/ExternalMediaPlayer/ExternalMediaAdapterConstants.h b/AVSCommon/AVS/include/AVSCommon/AVS/ExternalMediaPlayer/ExternalMediaAdapterConstants.h new file mode 100644 index 0000000000..e5a4ee03cd --- /dev/null +++ b/AVSCommon/AVS/include/AVSCommon/AVS/ExternalMediaPlayer/ExternalMediaAdapterConstants.h @@ -0,0 +1,70 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_EXTERNALMEDIAPLAYER_EXTERNALMEDIAADAPTERCONSTANTS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_EXTERNALMEDIAPLAYER_EXTERNALMEDIAADAPTERCONSTANTS_H_ + +#include "AVSCommon/AVS/NamespaceAndName.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { +namespace externalMediaPlayer { + +// The key values used in the context payload from External Media Player to AVS. +const char PLAYER_ID[] = "playerId"; +const char ENDPOINT_ID[] = "endpointId"; +const char LOGGED_IN[] = "loggedIn"; +const char USERNAME[] = "username"; +const char IS_GUEST[] = "isGuest"; +const char LAUNCHED[] = "launched"; +const char ACTIVE[] = "active"; + +// The key values used in the context payload from External Media Player to AVS. +const char STATE[] = "state"; +const char OPERATIONS[] = "supportedOperations"; +const char MEDIA[] = "media"; +const char POSITIONINMS[] = "positionMilliseconds"; +const char SHUFFLE[] = "shuffle"; +const char REPEAT[] = "repeat"; +const char FAVORITE[] = "favorite"; +const char PLAYBACK_SOURCE[] = "playbackSource"; +const char TYPE[] = "type"; +const char PLAYBACK_SOURCE_ID[] = "playbackSourceId"; +const char TRACKNAME[] = "trackName"; +const char TRACK_ID[] = "trackId"; +const char TRACK_NUMBER[] = "trackNumber"; +const char ARTIST[] = "artist"; +const char ARTIST_ID[] = "artistId"; +const char ALBUM[] = "album"; +const char ALBUM_ID[] = "albumId"; +const char COVER_URLS[] = "coverUrls"; +const char TINY_URL[] = "tiny"; +const char SMALL_URL[] = "small"; +const char MEDIUM_URL[] = "medium"; +const char LARGE_URL[] = "large"; +const char COVER_ID[] = "coverId"; +const char MEDIA_PROVIDER[] = "mediaProvider"; +const char MEDIA_TYPE[] = "mediaType"; +const char DURATIONINMS[] = "durationInMilliseconds"; +const char VALUE[] = "value"; +const char UNCERTAINITYINMS[] = "uncertaintyInMilliseconds"; + +} // namespace externalMediaPlayer +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_EXTERNALMEDIAPLAYER_EXTERNALMEDIAADAPTERCONSTANTS_H_ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/HandlerAndPolicy.h b/AVSCommon/AVS/include/AVSCommon/AVS/HandlerAndPolicy.h index 2abb726b38..18cb5ceceb 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/HandlerAndPolicy.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/HandlerAndPolicy.h @@ -32,7 +32,7 @@ class HandlerAndPolicy { /** * Constructor to initialize with default property values. */ - HandlerAndPolicy(); + HandlerAndPolicy() = default; /** * Constructor to initialize with specific property values. diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h b/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h index 3c6a49d0ce..86a5789f7b 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h @@ -18,6 +18,7 @@ #include #include +#include #include namespace alexaClientSDK { @@ -42,7 +43,7 @@ class AlexaClientSDKInit { * calls functions of other libraries that have the same requirements and thread safety. * terminate() must be called for each initialize() called. * - * @param jsonConfigurationStreams Vector of @c istreams containing JSON documents from which + * @param jsonStreams Vector of @c istreams containing JSON documents from which * to parse configuration parameters. Streams are processed in the order they appear in the vector. When a * value appears in more than one JSON stream the last processed stream's value overwrites the previous value * (and a debug log entry will be created). This allows for specifying default settings (by providing them @@ -52,7 +53,7 @@ class AlexaClientSDKInit { * * @return Whether the initialization was successful. */ - static bool initialize(const std::vector& jsonStreams); + static bool initialize(const std::vector>& jsonStreams); /** * Uninitialize the Alexa Client SDK. diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/MessageRequest.h b/AVSCommon/AVS/include/AVSCommon/AVS/MessageRequest.h index 8770f2e097..d6dbd0ccb1 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/MessageRequest.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/MessageRequest.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -16,10 +16,12 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_MESSAGEREQUEST_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_MESSAGEREQUEST_H_ +#include #include -#include #include +#include #include +#include #include "AVSCommon/AVS/Attachment/AttachmentReader.h" #include @@ -34,21 +36,51 @@ namespace avs { */ class MessageRequest { public: + /// A struct to hold an @c AttachmentReader alongside its name. + struct NamedReader { + /** + * Constructor. + * + * @param name The name of the message part. + * @param reader The @c AttachmentReader holding the data for this part. + */ + NamedReader(const std::string& name, std::shared_ptr reader) : + name{name}, + reader{reader} { + } + + /// The name of this message part. + std::string name; + + /// The @c AttachmentReader contains the data of this message part. + std::shared_ptr reader; + }; + /** * Constructor. + * * @param jsonContent The message to be sent to AVS. - * @param attachmentReader The attachment data (if present) to be sent to AVS along with the message. - * Defaults to @c nullptr. + * @param uriPathExtension An optional uri path extension which will be appended to the base url of the AVS. + * endpoint. If not specified, the default AVS path extension should be used by the sender implementation. */ - MessageRequest( - const std::string& jsonContent, - std::shared_ptr attachmentReader = nullptr); + MessageRequest(const std::string& jsonContent, const std::string& uriPathExtension = ""); /** * Destructor. */ virtual ~MessageRequest(); + /** + * Adds an attachment reader to the message. The attachment data will be the next + * part in the message to be sent to AVS. + * @note: The order by which the message attachments sent to AVS + * is the one by which they have been added to it. + * + * @param name The name of the message part containing the attachment data. + * @param attachmentReader The attachment data to be sent to AVS along with the message. + */ + void addAttachmentReader(const std::string& name, std::shared_ptr attachmentReader); + /** * Retrieves the JSON content to be sent to AVS. * @@ -57,11 +89,27 @@ class MessageRequest { std::string getJsonContent(); /** - * Retrieves the AttachmentReader of the Attachment data to be sent to AVS. + * Retrieves the path extension to be appended to the base URL when sending. * - * @return The AttachmentReader of the Attachment data to be sent to AVS. + * @return The path extension to be appended to the base URL when sending. */ - std::shared_ptr getAttachmentReader(); + std::string getUriPathExtension(); + + /** + * Gets the number of @c AttachmentReaders in this message. + * + * @return The number of readers in this message. + */ + int attachmentReadersCount(); + + /** + * Retrieves the ith AttachmentReader in the message. + * + * @param index The index of the @c AttachmentReader to retrieve. + * @return @c NamedReader of the ith attachment in the message. + * A null pointer is returned when @c index is out of bound. + */ + std::shared_ptr getAttachmentReader(size_t index); /** * This is called once the send request has completed. The status parameter indicates success or failure. @@ -90,14 +138,6 @@ class MessageRequest { */ void removeObserver(std::shared_ptr observer); - /** - * A function to evaluate if the given status reflects receipt of the message by the server. - * - * @param status The status being queried. - * @return Whether the status reflects receipt of the message by the server. - */ - static bool isServerStatus(sdkInterfaces::MessageRequestObserverInterface::Status status); - protected: /// Mutex to guard access of m_observers. std::mutex m_observerMutex; @@ -108,8 +148,11 @@ class MessageRequest { /// The JSON content to be sent to AVS. std::string m_jsonContent; - /// The AttachmentReader of the Attachment data to be sent to AVS. - std::shared_ptr m_attachmentReader; + /// The path extension to be appended to the base URL when sending. + std::string m_uriPathExtension; + + /// The AttachmentReaders of the Attachments data to be sent to AVS. + std::vector> m_readers; }; } // namespace avs diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/PlaybackButtons.h b/AVSCommon/AVS/include/AVSCommon/AVS/PlaybackButtons.h index 5c24196a29..7da6a585cc 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/PlaybackButtons.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/PlaybackButtons.h @@ -34,7 +34,30 @@ enum class PlaybackButton { NEXT, /// Playback Controller 'Previous' button. - PREVIOUS + PREVIOUS, + + // Playback Controller 'Skip Forward' button. + SKIP_FORWARD, + + // Playback Controller 'Skip Backward' button. + SKIP_BACKWARD +}; + +enum class PlaybackToggle { + // Playback Controller 'Shuffle' toggle. + SHUFFLE, + + // Playback Controller 'Loop' toggle. + LOOP, + + // Playback Controller 'Repeat' toggle. + REPEAT, + + // Playback Controller 'Thumbs Up' toggle. + THUMBS_UP, + + // Playback Controller 'Thumbs Down' toggle. + THUMBS_DOWN }; /** @@ -53,11 +76,38 @@ inline std::string PlaybackButtonToString(PlaybackButton button) { return "Previous"; case PlaybackButton::NEXT: return "Next"; + case PlaybackButton::SKIP_FORWARD: + return "Skip_Forward"; + case PlaybackButton::SKIP_BACKWARD: + return "Skip_Backward"; } return "unknown playbackButton"; } +/** + * Convert a @c PlaybackToggle to @c std::string. + * + * @param toggle The @c PlaybackToggle to convert. + * @return The representation of @c toggle. + */ +inline std::string PlaybackToggleToString(PlaybackToggle toggle) { + switch (toggle) { + case PlaybackToggle::SHUFFLE: + return "Shuffle"; + case PlaybackToggle::LOOP: + return "Loop"; + case PlaybackToggle::REPEAT: + return "Repeat"; + case PlaybackToggle::THUMBS_UP: + return "Thumbs_Up"; + case PlaybackToggle::THUMBS_DOWN: + return "Thumbs_Down"; + } + + return "unknown playbackToggle"; +} + /** * A macro to be used in operator << overload function to help with the translation * of @c Button to string. @@ -69,6 +119,17 @@ inline std::string PlaybackButtonToString(PlaybackButton button) { return stream << #button; \ } +/** + * A macro to be used in operator << overload function to help with the translation + * of @c Toggle to string. + * + * @param toggle The @c Toggle that needs to be translated. + */ +#define ACSDK_PLAYBACK_TOGGLE_TO_STRING(toggle) \ + case PlaybackToggle::toggle: { \ + return stream << #toggle; \ + } + /** * Write a @c Button value to an @c ostream as a string. * @@ -80,13 +141,38 @@ inline std::ostream& operator<<(std::ostream& stream, const PlaybackButton& butt switch (button) { ACSDK_PLAYBACK_BUTTON_TO_STRING(PLAY); ACSDK_PLAYBACK_BUTTON_TO_STRING(PAUSE); - ACSDK_PLAYBACK_BUTTON_TO_STRING(NEXT); ACSDK_PLAYBACK_BUTTON_TO_STRING(PREVIOUS); + ACSDK_PLAYBACK_BUTTON_TO_STRING(NEXT); + ACSDK_PLAYBACK_BUTTON_TO_STRING(SKIP_FORWARD); + ACSDK_PLAYBACK_BUTTON_TO_STRING(SKIP_BACKWARD); + } + + return stream; +} + +#undef ACSDK_PLAYBACK_BUTTON_TO_STRING + +/** + * Write a @c Toggle value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param toggle The @c Toggle value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const PlaybackToggle& toggle) { + switch (toggle) { + ACSDK_PLAYBACK_TOGGLE_TO_STRING(SHUFFLE); + ACSDK_PLAYBACK_TOGGLE_TO_STRING(LOOP); + ACSDK_PLAYBACK_TOGGLE_TO_STRING(REPEAT); + ACSDK_PLAYBACK_TOGGLE_TO_STRING(THUMBS_UP); + ACSDK_PLAYBACK_TOGGLE_TO_STRING(THUMBS_DOWN); } return stream; } +#undef ACSDK_PLAYBACK_TOGGLE_TO_STRING + } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK @@ -101,6 +187,16 @@ struct hash { return std::hash{}(PlaybackButtonToString(button)); } }; +/** + * @ std::hash() specialization defined to allow @c PlaybackToggle to be used as a key in @c std::unordered_map. + */ +template <> +struct hash { + inline size_t operator()(const alexaClientSDK::avsCommon::avs::PlaybackToggle& toggle) const { + return std::hash{}(PlaybackToggleToString(toggle)); + } +}; + } // namespace std #endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_PLAYBACKBUTTONS_H_ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/Requester.h b/AVSCommon/AVS/include/AVSCommon/AVS/Requester.h new file mode 100644 index 0000000000..d28053af73 --- /dev/null +++ b/AVSCommon/AVS/include/AVSCommon/AVS/Requester.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_REQUESTER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_REQUESTER_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { + +/// An enum class indicating whether an operation originated from a Device or Cloud (AVS). +enum class Requester { + /// The request came from AVS as a result of a directive. + CLOUD, + /// The request came from the device. Can be from either the AVS device or a connected device. + DEVICE +}; + +/** + * Converts an @c Requester enum to a string. + * + * @param requester The @c Requester enum. + * @return The string representation of the enum. + */ +inline std::string requesterToString(Requester requester) { + switch (requester) { + case Requester::CLOUD: + return "CLOUD"; + case Requester::DEVICE: + return "DEVICE"; + } + return "UNKNOWN"; +} + +/** + * Write a @c Requester value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param requester The @c Requester value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, Requester requester) { + return stream << requesterToString(requester); +} + +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_REQUESTER_H_ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/SpeakerConstants/SpeakerConstants.h b/AVSCommon/AVS/include/AVSCommon/AVS/SpeakerConstants/SpeakerConstants.h index b6f1f08ec9..01c792b877 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/SpeakerConstants/SpeakerConstants.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/SpeakerConstants/SpeakerConstants.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -38,9 +38,12 @@ const int8_t AVS_ADJUST_VOLUME_MIN = -100; /// AVS adjustVolume Maximum. const int8_t AVS_ADJUST_VOLUME_MAX = 100; +/// Default unmute volume level. +const int8_t MIN_UNMUTE_VOLUME = 10; + } // namespace speakerConstants } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_SPEAKERCONSTANTS_SPEAKERCONSTANTS_H_ +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_SPEAKERCONSTANTS_SPEAKERCONSTANTS_H_ \ No newline at end of file diff --git a/AVSCommon/AVS/src/AVSDirective.cpp b/AVSCommon/AVS/src/AVSDirective.cpp index 4cd1d099a3..b54635437f 100644 --- a/AVSCommon/AVS/src/AVSDirective.cpp +++ b/AVSCommon/AVS/src/AVSDirective.cpp @@ -14,14 +14,35 @@ */ #include "AVSCommon/AVS/AVSDirective.h" +#include #include "AVSCommon/Utils/Logger/Logger.h" +#include + namespace alexaClientSDK { namespace avsCommon { namespace avs { -using namespace avsCommon::utils; using namespace avsCommon::avs::attachment; +using namespace avsCommon::utils; +using namespace avsCommon::utils::json::jsonUtils; + +using namespace rapidjson; + +/// JSON key to get the directive object of a message. +static const std::string JSON_MESSAGE_DIRECTIVE_KEY = "directive"; +/// JSON key to get the header object of a message. +static const std::string JSON_MESSAGE_HEADER_KEY = "header"; +/// JSON key to get the namespace value of a header. +static const std::string JSON_MESSAGE_NAMESPACE_KEY = "namespace"; +/// JSON key to get the name value of a header. +static const std::string JSON_MESSAGE_NAME_KEY = "name"; +/// JSON key to get the messageId value of a header. +static const std::string JSON_MESSAGE_ID_KEY = "messageId"; +/// JSON key to get the dialogRequestId value of a header. +static const std::string JSON_MESSAGE_DIALOG_REQUEST_ID_KEY = "dialogRequestId"; +/// JSON key to get the payload object of a message. +static const std::string JSON_MESSAGE_PAYLOAD_KEY = "payload"; /// String to identify log entries originating from this file. static const std::string TAG("AvsDirective"); @@ -33,6 +54,146 @@ static const std::string TAG("AvsDirective"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) +/** + * Utility function to attempt a JSON parse of the passed in string, and inform the caller if this succeeded or failed. + * + * @param unparsedDirective The unparsed JSON string which should represent an AVS Directive. + * @param[out] document The rapidjson Document which will be set to the parsed structure if the parse succeeds. + * @return Whether the parse was successful. + */ +static bool parseDocument(const std::string& unparsedDirective, Document* document) { + if (!document) { + ACSDK_ERROR(LX("parseDocumentFailed").m("nullptr document")); + return false; + } + + if (!parseJSON(unparsedDirective, document)) { + ACSDK_ERROR(LX("parseDocumentFailed").d("uparsedDirective", unparsedDirective)); + return false; + } + + return true; +} + +/** + * Utility function to parse the header from a rapidjson document structure. + * + * @param document The constructed document tree + * @param [out] parseStatus An out parameter to express if the parse was successful + * @return A pointer to an AVSMessageHeader object, or nullptr if the parse fails. + */ +static std::shared_ptr parseHeader(const Document& document, AVSDirective::ParseStatus* parseStatus) { + if (!parseStatus) { + ACSDK_ERROR(LX("parseHeaderFailed").m("nullptr parseStatus")); + return nullptr; + } + + *parseStatus = AVSDirective::ParseStatus::SUCCESS; + + // Get iterators to child nodes. + Value::ConstMemberIterator directiveIt; + if (!findNode(document, JSON_MESSAGE_DIRECTIVE_KEY, &directiveIt)) { + *parseStatus = AVSDirective::ParseStatus::ERROR_MISSING_DIRECTIVE_KEY; + return nullptr; + } + + Value::ConstMemberIterator headerIt; + if (!findNode(directiveIt->value, JSON_MESSAGE_HEADER_KEY, &headerIt)) { + *parseStatus = AVSDirective::ParseStatus::ERROR_MISSING_HEADER_KEY; + return nullptr; + } + + // Now, extract values. + std::string avsNamespace; + if (!retrieveValue(headerIt->value, JSON_MESSAGE_NAMESPACE_KEY, &avsNamespace)) { + *parseStatus = AVSDirective::ParseStatus::ERROR_MISSING_NAMESPACE_KEY; + return nullptr; + } + + std::string avsName; + if (!retrieveValue(headerIt->value, JSON_MESSAGE_NAME_KEY, &avsName)) { + *parseStatus = AVSDirective::ParseStatus::ERROR_MISSING_NAME_KEY; + return nullptr; + } + + std::string avsMessageId; + if (!retrieveValue(headerIt->value, JSON_MESSAGE_ID_KEY, &avsMessageId)) { + *parseStatus = AVSDirective::ParseStatus::ERROR_MISSING_MESSAGE_ID_KEY; + return nullptr; + } + + // This is an optional header field - it's ok if it is not present. + // Avoid jsonUtils::retrieveValue because it logs a missing value as an ERROR. + std::string avsDialogRequestId; + auto it = headerIt->value.FindMember(JSON_MESSAGE_DIALOG_REQUEST_ID_KEY); + if (it != headerIt->value.MemberEnd()) { + convertToValue(it->value, &avsDialogRequestId); + } + + return std::make_shared(avsNamespace, avsName, avsMessageId, avsDialogRequestId); +} + +/** + * Utility function to parse the payload from a rapidjson document structure. + * + * @param document The constructed document tree + * @param [out] parseStatus An out parameter to express if the parse was successful + * @return The payload content if it is available. + */ +static std::string parsePayload(const Document& document, AVSDirective::ParseStatus* parseStatus) { + if (!parseStatus) { + ACSDK_ERROR(LX("parsePayloadFailed").m("nullptr parseStatus")); + return ""; + } + + Value::ConstMemberIterator directiveIt; + if (!findNode(document, JSON_MESSAGE_DIRECTIVE_KEY, &directiveIt)) { + *parseStatus = AVSDirective::ParseStatus::ERROR_MISSING_DIRECTIVE_KEY; + return ""; + } + + std::string payload; + if (!retrieveValue(directiveIt->value, JSON_MESSAGE_PAYLOAD_KEY, &payload)) { + *parseStatus = AVSDirective::ParseStatus::ERROR_MISSING_PAYLOAD_KEY; + return ""; + } + + *parseStatus = AVSDirective::ParseStatus::SUCCESS; + return payload; +} + +std::pair, AVSDirective::ParseStatus> AVSDirective::create( + const std::string& unparsedDirective, + std::shared_ptr attachmentManager, + const std::string& attachmentContextId) { + std::pair, ParseStatus> result; + result.second = ParseStatus::SUCCESS; + + Document document; + if (!parseDocument(unparsedDirective, &document)) { + ACSDK_ERROR(LX("createFailed").m("failed to parse JSON")); + result.second = ParseStatus::ERROR_INVALID_JSON; + return result; + } + + auto header = parseHeader(document, &(result.second)); + if (ParseStatus::SUCCESS != result.second) { + ACSDK_ERROR(LX("createFailed").m("failed to parse header")); + return result; + } + + auto payload = parsePayload(document, &(result.second)); + if (ParseStatus::SUCCESS != result.second) { + ACSDK_ERROR(LX("createFailed").m("failed to parse payload")); + return result; + } + + result.first = std::unique_ptr( + new AVSDirective(unparsedDirective, header, payload, attachmentManager, attachmentContextId)); + + return result; +} + std::unique_ptr AVSDirective::create( const std::string& unparsedDirective, std::shared_ptr avsMessageHeader, diff --git a/AVSCommon/AVS/src/AbstractConnection.cpp b/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp similarity index 84% rename from AVSCommon/AVS/src/AbstractConnection.cpp rename to AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp index e88b162616..6af14d1685 100644 --- a/AVSCommon/AVS/src/AbstractConnection.cpp +++ b/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -#include "AVSCommon/AVS/AbstractConnection.h" +#include "AVSCommon/AVS/AbstractAVSConnectionManager.h" #include "AVSCommon/Utils/Logger/Logger.h" namespace alexaClientSDK { @@ -23,7 +23,7 @@ namespace avs { using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("AbstractConnection"); +static const std::string TAG("AbstractAVSConnectionManager"); /** * Create a LogEntry using this file's TAG and the specified event string. @@ -32,14 +32,15 @@ static const std::string TAG("AbstractConnection"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) -AbstractConnection::AbstractConnection( +AbstractAVSConnectionManager::AbstractAVSConnectionManager( std::unordered_set> observers) : m_connectionStatus{ConnectionStatusObserverInterface::Status::DISCONNECTED}, m_connectionChangedReason{ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST}, m_connectionStatusObservers{observers} { } -void AbstractConnection::addConnectionStatusObserver(std::shared_ptr observer) { +void AbstractAVSConnectionManager::addConnectionStatusObserver( + std::shared_ptr observer) { if (!observer) { ACSDK_ERROR(LX("addConnectionStatusObserverFailed").d("reason", "nullObserver")); return; @@ -56,7 +57,7 @@ void AbstractConnection::addConnectionStatusObserver(std::shared_ptr observer) { if (!observer) { ACSDK_ERROR(LX("removeConnectionStatusObserverFailed").d("reason", "nullObserver")); @@ -67,7 +68,7 @@ void AbstractConnection::removeConnectionStatusObserver( m_connectionStatusObservers.erase(observer); } -void AbstractConnection::updateConnectionStatus( +void AbstractAVSConnectionManager::updateConnectionStatus( ConnectionStatusObserverInterface::Status status, ConnectionStatusObserverInterface::ChangedReason reason) { std::unique_lock lock{m_mutex}; @@ -78,7 +79,7 @@ void AbstractConnection::updateConnectionStatus( notifyObservers(); } -void AbstractConnection::notifyObservers() { +void AbstractAVSConnectionManager::notifyObservers() { std::unique_lock lock{m_mutex}; auto observers = m_connectionStatusObservers; auto localStatus = m_connectionStatus; @@ -90,7 +91,7 @@ void AbstractConnection::notifyObservers() { } } -void AbstractConnection::clearObservers() { +void AbstractAVSConnectionManager::clearObservers() { std::lock_guard lock{m_mutex}; m_connectionStatusObservers.clear(); } diff --git a/AVSCommon/AVS/src/AlexaClientSDKInit.cpp b/AVSCommon/AVS/src/AlexaClientSDKInit.cpp index b93f3adc64..e43658ec89 100644 --- a/AVSCommon/AVS/src/AlexaClientSDKInit.cpp +++ b/AVSCommon/AVS/src/AlexaClientSDKInit.cpp @@ -41,7 +41,7 @@ bool AlexaClientSDKInit::isInitialized() { return g_isInitialized > 0; } -bool AlexaClientSDKInit::initialize(const std::vector& jsonStreams) { +bool AlexaClientSDKInit::initialize(const std::vector>& jsonStreams) { if (!(curl_version_info(CURLVERSION_NOW)->features & CURL_VERSION_HTTP2)) { ACSDK_ERROR(LX("initializeFailed").d("reason", "curlDoesNotSupportHTTP2")); return false; diff --git a/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp b/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp new file mode 100644 index 0000000000..d963d3b237 --- /dev/null +++ b/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include +#include "AVSCommon/AVS/Attachment/AttachmentUtils.h" +#include +#include "AVSCommon/Utils/Logger/Logger.h" + +using namespace alexaClientSDK::avsCommon::utils::sds; + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { +namespace attachment { + +/// String to identify log entries originating from this file. +static const std::string TAG("AttachmentUtils"); + +/// Maximum size of the reader is 4KB. +static const std::size_t MAX_READER_SIZE = 4 * 1024; + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +std::unique_ptr AttachmentUtils::createAttachmentReader(const std::vector& srcBuffer) { + auto bufferSize = InProcessSDS::calculateBufferSize(srcBuffer.size()); + auto buffer = std::make_shared(bufferSize); + auto stream = InProcessSDS::create(buffer); + + if (!stream) { + ACSDK_ERROR(LX("createAttachmentReaderFailed").d("reason", "Failed to create stream")); + return nullptr; + } + + auto writer = stream->createWriter(InProcessSDS::Writer::Policy::BLOCKING); + if (!writer) { + ACSDK_ERROR(LX("createAttachmentReaderFailed").d("reason", "Failed to create writer")); + return nullptr; + } + + if (srcBuffer.size() > MAX_READER_SIZE) { + ACSDK_WARN(LX("createAttachmentReaderExceptionSize").d("size", srcBuffer.size())); + } + + auto numWritten = writer->write(srcBuffer.data(), srcBuffer.size()); + if (numWritten < 0 || static_cast(numWritten) != srcBuffer.size()) { + ACSDK_ERROR(LX("createAttachmentReaderFailed") + .d("reason", "writing failed") + .d("buffer size", srcBuffer.size()) + .d("numWritten", numWritten)); + return nullptr; + } + + auto attachmentReader = + InProcessAttachmentReader::create(InProcessSDS::Reader::Policy::BLOCKING, std::move(stream)); + + return std::move(attachmentReader); +} +} // namespace attachment +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/AVS/src/Attachment/InProcessAttachmentReader.cpp b/AVSCommon/AVS/src/Attachment/InProcessAttachmentReader.cpp index f7b16f73f7..8d697420ff 100644 --- a/AVSCommon/AVS/src/Attachment/InProcessAttachmentReader.cpp +++ b/AVSCommon/AVS/src/Attachment/InProcessAttachmentReader.cpp @@ -37,8 +37,10 @@ std::unique_ptr InProcessAttachmentReader::create( SDSTypeReader::Policy policy, std::shared_ptr sds, SDSTypeIndex offset, - SDSTypeReader::Reference reference) { - auto reader = std::unique_ptr(new InProcessAttachmentReader(policy, sds)); + SDSTypeReader::Reference reference, + bool resetOnOverrun) { + auto reader = + std::unique_ptr(new InProcessAttachmentReader(policy, sds, resetOnOverrun)); if (!reader->m_reader) { ACSDK_ERROR(LX("createFailed").d("reason", "object not fully created")); @@ -53,7 +55,11 @@ std::unique_ptr InProcessAttachmentReader::create( return reader; } -InProcessAttachmentReader::InProcessAttachmentReader(SDSTypeReader::Policy policy, std::shared_ptr sds) { +InProcessAttachmentReader::InProcessAttachmentReader( + SDSTypeReader::Policy policy, + std::shared_ptr sds, + bool resetOnOverrun) : + m_resetOnOverrun{resetOnOverrun} { if (!sds) { ACSDK_ERROR(LX("ConstructorFailed").d("reason", "SDS parameter is nullptr")); return; @@ -121,11 +127,20 @@ std::size_t InProcessAttachmentReader::read( if (readResult < 0) { switch (readResult) { - // This means the writer has overwritten the reader. An attachment cannot recover from this. + // This means the writer has overwritten the reader. case SDSType::Reader::Error::OVERRUN: - *readStatus = ReadStatus::ERROR_OVERRUN; - ACSDK_ERROR(LX("readFailed").d("reason", "memory overrun by writer")); - close(); + if (m_resetOnOverrun) { + // An attachment's read position will be reset to current writer position. + // Subsequent reads will deliver data from current writer position onward. + *readStatus = ReadStatus::OK_OVERRUN_RESET; + ACSDK_DEBUG5(LX("readFailed").d("reason", "memory overrun by writer")); + m_reader->seek(0, SDSTypeReader::Reference::BEFORE_WRITER); + } else { + // An attachment cannot recover from this. + *readStatus = ReadStatus::ERROR_OVERRUN; + ACSDK_ERROR(LX("readFailed").d("reason", "memory overrun by writer")); + close(); + } break; // This means there is still an active writer, but no data. A read would block if the policy was blocking. @@ -147,7 +162,7 @@ std::size_t InProcessAttachmentReader::read( } else if (0 == readResult) { *readStatus = ReadStatus::CLOSED; - ACSDK_INFO(LX("readFailed").d("reason", "SDS is closed")); + ACSDK_DEBUG0(LX("readFailed").d("reason", "SDS is closed")); } else { bytesRead = static_cast(readResult) * wordSize; } diff --git a/AVSCommon/AVS/src/BlockingPolicy.cpp b/AVSCommon/AVS/src/BlockingPolicy.cpp new file mode 100644 index 0000000000..a82b2713cd --- /dev/null +++ b/AVSCommon/AVS/src/BlockingPolicy.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { + +using namespace rapidjson; +using namespace avsCommon::avs; +using namespace avsCommon::utils::json; + +/// Flag indicating @c AUDIO medium is used. +static const long MEDIUM_FLAG_AUDIO = 1; + +/// Flag indicating @c VISUAL medium is used. +static const long MEDIUM_FLAG_VISUAL = 2; + +/// String to identify log entries originating from this file. +static const std::string TAG("BlockingPolicy"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +const BlockingPolicy::Mediums BlockingPolicy::MEDIUM_AUDIO{MEDIUM_FLAG_AUDIO}; +const BlockingPolicy::Mediums BlockingPolicy::MEDIUM_VISUAL{MEDIUM_FLAG_VISUAL}; +const BlockingPolicy::Mediums BlockingPolicy::MEDIUMS_AUDIO_AND_VISUAL{MEDIUM_FLAG_AUDIO | MEDIUM_FLAG_VISUAL}; +const BlockingPolicy::Mediums BlockingPolicy::MEDIUMS_NONE{}; + +BlockingPolicy::BlockingPolicy(const Mediums& mediums, bool isBlocking) : m_mediums{mediums}, m_isBlocking{isBlocking} { +} + +bool BlockingPolicy::isValid() const { + // We use neither mediums + blocking to indicate invalid policy + // as this is invalid combination. + auto isValid = !((MEDIUMS_NONE == m_mediums) && m_isBlocking); + + return isValid; +} + +bool BlockingPolicy::isBlocking() const { + return m_isBlocking; +} + +BlockingPolicy::Mediums BlockingPolicy::getMediums() const { + return m_mediums; +} + +bool operator==(const BlockingPolicy& lhs, const BlockingPolicy& rhs) { + return ((lhs.getMediums() == rhs.getMediums()) && (lhs.isBlocking() == rhs.isBlocking())); +} + +bool operator!=(const BlockingPolicy& lhs, const BlockingPolicy& rhs) { + return !(lhs == rhs); +} + +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK \ No newline at end of file diff --git a/AVSCommon/AVS/src/CapabilityAgent.cpp b/AVSCommon/AVS/src/CapabilityAgent.cpp index 243f63f02d..7834427654 100644 --- a/AVSCommon/AVS/src/CapabilityAgent.cpp +++ b/AVSCommon/AVS/src/CapabilityAgent.cpp @@ -113,11 +113,22 @@ void CapabilityAgent::sendExceptionEncounteredAndReportFailed( std::shared_ptr info, const std::string& message, avsCommon::avs::ExceptionErrorType type) { - m_exceptionEncounteredSender->sendExceptionEncountered(info->directive->getUnparsedDirective(), type, message); - if (info && info->result) { - info->result->setFailed(message); + if (info) { + if (info->directive) { + m_exceptionEncounteredSender->sendExceptionEncountered( + info->directive->getUnparsedDirective(), type, message); + removeDirective(info->directive->getMessageId()); + } else { + ACSDK_ERROR(LX("sendExceptionEncounteredAndReportFailed").d("reason", "infoHasNoDirective")); + } + if (info->result) { + info->result->setFailed(message); + } else { + ACSDK_ERROR(LX("sendExceptionEncounteredAndReportFailed").d("reason", "infoHasNoResult")); + } + } else { + ACSDK_ERROR(LX("sendExceptionEncounteredAndReportFailed").d("reason", "infoNotFound")); } - removeDirective(info->directive->getMessageId()); } void CapabilityAgent::onDeregistered() { diff --git a/AVSCommon/AVS/src/CapabilityConfiguration.cpp b/AVSCommon/AVS/src/CapabilityConfiguration.cpp new file mode 100644 index 0000000000..ab5199bb3d --- /dev/null +++ b/AVSCommon/AVS/src/CapabilityConfiguration.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "AVSCommon/AVS/CapabilityConfiguration.h" + +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { + +CapabilityConfiguration::CapabilityConfiguration( + const std::unordered_map& capabilityConfigurationIn) : + capabilityConfiguration{capabilityConfigurationIn} { +} + +bool operator==(const CapabilityConfiguration& lhs, const CapabilityConfiguration& rhs) { + if (lhs.capabilityConfiguration.size() != rhs.capabilityConfiguration.size()) { + return false; + } + + for (const auto& lhsIterator : lhs.capabilityConfiguration) { + std::string lhsKey = lhsIterator.first; + std::string lhsValue = lhsIterator.second; + + auto rhsIterator = rhs.capabilityConfiguration.find(lhsKey); + if (rhsIterator == rhs.capabilityConfiguration.end()) { + return false; + } + + std::string rhsValue = rhsIterator->second; + if (lhsValue.compare(rhsValue) != 0) { + return false; + } + } + + return true; +} + +bool operator!=(const CapabilityConfiguration& lhs, const CapabilityConfiguration& rhs) { + return !(lhs == rhs); +} + +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK + +namespace std { + +size_t hash::operator()( + const alexaClientSDK::avsCommon::avs::CapabilityConfiguration& in) const { + std::size_t seed = 0; + /// Prefix and separator to construct a single string that represents a key/value pair. + const std::string keyPrefix = "key:"; + const std::string valuePrefix = "value:"; + const std::string keyValueSeparator = ","; + + /// Sort the entries so that the hash values are the same. Since the map is unordered, we can't guarantee any order. + std::vector> capabilityConfigurationElems( + in.capabilityConfiguration.begin(), in.capabilityConfiguration.end()); + std::sort(capabilityConfigurationElems.begin(), capabilityConfigurationElems.end()); + + for (const auto& configIterator : capabilityConfigurationElems) { + const std::string mapEntryForHash = + keyPrefix + configIterator.first + keyValueSeparator + valuePrefix + configIterator.second; + alexaClientSDK::avsCommon::utils::functional::hashCombine(seed, mapEntryForHash); + } + + return seed; +}; + +} // namespace std diff --git a/AVSCommon/AVS/src/DialogUXStateAggregator.cpp b/AVSCommon/AVS/src/DialogUXStateAggregator.cpp index 02707ad64d..a7fd36a49a 100644 --- a/AVSCommon/AVS/src/DialogUXStateAggregator.cpp +++ b/AVSCommon/AVS/src/DialogUXStateAggregator.cpp @@ -76,7 +76,7 @@ void DialogUXStateAggregator::onStateChanged(AudioInputProcessorObserverInterfac return; case AudioInputProcessorObserverInterface::State::EXPECTING_SPEECH: onActivityStarted(); - setState(DialogUXStateObserverInterface::DialogUXState::LISTENING); + setState(DialogUXStateObserverInterface::DialogUXState::EXPECTING); return; case AudioInputProcessorObserverInterface::State::BUSY: setState(DialogUXStateObserverInterface::DialogUXState::THINKING); @@ -117,7 +117,8 @@ void DialogUXStateAggregator::onStateChanged(SpeechSynthesizerObserverInterface: void DialogUXStateAggregator::receive(const std::string& contextId, const std::string& message) { m_executor.submit([this]() { - if (DialogUXStateObserverInterface::DialogUXState::THINKING == m_currentState) { + if (DialogUXStateObserverInterface::DialogUXState::THINKING == m_currentState && + SpeechSynthesizerObserverInterface::SpeechSynthesizerState::GAINING_FOCUS != m_speechSynthesizerState) { /* * Stop the long timer and start a short timer so that either the state will change (i.e. Speech begins) * or we automatically go to idle after the short timeout (i.e. the directive received isn't related to diff --git a/AVSCommon/AVS/src/ExternalMediaPlayer/AdapterUtils.cpp b/AVSCommon/AVS/src/ExternalMediaPlayer/AdapterUtils.cpp index 5c20ceb6d3..169dda8ce3 100644 --- a/AVSCommon/AVS/src/ExternalMediaPlayer/AdapterUtils.cpp +++ b/AVSCommon/AVS/src/ExternalMediaPlayer/AdapterUtils.cpp @@ -14,6 +14,7 @@ */ #include "AVSCommon/AVS/ExternalMediaPlayer/AdapterUtils.h" +#include "AVSCommon/AVS/ExternalMediaPlayer/ExternalMediaAdapterConstants.h" #include #include @@ -51,45 +52,6 @@ const NamespaceAndName LOGOUT("ExternalMediaPlayer", "Logout"); const NamespaceAndName PLAYER_EVENT("ExternalMediaPlayer", "PlayerEvent"); const NamespaceAndName PLAYER_ERROR_EVENT("ExternalMediaPlayer", "PlayerError"); -// The key values used in the context payload from External Media Player to AVS. -const char PLAYER_ID[] = "playerId"; -const char ENDPOINT_ID[] = "endpointId"; -const char LOGGED_IN[] = "loggedIn"; -const char USERNAME[] = "username"; -const char IS_GUEST[] = "isGuest"; -const char LAUNCHED[] = "launched"; -const char ACTIVE[] = "active"; - -// The key values used in the context payload from External Media Player to AVS. -const char STATE[] = "state"; -const char OPERATIONS[] = "supportedOperations"; -const char MEDIA[] = "media"; -const char POSITIONINMS[] = "positionMilliseconds"; -const char SHUFFLE[] = "shuffle"; -const char REPEAT[] = "repeat"; -const char FAVORITE[] = "favorite"; -const char PLAYBACK_SOURCE[] = "playbackSource"; -const char TYPE[] = "type"; -const char PLAYBACK_SOURCE_ID[] = "playbackSourceId"; -const char TRACKNAME[] = "trackName"; -const char TRACK_ID[] = "trackId"; -const char TRACK_NUMBER[] = "trackNumber"; -const char ARTIST[] = "artist"; -const char ARTIST_ID[] = "artistId"; -const char ALBUM[] = "album"; -const char ALBUM_ID[] = "albumId"; -const char COVER_URLS[] = "coverUrls"; -const char TINY_URL[] = "tiny"; -const char SMALL_URL[] = "small"; -const char MEDIUM_URL[] = "medium"; -const char LARGE_URL[] = "large"; -const char COVER_ID[] = "coverId"; -const char MEDIA_PROVIDER[] = "mediaProvider"; -const char MEDIA_TYPE[] = "mediaType"; -const char DURATIONINMS[] = "durationInMilliseconds"; -const char VALUE[] = "value"; -const char UNCERTAINITYINMS[] = "uncertaintyInMilliseconds"; - /// The default state of a player. const char DEFAULT_STATE[] = "IDLE"; @@ -117,10 +79,7 @@ rapidjson::Value buildPlaybackState( playerJson.AddMember(STATE, playbackState.state, allocator); auto operations = buildSupportedOperations(playbackState.supportedOperations, allocator); playerJson.AddMember(OPERATIONS, operations, allocator); - playerJson.AddMember( - POSITIONINMS, - std::chrono::duration_cast(playbackState.trackOffset).count(), - allocator); + playerJson.AddMember(POSITIONINMS, static_cast(playbackState.trackOffset.count()), allocator); playerJson.AddMember(SHUFFLE, SHUFFLE_STATUS_STRING(playbackState.shuffleEnabled), allocator); playerJson.AddMember(REPEAT, REPEAT_STATUS_STRING(playbackState.repeatEnabled), allocator); playerJson.AddMember(FAVORITE, RatingToString(playbackState.favorites), allocator); @@ -149,8 +108,7 @@ rapidjson::Value buildPlaybackState( value.AddMember(COVER_ID, playbackState.coverId, allocator); value.AddMember(MEDIA_PROVIDER, playbackState.mediaProvider, allocator); value.AddMember(MEDIA_TYPE, MediaTypeToString(playbackState.mediaType), allocator); - value.AddMember( - DURATIONINMS, std::chrono::duration_cast(playbackState.duration).count(), allocator); + value.AddMember(DURATIONINMS, static_cast(playbackState.duration.count()), allocator); media.AddMember(VALUE, value, allocator); playerJson.AddMember(MEDIA, media, allocator); diff --git a/AVSCommon/AVS/src/HandlerAndPolicy.cpp b/AVSCommon/AVS/src/HandlerAndPolicy.cpp index f207c1e85a..a7f4c4b3b1 100644 --- a/AVSCommon/AVS/src/HandlerAndPolicy.cpp +++ b/AVSCommon/AVS/src/HandlerAndPolicy.cpp @@ -23,16 +23,13 @@ namespace avs { using namespace sdkInterfaces; -HandlerAndPolicy::HandlerAndPolicy() : policy{BlockingPolicy::NONE} { -} - HandlerAndPolicy::HandlerAndPolicy(std::shared_ptr handlerIn, BlockingPolicy policyIn) : handler{handlerIn}, policy{policyIn} { } HandlerAndPolicy::operator bool() const { - return handler && (policy != BlockingPolicy::NONE); + return handler && policy.isValid(); } bool operator==(const HandlerAndPolicy& lhs, const HandlerAndPolicy& rhs) { diff --git a/AVSCommon/AVS/src/MessageRequest.cpp b/AVSCommon/AVS/src/MessageRequest.cpp index 17154b9b4a..1d58b4537e 100644 --- a/AVSCommon/AVS/src/MessageRequest.cpp +++ b/AVSCommon/AVS/src/MessageRequest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -32,22 +32,45 @@ static const std::string TAG("MessageRequest"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) -MessageRequest::MessageRequest( - const std::string& jsonContent, - std::shared_ptr attachmentReader) : +MessageRequest::MessageRequest(const std::string& jsonContent, const std::string& uriPathExtension) : m_jsonContent{jsonContent}, - m_attachmentReader{attachmentReader} { + m_uriPathExtension{uriPathExtension} { } MessageRequest::~MessageRequest() { } +void MessageRequest::addAttachmentReader( + const std::string& name, + std::shared_ptr attachmentReader) { + if (!attachmentReader) { + ACSDK_ERROR(LX("addAttachmentReaderFailed").d("reason", "nullAttachment")); + return; + } + + auto namedReader = std::make_shared(name, attachmentReader); + m_readers.push_back(namedReader); +} + std::string MessageRequest::getJsonContent() { return m_jsonContent; } -std::shared_ptr MessageRequest::getAttachmentReader() { - return m_attachmentReader; +std::string MessageRequest::getUriPathExtension() { + return m_uriPathExtension; +} + +int MessageRequest::attachmentReadersCount() { + return m_readers.size(); +} + +std::shared_ptr MessageRequest::getAttachmentReader(size_t index) { + if (m_readers.size() <= index) { + ACSDK_ERROR(LX("getAttachmentReaderFailed").d("reason", "index out of bound").d("index", index)); + return nullptr; + } + + return m_readers[index]; } void MessageRequest::sendCompleted(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status status) { @@ -95,30 +118,6 @@ void MessageRequest::removeObserver( using namespace avsCommon::sdkInterfaces; -bool MessageRequest::isServerStatus(MessageRequestObserverInterface::Status status) { - switch (status) { - case MessageRequestObserverInterface::Status::SUCCESS: - case MessageRequestObserverInterface::Status::SUCCESS_NO_CONTENT: - case MessageRequestObserverInterface::Status::SERVER_INTERNAL_ERROR_V2: - case MessageRequestObserverInterface::Status::CANCELED: - case MessageRequestObserverInterface::Status::THROTTLED: - case MessageRequestObserverInterface::Status::BAD_REQUEST: - case MessageRequestObserverInterface::Status::SERVER_OTHER_ERROR: - return true; - case MessageRequestObserverInterface::Status::PENDING: - case MessageRequestObserverInterface::Status::NOT_CONNECTED: - case MessageRequestObserverInterface::Status::NOT_SYNCHRONIZED: - case MessageRequestObserverInterface::Status::TIMEDOUT: - case MessageRequestObserverInterface::Status::PROTOCOL_ERROR: - case MessageRequestObserverInterface::Status::INTERNAL_ERROR: - case MessageRequestObserverInterface::Status::REFUSED: - case MessageRequestObserverInterface::Status::INVALID_AUTH: - return false; - } - - return false; -} - } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/test/AlexaClientSDKInitTest.cpp b/AVSCommon/AVS/test/AlexaClientSDKInitTest.cpp index dae7f40a53..964ab4d482 100644 --- a/AVSCommon/AVS/test/AlexaClientSDKInitTest.cpp +++ b/AVSCommon/AVS/test/AlexaClientSDKInitTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ using namespace std; * * @note This test also validates whether libcurl supports HTTP2. */ -TEST(AlexaClientSDKInitTest, initializeNoJSONConfig) { +TEST(AlexaClientSDKInitTest, test_initializeNoJSONConfig) { ASSERT_TRUE(AlexaClientSDKInit::initialize({})); AlexaClientSDKInit::uninitialize(); } @@ -43,10 +43,10 @@ TEST(AlexaClientSDKInitTest, initializeNoJSONConfig) { * * @note This test also validates whether libcurl supports HTTP2. */ -TEST(AlexaClientSDKInitTest, initializeInvalidJSONConfig) { - stringstream invalidJSON; - invalidJSON << "{"; - ASSERT_FALSE(AlexaClientSDKInit::initialize({&invalidJSON})); +TEST(AlexaClientSDKInitTest, test_initializeInvalidJSONConfig) { + auto invalidJSON = std::shared_ptr(new std::stringstream()); + (*invalidJSON) << "{"; + ASSERT_FALSE(AlexaClientSDKInit::initialize({invalidJSON})); } /** @@ -54,24 +54,24 @@ TEST(AlexaClientSDKInitTest, initializeInvalidJSONConfig) { * * @note This test also validates whether libcurl supports HTTP2. */ -TEST(AlexaClientSDKInitTest, initializeValidJSONConfig) { - stringstream validJSON; - validJSON << R"({"key":"value"})"; - ASSERT_TRUE(AlexaClientSDKInit::initialize({&validJSON})); +TEST(AlexaClientSDKInitTest, test_initializeValidJSONConfig) { + auto validJSON = std::shared_ptr(new std::stringstream()); + (*validJSON) << R"({"key":"value"})"; + ASSERT_TRUE(AlexaClientSDKInit::initialize({validJSON})); AlexaClientSDKInit::uninitialize(); } /** * Tests @c isInitialized when the SDK has not been initialized yet, expecting it to return @c false. */ -TEST(AlexaClientSDKInitTest, uninitializedIsInitialized) { +TEST(AlexaClientSDKInitTest, test_uninitializedIsInitialized) { ASSERT_FALSE(AlexaClientSDKInit::isInitialized()); } /** * Tests @c isInitialized when the SDK is initialized, expecting it to return @c true. */ -TEST(AlexaClientSDKInitTest, isInitialized) { +TEST(AlexaClientSDKInitTest, test_isInitialized) { ASSERT_TRUE(AlexaClientSDKInit::initialize({})); // Expect used to ensure we uninitialize. EXPECT_TRUE(AlexaClientSDKInit::isInitialized()); @@ -81,7 +81,7 @@ TEST(AlexaClientSDKInitTest, isInitialized) { /** * Tests @c uninitialize when the SDK has not been initialized yet, expecting no crashes or exceptions. */ -TEST(AlexaClientSDKInitTest, uninitialize) { +TEST(AlexaClientSDKInitTest, test_uninitialize) { AlexaClientSDKInit::uninitialize(); } diff --git a/AVSCommon/AVS/test/Attachment/AttachmentManagerV2Test.cpp b/AVSCommon/AVS/test/Attachment/AttachmentManagerV2Test.cpp index 3cab3e7920..375614165e 100644 --- a/AVSCommon/AVS/test/Attachment/AttachmentManagerV2Test.cpp +++ b/AVSCommon/AVS/test/Attachment/AttachmentManagerV2Test.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -118,7 +118,7 @@ void AttachmentManagerTest::testReaders(const ReaderVec& readers, bool expectedV /** * Test that the AttachmentManager's generate attachment id function works as expected. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerGenerateAttachmentId) { +TEST_F(AttachmentManagerTest, test_attachmentManagerGenerateAttachmentId) { // Normal use cases. auto id1 = m_manager.generateAttachmentId(TEST_CONTEXT_ID_STRING, TEST_CONTENT_ID_STRING); auto id2 = m_manager.generateAttachmentId(TEST_CONTEXT_ID_STRING, TEST_CONTENT_ID_STRING); @@ -143,7 +143,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerGenerateAttachmentId) { /** * Test that the AttachmentManager's set timeout function works as expected. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerSetTimeout) { +TEST_F(AttachmentManagerTest, test_attachmentManagerSetTimeout) { ASSERT_TRUE(m_manager.setAttachmentTimeoutMinutes(TIMEOUT_REGULAR)); ASSERT_TRUE(m_manager.setAttachmentTimeoutMinutes(AttachmentManager::ATTACHMENT_MANAGER_TIMOUT_MINUTES_MINIMUM)); ASSERT_FALSE(m_manager.setAttachmentTimeoutMinutes(TIMEOUT_ZERO)); @@ -153,7 +153,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerSetTimeout) { /** * Test that the AttachmentManager's create* functions work in this particular order. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerCreateWriterThenReader) { +TEST_F(AttachmentManagerTest, test_attachmentManagerCreateWriterThenReader) { // Create the writer before the reader. auto writer = m_manager.createWriter(TEST_ATTACHMENT_ID_STRING_ONE); auto reader = m_manager.createReader(TEST_ATTACHMENT_ID_STRING_ONE, utils::sds::ReaderPolicy::BLOCKING); @@ -164,7 +164,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerCreateWriterThenReader) { /** * Test that the AttachmentManager's create* functions work in this particular order. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerCreateReaderThenWriter) { +TEST_F(AttachmentManagerTest, test_attachmentManagerCreateReaderThenWriter) { // Create the reader before the writer. auto reader = m_manager.createReader(TEST_ATTACHMENT_ID_STRING_ONE, utils::sds::ReaderPolicy::BLOCKING); auto writer = m_manager.createWriter(TEST_ATTACHMENT_ID_STRING_ONE); @@ -175,7 +175,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerCreateReaderThenWriter) { /** * Test that the AttachmentManager's create reader function works as expected. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerCreateReader) { +TEST_F(AttachmentManagerTest, test_attachmentManagerCreateReader) { // Create the reader. auto reader = m_manager.createReader(TEST_ATTACHMENT_ID_STRING_ONE, utils::sds::ReaderPolicy::BLOCKING); ASSERT_NE(reader, nullptr); @@ -184,7 +184,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerCreateReader) { /** * Test that a reader created from an attachment that doesn't have a writer will wait for the writer. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerReadAttachmentWithoutWriter) { +TEST_F(AttachmentManagerTest, test_attachmentManagerReadAttachmentWithoutWriter) { auto testPattern = createTestPattern(TEST_SDS_BUFFER_SIZE_IN_BYTES); std::vector result(testPattern.size()); @@ -219,7 +219,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerReadAttachmentWithoutWriter) * Test that the AttachmentManager's cleanup logic works as expected, and that it does not impact readers and * writers that are returned before the cleanup. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerTestCreateReadersThenWriters) { +TEST_F(AttachmentManagerTest, test_attachmentManagerTestCreateReadersThenWriters) { WriterVec writers; ReaderVec readers; @@ -234,7 +234,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerTestCreateReadersThenWriters) * Test that the AttachmentManager's cleanup logic works as expected, and that it does not impact readers and * writers that are returned before the cleanup. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerTestCreateWritersThenReaders) { +TEST_F(AttachmentManagerTest, test_attachmentManagerTestCreateWritersThenReaders) { WriterVec writers; ReaderVec readers; @@ -248,7 +248,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerTestCreateWritersThenReaders) /** * Verify an AttachmentManager can't create multiple writers. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerCreateMultipleWriters) { +TEST_F(AttachmentManagerTest, test_attachmentManagerCreateMultipleWriters) { auto writer1 = m_manager.createWriter(TEST_ATTACHMENT_ID_STRING_ONE); auto writer2 = m_manager.createWriter(TEST_ATTACHMENT_ID_STRING_ONE); ASSERT_NE(writer1, nullptr); @@ -258,7 +258,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerCreateMultipleWriters) { /** * Verify an AttachmentManager can't create multiple readers. */ -TEST_F(AttachmentManagerTest, testAttachmentManagerCreateMultipleReaders) { +TEST_F(AttachmentManagerTest, test_attachmentManagerCreateMultipleReaders) { auto reader1 = m_manager.createReader(TEST_ATTACHMENT_ID_STRING_ONE, utils::sds::ReaderPolicy::BLOCKING); auto reader2 = m_manager.createReader(TEST_ATTACHMENT_ID_STRING_ONE, utils::sds::ReaderPolicy::BLOCKING); ASSERT_NE(reader1, nullptr); @@ -268,7 +268,7 @@ TEST_F(AttachmentManagerTest, testAttachmentManagerCreateMultipleReaders) { /** * Test a one-pass write and read with both Attachment wrapper classes. */ -TEST_F(AttachmentManagerTest, testAttachmentWriterAndReaderInOnePass) { +TEST_F(AttachmentManagerTest, test_attachmentWriterAndReaderInOnePass) { auto writer = m_manager.createWriter(TEST_ATTACHMENT_ID_STRING_ONE); auto reader = m_manager.createReader(TEST_ATTACHMENT_ID_STRING_ONE, utils::sds::ReaderPolicy::BLOCKING); ASSERT_NE(writer, nullptr); diff --git a/AVSCommon/AVS/test/Attachment/AttachmentReaderTest.cpp b/AVSCommon/AVS/test/Attachment/AttachmentReaderTest.cpp index 76db9a536f..425ccccf91 100644 --- a/AVSCommon/AVS/test/Attachment/AttachmentReaderTest.cpp +++ b/AVSCommon/AVS/test/Attachment/AttachmentReaderTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ #include "../include/AVSCommon/AVS/Attachment/InProcessAttachmentReader.h" #include "../include/AVSCommon/AVS/Attachment/AttachmentWriter.h" +#include "../include/AVSCommon/Utils/Threading/Executor.h" #include "Common/Common.h" @@ -34,6 +35,10 @@ using namespace alexaClientSDK::avsCommon::utils::sds; static const int TEST_SDS_SEEK_POSITION = TEST_SDS_BUFFER_SIZE_IN_BYTES - (TEST_SDS_PARTIAL_READ_AMOUNT_IN_BYTES + 10); /// A test seek position which is bad. static const int TEST_SDS_BAD_SEEK_POSITION = TEST_SDS_BUFFER_SIZE_IN_BYTES + 1; +/// Timeout for how long attachment reader loop can run while it is checking for certain status from read call. +static const int ATTACHMENT_READ_LOOP_TIMEOUT_MS = 5 * 1000; +/// Time to wait between each read call in reader loop. +static const int ATTACHMENT_READ_LOOP_WAIT_BETWEEN_READS_MS = 20; /** * A class which helps drive this unit test suite. @@ -52,8 +57,9 @@ class AttachmentReaderTest : public ::testing::Test { * Initialization function to set up and test the class members. * * @param createReader Whether the reader data member should be constructed from m_sds. + * @param resetOnOverrun Whether to create @c InProcessAttachmentReader with @c resetOnOverrun policy or not. */ - void init(bool createReader = true); + void init(bool createReader = true, bool resetOnOverrun = false); /** * Utility function to test multiple reads from an SDS. @@ -88,13 +94,14 @@ class AttachmentReaderTest : public ::testing::Test { std::vector m_testPattern; }; -void AttachmentReaderTest::init(bool createReader) { +void AttachmentReaderTest::init(bool createReader, bool resetOnOverrun) { m_sds = createSDS(TEST_SDS_BUFFER_SIZE_IN_BYTES); ASSERT_NE(m_sds, nullptr); m_writer = m_sds->createWriter(m_writerPolicy); ASSERT_NE(m_writer, nullptr); if (createReader) { - m_reader = InProcessAttachmentReader::create(m_readerPolicy, m_sds); + m_reader = InProcessAttachmentReader::create( + m_readerPolicy, m_sds, 0, InProcessAttachmentReader::SDSTypeReader::Reference::ABSOLUTE, resetOnOverrun); ASSERT_NE(m_reader, nullptr); } m_testPattern = createTestPattern(TEST_SDS_BUFFER_SIZE_IN_BYTES); @@ -159,7 +166,7 @@ void AttachmentReaderTest::readAndVerifyResult( /** * Test reading an invalid SDS. */ -TEST_F(AttachmentReaderTest, testAttachmentReaderWithInvalidSDS) { +TEST_F(AttachmentReaderTest, test_attachmentReaderWithInvalidSDS) { auto reader = InProcessAttachmentReader::create(m_readerPolicy, nullptr); ASSERT_EQ(reader, nullptr); } @@ -167,7 +174,7 @@ TEST_F(AttachmentReaderTest, testAttachmentReaderWithInvalidSDS) { /** * Test reading an SDS with a bad seek position. */ -TEST_F(AttachmentReaderTest, testAttachmentReaderWithBadSeekPosition) { +TEST_F(AttachmentReaderTest, test_attachmentReaderWithBadSeekPosition) { auto reader = InProcessAttachmentReader::create(m_readerPolicy, m_sds, TEST_SDS_BAD_SEEK_POSITION); ASSERT_EQ(reader, nullptr); } @@ -175,7 +182,7 @@ TEST_F(AttachmentReaderTest, testAttachmentReaderWithBadSeekPosition) { /** * Test a one-pass write and read. */ -TEST_F(AttachmentReaderTest, testAttachmentReaderReadInOnePass) { +TEST_F(AttachmentReaderTest, test_attachmentReaderReadInOnePass) { init(); auto testPattern = createTestPattern(TEST_SDS_BUFFER_SIZE_IN_BYTES); @@ -188,7 +195,7 @@ TEST_F(AttachmentReaderTest, testAttachmentReaderReadInOnePass) { /** * Test a partial read. */ -TEST_F(AttachmentReaderTest, testAttachmentReaderPartialRead) { +TEST_F(AttachmentReaderTest, test_attachmentReaderPartialRead) { init(); auto numWritten = m_writer->write(m_testPattern.data(), m_testPattern.size()); @@ -200,7 +207,7 @@ TEST_F(AttachmentReaderTest, testAttachmentReaderPartialRead) { /** * Test a partial read with a seek. */ -TEST_F(AttachmentReaderTest, testAttachmentReaderPartialReadWithSeek) { +TEST_F(AttachmentReaderTest, test_attachmentReaderPartialReadWithSeek) { init(false); // test a single write & read. @@ -219,17 +226,111 @@ TEST_F(AttachmentReaderTest, testAttachmentReaderPartialReadWithSeek) { /** * Test multiple partial reads of complete data, where the writer closes. */ -TEST_F(AttachmentReaderTest, testAttachmentReaderMultipleReads) { +TEST_F(AttachmentReaderTest, test_attachmentReaderMultipleReads) { testMultipleReads(false); } /** * Test multiple partial reads of complete data, where the writer remains open. */ -TEST_F(AttachmentReaderTest, testAttachmentReaderMultipleReadsOfUnfinishedData) { +TEST_F(AttachmentReaderTest, test_attachmentReaderMultipleReadsOfUnfinishedData) { testMultipleReads(true); } +/** + * Test that reading at much slower pace than writing causes reader to eventually receive + * overrun error. + */ +TEST_F(AttachmentReaderTest, test_overrunResultsInError) { + m_writerPolicy = InProcessSDS::Writer::Policy::NONBLOCKABLE; + init(); + + auto continueWriting = std::make_shared>(true); + auto writer = m_writer.get(); + + std::thread writerThread([writer, continueWriting]() { + auto testPattern = createTestPattern(TEST_SDS_BUFFER_SIZE_IN_BYTES); + while (continueWriting->load()) { + writer->write(testPattern.data(), testPattern.size()); + } + }); + + std::vector result(TEST_SDS_BUFFER_SIZE_IN_BYTES); + auto readStatus = InProcessAttachmentReader::ReadStatus::OK; + + uint32_t maxLoops = ATTACHMENT_READ_LOOP_TIMEOUT_MS / ATTACHMENT_READ_LOOP_WAIT_BETWEEN_READS_MS; + uint32_t loopCounter = 0; + while (readStatus != InProcessAttachmentReader::ReadStatus::ERROR_OVERRUN) { + m_reader->read(result.data(), result.size(), &readStatus); + std::this_thread::sleep_for(std::chrono::milliseconds(ATTACHMENT_READ_LOOP_WAIT_BETWEEN_READS_MS)); + if (++loopCounter == maxLoops) { + break; + } + } + continueWriting->store(false); + writerThread.join(); + + ASSERT_LT(loopCounter, maxLoops); +} + +/** + * Test that reading at much slower pace than writing causes reader cursor position to be reset + * to writer cursor position. + */ +TEST_F(AttachmentReaderTest, test_overrunResultsInReaderReset) { + m_writerPolicy = InProcessSDS::Writer::Policy::NONBLOCKABLE; + init(true, true); + + auto continueWriting = std::make_shared>(true); + auto writer = m_writer.get(); + + std::thread writerThread([writer, continueWriting]() { + auto testPattern = createTestPattern(TEST_SDS_BUFFER_SIZE_IN_BYTES); + while (continueWriting->load()) { + writer->write(testPattern.data(), testPattern.size()); + } + }); + + std::vector result(TEST_SDS_BUFFER_SIZE_IN_BYTES); + auto readStatus = InProcessAttachmentReader::ReadStatus::OK; + + uint32_t maxLoops = ATTACHMENT_READ_LOOP_TIMEOUT_MS / ATTACHMENT_READ_LOOP_WAIT_BETWEEN_READS_MS; + uint32_t loopCounter = 0; + while (readStatus != InProcessAttachmentReader::ReadStatus::OK_OVERRUN_RESET) { + m_reader->read(result.data(), result.size(), &readStatus); + std::this_thread::sleep_for(std::chrono::milliseconds(ATTACHMENT_READ_LOOP_WAIT_BETWEEN_READS_MS)); + if (++loopCounter == maxLoops) { + break; + } + } + + // Quit writing + continueWriting->store(false); + writerThread.join(); + + ASSERT_LT(loopCounter, maxLoops); + + // Drain the reader + loopCounter = 0; + while (readStatus != InProcessAttachmentReader::ReadStatus::OK_WOULDBLOCK) { + m_reader->read(result.data(), result.size(), &readStatus); + std::this_thread::sleep_for(std::chrono::milliseconds(ATTACHMENT_READ_LOOP_WAIT_BETWEEN_READS_MS)); + if (++loopCounter == maxLoops) { + break; + } + } + + ASSERT_LT(loopCounter, maxLoops); + + // Write the bytes, read and verify that same pattern is read + auto testPattern = createTestPattern(TEST_SDS_BUFFER_SIZE_IN_BYTES); + writer->write(testPattern.data(), testPattern.size()); + m_reader->read(result.data(), result.size(), &readStatus); + + EXPECT_EQ(readStatus, InProcessAttachmentReader::ReadStatus::OK); + EXPECT_EQ(testPattern, result); +} + } // namespace test } // namespace avs } // namespace avsCommon diff --git a/AVSCommon/AVS/test/Attachment/AttachmentTest.cpp b/AVSCommon/AVS/test/Attachment/AttachmentTest.cpp index 052b412666..94178ed43c 100644 --- a/AVSCommon/AVS/test/Attachment/AttachmentTest.cpp +++ b/AVSCommon/AVS/test/Attachment/AttachmentTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -76,28 +76,28 @@ void AttachmentTest::testCreateReader(ReaderPolicy policy) { /** * Verify the ID is correctly stored and accessed from an Attachment. */ -TEST_F(AttachmentTest, testGetAttachmentId) { +TEST_F(AttachmentTest, test_getAttachmentId) { ASSERT_EQ(TEST_ATTACHMENT_ID_STRING_ONE, m_attachment->getId()); } /** * Verify that an Attachment can create a blocking reader in various scenarios. */ -TEST_F(AttachmentTest, testAttachmentCreateBlockingReader) { +TEST_F(AttachmentTest, test_attachmentCreateBlockingReader) { testCreateReader(ReaderPolicy::BLOCKING); } /** * Verify that an Attachment can create a non-blocking reader in various scenarios. */ -TEST_F(AttachmentTest, testAttachmentCreateNonBlockingReader) { +TEST_F(AttachmentTest, test_attachmentCreateNonBlockingReader) { testCreateReader(ReaderPolicy::NONBLOCKING); } /** * Verify that an Attachment can create a writer in various scenarios. */ -TEST_F(AttachmentTest, testAttachmentCreateWriter) { +TEST_F(AttachmentTest, test_attachmentCreateWriter) { // Test create writer where there is no reader. auto writer = m_attachment->createWriter(); ASSERT_NE(writer, nullptr); @@ -122,7 +122,7 @@ TEST_F(AttachmentTest, testAttachmentCreateWriter) { /** * Test creating an Attachment with an existing SDS. */ -TEST_F(AttachmentTest, testCreateAttachmentWithSDS) { +TEST_F(AttachmentTest, test_createAttachmentWithSDS) { auto sds = createSDS(TEST_SDS_BUFFER_SIZE_IN_BYTES); auto attachment = std::make_shared(TEST_ATTACHMENT_ID_STRING_ONE, std::move(sds)); @@ -139,7 +139,7 @@ TEST_F(AttachmentTest, testCreateAttachmentWithSDS) { /** * Verify an Attachment can't create multiple writers. */ -TEST_F(AttachmentTest, testAttachmentCreateMultipleWriters) { +TEST_F(AttachmentTest, test_attachmentCreateMultipleWriters) { auto writer1 = m_attachment->createWriter(); auto writer2 = m_attachment->createWriter(); ASSERT_NE(writer1, nullptr); diff --git a/AVSCommon/AVS/test/Attachment/AttachmentUtilsTest.cpp b/AVSCommon/AVS/test/Attachment/AttachmentUtilsTest.cpp new file mode 100644 index 0000000000..49dfac04f8 --- /dev/null +++ b/AVSCommon/AVS/test/Attachment/AttachmentUtilsTest.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include + +#include "AVSCommon/AVS/Attachment/AttachmentUtils.h" + +using namespace ::testing; +using namespace alexaClientSDK::avsCommon::avs::attachment; + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { +namespace test { + +const static std::string sampleBuffer = "example buffer"; + +class AttachmentUtilsTest : public ::testing::Test { +public: + /// Setup called before each test. + void SetUp(); + +protected: + /// Example buffer @ AttachmentReader. + std::shared_ptr m_attachmentReader; +}; + +void AttachmentUtilsTest::SetUp() { + std::vector srcBuffer; + srcBuffer.assign(sampleBuffer.begin(), sampleBuffer.end()); + + m_attachmentReader = AttachmentUtils::createAttachmentReader(srcBuffer); + ASSERT_TRUE(m_attachmentReader); +} + +/** + * Test read until end of buffer + */ +TEST_F(AttachmentUtilsTest, test_readCompleteBuffer) { + char dstBuffer[sampleBuffer.length() + 10]; + memset(dstBuffer, 0, sampleBuffer.length() + 10); + + AttachmentReader::ReadStatus status; + size_t bytesRead = m_attachmentReader->read(dstBuffer, sampleBuffer.length(), &status); + + ASSERT_EQ(bytesRead, sampleBuffer.length()); + ASSERT_EQ(status, AttachmentReader::ReadStatus::OK); + + bytesRead = m_attachmentReader->read(dstBuffer, sampleBuffer.length(), &status); + ASSERT_EQ(bytesRead, 0U); + ASSERT_EQ(status, AttachmentReader::ReadStatus::CLOSED); + ASSERT_EQ(strcmp(sampleBuffer.data(), dstBuffer), 0); +} + +/* + * Test empty buffer + */ +TEST_F(AttachmentUtilsTest, test_emptyBuffer) { + std::vector emptySrcBuffer; + auto emptyAttachmentReader = AttachmentUtils::createAttachmentReader(emptySrcBuffer); + ASSERT_FALSE(emptyAttachmentReader); +} + +} // namespace test +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/AVS/test/Attachment/AttachmentWriterTest.cpp b/AVSCommon/AVS/test/Attachment/AttachmentWriterTest.cpp index 1102b4f9bc..77ca2f52b6 100644 --- a/AVSCommon/AVS/test/Attachment/AttachmentWriterTest.cpp +++ b/AVSCommon/AVS/test/Attachment/AttachmentWriterTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -117,7 +117,7 @@ void AttachmentWriterTest::testMultipleReads(bool closeWriterBeforeReading) { /** * Test writing to an invalid SDS. */ -TEST_F(AttachmentWriterTest, testAttachmentWriterWithInvalidSDS) { +TEST_F(AttachmentWriterTest, test_attachmentWriterWithInvalidSDS) { auto writer = InProcessAttachmentWriter::create(nullptr); ASSERT_EQ(writer, nullptr); } @@ -125,7 +125,7 @@ TEST_F(AttachmentWriterTest, testAttachmentWriterWithInvalidSDS) { /** * Test writing to a closed writer. */ -TEST_F(AttachmentWriterTest, testAttachmentWriterOnClosedWriter) { +TEST_F(AttachmentWriterTest, test_attachmentWriterOnClosedWriter) { init(); m_writer->close(); @@ -139,7 +139,7 @@ TEST_F(AttachmentWriterTest, testAttachmentWriterOnClosedWriter) { /** * Test writing a single pass of data. */ -TEST_F(AttachmentWriterTest, testAttachmentWriterWriteSinglePass) { +TEST_F(AttachmentWriterTest, test_attachmentWriterWriteSinglePass) { init(); AttachmentWriter::WriteStatus writeStatus = AttachmentWriter::WriteStatus::OK; @@ -151,7 +151,7 @@ TEST_F(AttachmentWriterTest, testAttachmentWriterWriteSinglePass) { /** * Test a one-pass write and read with both wrapper classes. */ -TEST_F(AttachmentWriterTest, testAttachmentWriterAndReadInOnePass) { +TEST_F(AttachmentWriterTest, test_attachmentWriterAndReadInOnePass) { init(); auto writeStatus = InProcessAttachmentWriter::WriteStatus::OK; @@ -173,14 +173,14 @@ TEST_F(AttachmentWriterTest, testAttachmentWriterAndReadInOnePass) { /** * Test multiple partial reads of complete data, where the writer is closed. */ -TEST_F(AttachmentWriterTest, testAttachmentReaderAndWriterMultipleReads) { +TEST_F(AttachmentWriterTest, test_attachmentReaderAndWriterMultipleReads) { testMultipleReads(true); } /** * Test multiple partial reads of complete data, where the writer remains open. */ -TEST_F(AttachmentWriterTest, testAttachmentWriterAndReaderMultipleReadsOfUnfinishedData) { +TEST_F(AttachmentWriterTest, test_attachmentWriterAndReaderMultipleReadsOfUnfinishedData) { testMultipleReads(false); } diff --git a/AVSCommon/AVS/test/BlockingPolicyTest.cpp b/AVSCommon/AVS/test/BlockingPolicyTest.cpp new file mode 100644 index 0000000000..378653133a --- /dev/null +++ b/AVSCommon/AVS/test/BlockingPolicyTest.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include + +#include "AVSCommon/AVS/BlockingPolicy.h" + +using namespace testing; + +namespace alexaClientSDK { +namespace avsCommon { +namespace test { + +using namespace avsCommon::avs; + +class BlockingPolicyTest : public ::testing::Test {}; + +// test defaultConstructor +TEST_F(BlockingPolicyTest, test_defaultConstructor) { + BlockingPolicy blockingPolicy; + + ASSERT_FALSE(blockingPolicy.isValid()); +} + +/// Test isBlocking +TEST_F(BlockingPolicyTest, test_isBlocking) { + BlockingPolicy blocking(BlockingPolicy::MEDIUM_VISUAL, true); + BlockingPolicy nonBlocking(BlockingPolicy::MEDIUM_VISUAL, false); + + ASSERT_TRUE(blocking.isBlocking()); + ASSERT_FALSE(nonBlocking.isBlocking()); +} + +/// Test getMediums +TEST_F(BlockingPolicyTest, test_getMediums) { + BlockingPolicy audio(BlockingPolicy::MEDIUM_AUDIO, false); + BlockingPolicy visual(BlockingPolicy::MEDIUM_VISUAL, false); + BlockingPolicy audioAndVisual(BlockingPolicy::MEDIUMS_AUDIO_AND_VISUAL, false); + BlockingPolicy none(BlockingPolicy::MEDIUMS_NONE, false); + + ASSERT_EQ(audio.getMediums(), BlockingPolicy::MEDIUM_AUDIO); + ASSERT_EQ(visual.getMediums(), BlockingPolicy::MEDIUM_VISUAL); + ASSERT_EQ(audioAndVisual.getMediums(), BlockingPolicy::MEDIUMS_AUDIO_AND_VISUAL); + ASSERT_EQ(none.getMediums(), BlockingPolicy::MEDIUMS_NONE); +} + +} // namespace test +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/AVS/test/CapabilityAgentTest.cpp b/AVSCommon/AVS/test/CapabilityAgentTest.cpp index 97da0e22f3..9b2b3b651a 100644 --- a/AVSCommon/AVS/test/CapabilityAgentTest.cpp +++ b/AVSCommon/AVS/test/CapabilityAgentTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ #include "AVSCommon/AVS/CapabilityAgent.h" #include "AVSCommon/AVS/Attachment/AttachmentManager.h" +#include using namespace testing; @@ -30,6 +31,7 @@ namespace avsCommon { namespace test { using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::test; using namespace avsCommon::avs; using namespace avsCommon::avs::attachment; using namespace rapidjson; @@ -67,6 +69,9 @@ static const std::string PAYLOAD("payload"); /// A speech recognizer payload for testing static const std::string PAYLOAD_TEST("payload_Test"); +/// A string to send with the sendExceptionEncounteredAndReportFailed method +static const std::string EXCEPTION_ENCOUNTERED_STRING("encountered_exception"); + /// A payload for testing // clang-format off static const std::string PAYLOAD_SPEECH_RECOGNIZER = @@ -231,11 +236,22 @@ class MockCapabilityAgent : public CapabilityAgent { * Creates an instance of the @c MockCapabilityAgent. * * @param nameSpace The namespace of the Capability Agent. + * @param m_exceptionSender The @c ExceptionEncounteredSenderInterface instance. * @return A shared pointer to an instance of the @c MockCapabilityAgent. */ - static std::shared_ptr create(const std::string nameSpace); + static std::shared_ptr create( + const std::string nameSpace, + const std::shared_ptr m_exceptionSender); - MockCapabilityAgent(const std::string& nameSpace); + /** + * MockCapabilityAgent Constructor. + * + * @param nameSpace The namespace of the Capability Agent. + * @param m_exceptionSender The @c ExceptionEncounteredSenderInterface instance. + */ + MockCapabilityAgent( + const std::string& nameSpace, + const std::shared_ptr m_exceptionSender); ~MockCapabilityAgent() override; @@ -261,6 +277,16 @@ class MockCapabilityAgent : public CapabilityAgent { avs::DirectiveHandlerConfiguration getConfiguration() const override; + /** + * Wrapper function to test protected method @c sendExceptionEncounteredAndReportFailed + * + * @param directiveIn The AVSDirective to pass to @c sendExceptionEncounteredAndReportFailed. + * @param resultIn The DirectiveHandlerResultInterface to pass to @c sendExceptionEncounteredAndReportFailed. + */ + void testsendExceptionEncounteredAndReportFailed( + std::shared_ptr directiveIn, + std::unique_ptr resultIn); + FunctionCalled waitForFunctionCalls(const std::chrono::milliseconds duration = std::chrono::milliseconds(400)); const std::pair callBuildJsonEventString( @@ -280,12 +306,16 @@ class MockCapabilityAgent : public CapabilityAgent { std::mutex m_mutex; }; -std::shared_ptr MockCapabilityAgent::create(const std::string nameSpace) { - return std::make_shared(nameSpace); +std::shared_ptr MockCapabilityAgent::create( + const std::string nameSpace, + const std::shared_ptr m_exceptionSender) { + return std::make_shared(nameSpace, m_exceptionSender); } -MockCapabilityAgent::MockCapabilityAgent(const std::string& nameSpace) : - CapabilityAgent(nameSpace, nullptr), +MockCapabilityAgent::MockCapabilityAgent( + const std::string& nameSpace, + const std::shared_ptr m_exceptionSender) : + CapabilityAgent(nameSpace, m_exceptionSender), m_functionCalled{FunctionCalled::NONE} { } @@ -321,6 +351,15 @@ avs::DirectiveHandlerConfiguration MockCapabilityAgent::getConfiguration() const return avs::DirectiveHandlerConfiguration(); } +void MockCapabilityAgent::testsendExceptionEncounteredAndReportFailed( + std::shared_ptr directiveIn, + std::unique_ptr resultIn) { + std::shared_ptr m_directiveInfo = + CapabilityAgent::createDirectiveInfo(directiveIn, std::move(resultIn)); + sendExceptionEncounteredAndReportFailed( + m_directiveInfo, EXCEPTION_ENCOUNTERED_STRING, ExceptionErrorType::INTERNAL_ERROR); +} + MockCapabilityAgent::FunctionCalled MockCapabilityAgent::waitForFunctionCalls( const std::chrono::milliseconds duration) { std::unique_lock lock(m_mutex); @@ -357,10 +396,14 @@ class CapabilityAgentTest : public ::testing::Test { std::shared_ptr m_capabilityAgent; std::shared_ptr m_attachmentManager; + + // mockExceptionSender + std::shared_ptr m_exceptionSender; }; void CapabilityAgentTest::SetUp() { - m_capabilityAgent = MockCapabilityAgent::create(NAMESPACE_SPEECH_RECOGNIZER); + m_exceptionSender = std::make_shared>(); + m_capabilityAgent = MockCapabilityAgent::create(NAMESPACE_SPEECH_RECOGNIZER, m_exceptionSender); m_attachmentManager = std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS); } @@ -422,7 +465,7 @@ void CapabilityAgentTest::testBuildJsonEventString( * Call the @c handleDirectiveImmediately from the @c CapabilityAgent base class with a directive as the argument. * Expect the @c handleDirectiveImmediately with the argument of @c DirectiveAndResultInterface will be called. */ -TEST_F(CapabilityAgentTest, testCallToHandleImmediately) { +TEST_F(CapabilityAgentTest, test_callToHandleImmediately) { auto avsMessageHeader = std::make_shared( NAMESPACE_SPEECH_RECOGNIZER, NAME_STOP_CAPTURE, MESSAGE_ID_TEST, DIALOG_REQUEST_ID_TEST); std::shared_ptr directive = @@ -436,7 +479,7 @@ TEST_F(CapabilityAgentTest, testCallToHandleImmediately) { * Call the @c preHandleDirective from the @c CapabilityAgent base class with a directive as the argument. * Expect the @c preHandleDirective with the argument of @c DirectiveAndResultInterface will be called. */ -TEST_F(CapabilityAgentTest, testCallToPrehandleDirective) { +TEST_F(CapabilityAgentTest, test_callToPrehandleDirective) { auto avsMessageHeader = std::make_shared( NAMESPACE_SPEECH_RECOGNIZER, NAME_STOP_CAPTURE, MESSAGE_ID_TEST, DIALOG_REQUEST_ID_TEST); std::shared_ptr directive = @@ -451,7 +494,7 @@ TEST_F(CapabilityAgentTest, testCallToPrehandleDirective) { * Call the @c handleDirective from the @c CapabilityAgent base class with a directive as the argument. * Expect the @c handleDirective with the argument of @c DirectiveAndResultInterface will be called. */ -TEST_F(CapabilityAgentTest, testCallToHandleDirective) { +TEST_F(CapabilityAgentTest, test_callToHandleDirective) { auto avsMessageHeader = std::make_shared( NAMESPACE_SPEECH_RECOGNIZER, NAME_STOP_CAPTURE, MESSAGE_ID_TEST, DIALOG_REQUEST_ID_TEST); std::shared_ptr directive = @@ -467,7 +510,7 @@ TEST_F(CapabilityAgentTest, testCallToHandleDirective) { * Call the @c handleDirective from the @c CapabilityAgent base class with a directive as the argument. No * @c preHandleDirective is called before handleDirective. Expect @c handleDirective to return @c false. */ -TEST_F(CapabilityAgentTest, testCallToHandleDirectiveWithNoPrehandle) { +TEST_F(CapabilityAgentTest, test_callToHandleDirectiveWithNoPrehandle) { auto avsMessageHeader = std::make_shared( NAMESPACE_SPEECH_RECOGNIZER, NAME_STOP_CAPTURE, MESSAGE_ID_TEST, DIALOG_REQUEST_ID_TEST); std::shared_ptr directive = @@ -479,7 +522,7 @@ TEST_F(CapabilityAgentTest, testCallToHandleDirectiveWithNoPrehandle) { * Call the @c cancelDirective from the @c CapabilityAgent base class with a directive as the argument. * Expect the @c cancelDirective with the argument of @c DirectiveAndResultInterface will be called. */ -TEST_F(CapabilityAgentTest, testCallToCancelDirective) { +TEST_F(CapabilityAgentTest, test_callToCancelDirective) { auto avsMessageHeader = std::make_shared( NAMESPACE_SPEECH_RECOGNIZER, NAME_STOP_CAPTURE, MESSAGE_ID_TEST, DIALOG_REQUEST_ID_TEST); std::shared_ptr directive = @@ -496,7 +539,7 @@ TEST_F(CapabilityAgentTest, testCallToCancelDirective) { * @c preHandleDirective is called before handleDirective. Expect the @c cancelDirective with the argument of * @c DirectiveAndResultInterface will not be called. */ -TEST_F(CapabilityAgentTest, testCallToCancelDirectiveWithNoPrehandle) { +TEST_F(CapabilityAgentTest, test_callToCancelDirectiveWithNoPrehandle) { auto avsMessageHeader = std::make_shared( NAMESPACE_SPEECH_RECOGNIZER, NAME_STOP_CAPTURE, MESSAGE_ID_TEST, DIALOG_REQUEST_ID_TEST); std::shared_ptr directive = @@ -510,7 +553,7 @@ TEST_F(CapabilityAgentTest, testCallToCancelDirectiveWithNoPrehandle) { * corresponding @c testEvent. The messageId will not match since it is a random number. Verify the string before and * after the messageId. */ -TEST_F(CapabilityAgentTest, testWithDialogIdAndContext) { +TEST_F(CapabilityAgentTest, test_withDialogIdAndContext) { testBuildJsonEventString(testEventWithDialogReqIdAndContext, true); } @@ -519,7 +562,7 @@ TEST_F(CapabilityAgentTest, testWithDialogIdAndContext) { * matches the corresponding @c testEvent. The messageId will not match since it is a random number. Verify the string * before and after the messageId. */ -TEST_F(CapabilityAgentTest, testWithDialogIdAndNoContext) { +TEST_F(CapabilityAgentTest, test_withDialogIdAndNoContext) { testBuildJsonEventString(testEventWithDialogReqIdNoContext, true); } @@ -528,7 +571,7 @@ TEST_F(CapabilityAgentTest, testWithDialogIdAndNoContext) { * that matches the corresponding @c testEvent. The messageId will not match since it is a random number. * Verify the string before and after the messageId. */ -TEST_F(CapabilityAgentTest, testWithoutDialogIdOrContext) { +TEST_F(CapabilityAgentTest, test_withoutDialogIdOrContext) { testBuildJsonEventString(testEventWithoutDialogReqIdOrContext, false); } @@ -537,10 +580,26 @@ TEST_F(CapabilityAgentTest, testWithoutDialogIdOrContext) { * string that matches the corresponding @c testEvent. The messageId will not match since it is a random number. * Verify the string before and after the messageId. */ -TEST_F(CapabilityAgentTest, testWithContextAndNoDialogId) { +TEST_F(CapabilityAgentTest, test_withContextAndNoDialogId) { testBuildJsonEventString(testEventWithContextAndNoDialogReqId, false); } +/** + * Call sendExceptionEncounteredAndReportFailed with info pointing to null directive. Expect sendExceptionEncountered to + * not be called. Send again with info pointing to a valid directive. Expect sendExceptionEncountered to be called + */ +TEST_F(CapabilityAgentTest, test_sendExceptionEncounteredWithNullInfo) { + EXPECT_CALL(*(m_exceptionSender.get()), sendExceptionEncountered(_, _, _)).Times(0); + m_capabilityAgent->testsendExceptionEncounteredAndReportFailed(nullptr, nullptr); + + EXPECT_CALL(*(m_exceptionSender.get()), sendExceptionEncountered(_, _, _)).Times(1); + auto avsMessageHeader = std::make_shared( + NAMESPACE_SPEECH_RECOGNIZER, NAME_STOP_CAPTURE, MESSAGE_ID_TEST, DIALOG_REQUEST_ID_TEST); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, ""); + m_capabilityAgent->testsendExceptionEncounteredAndReportFailed(directive, nullptr); +} + } // namespace test } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/test/CapabilityConfigurationTest.cpp b/AVSCommon/AVS/test/CapabilityConfigurationTest.cpp new file mode 100644 index 0000000000..0b0e0b40b5 --- /dev/null +++ b/AVSCommon/AVS/test/CapabilityConfigurationTest.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include +#include + +#include + +using namespace ::testing; + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { +namespace test { + +/// Key 1 for an entry of @c CapabilityConfiguration +static const std::string KEY1 = "key1"; +/// Key 2 for an entry of @c CapabilityConfiguration +static const std::string KEY2 = "key2"; +/// Value 1 for an entry of @c CapabilityConfiguration +static const std::string VALUE1 = "value1"; +/// Value 2 for an entry of @c CapabilityConfiguration +static const std::string VALUE2 = "value2"; + +/** + * Test harness for @c CapabilityConfiguration class. + */ +class CapabilityConfigurationTest : public ::testing::Test {}; + +/// Test the constructor +TEST_F(CapabilityConfigurationTest, test_constructor) { + std::unordered_map capabilityConfigurationMap; + CapabilityConfiguration instance(capabilityConfigurationMap); + ASSERT_TRUE(instance.capabilityConfiguration.empty()); +} + +/// Test the == operator +TEST_F(CapabilityConfigurationTest, test_equality) { + std::unordered_map lhsCapabilityConfigurationMap; + lhsCapabilityConfigurationMap.insert({KEY1, VALUE1}); + + std::unordered_map rhsCapabilityConfigurationMap; + rhsCapabilityConfigurationMap.insert({KEY1, VALUE1}); + + CapabilityConfiguration lhsInstance(lhsCapabilityConfigurationMap); + CapabilityConfiguration rhsInstance(rhsCapabilityConfigurationMap); + + ASSERT_EQ(lhsInstance, rhsInstance); + ASSERT_EQ(std::hash{}(lhsInstance), std::hash{}(rhsInstance)); +} + +/// Test the != operator +TEST_F(CapabilityConfigurationTest, test_inequality) { + std::unordered_map lhsCapabilityConfigurationMap; + lhsCapabilityConfigurationMap.insert({KEY1, VALUE1}); + + std::unordered_map rhsCapabilityConfigurationMap; + rhsCapabilityConfigurationMap.insert({KEY2, VALUE2}); + + CapabilityConfiguration lhsInstance(lhsCapabilityConfigurationMap); + CapabilityConfiguration rhsInstance(rhsCapabilityConfigurationMap); + + ASSERT_NE(lhsInstance, rhsInstance); + ASSERT_NE(std::hash{}(lhsInstance), std::hash{}(rhsInstance)); +} + +/// Test if equality and hash works if you have multiple entries in maps that are the same but in different order +TEST_F(CapabilityConfigurationTest, test_multipleValues) { + std::unordered_map lhsCapabilityConfigurationMap; + lhsCapabilityConfigurationMap.insert({KEY1, VALUE1}); + lhsCapabilityConfigurationMap.insert({KEY2, VALUE2}); + + std::unordered_map rhsCapabilityConfigurationMap; + rhsCapabilityConfigurationMap.insert({KEY2, VALUE2}); + rhsCapabilityConfigurationMap.insert({KEY1, VALUE1}); + + CapabilityConfiguration lhsInstance(lhsCapabilityConfigurationMap); + CapabilityConfiguration rhsInstance(rhsCapabilityConfigurationMap); + ASSERT_EQ(lhsInstance, rhsInstance); + ASSERT_EQ(std::hash{}(lhsInstance), std::hash{}(rhsInstance)); +} + +} // namespace test +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp b/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp index 46a0ed47e1..24823df882 100644 --- a/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp +++ b/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -32,6 +32,10 @@ static const auto DEFAULT_TIMEOUT = std::chrono::seconds(5); /// Short time out for when callbacks are expected not to occur. static const auto SHORT_TIMEOUT = std::chrono::milliseconds(50); +/// Time out for testing if transitionFromThinking timeout has occurred. This needs to be longer than the SHORT_TIMEOUT +/// defined in DialogUXStateAggregator.cpp. +static const auto TRANSITION_FROM_THINKING_TIMEOUT = std::chrono::milliseconds(300); + /// A test observer that mocks out the DialogUXStateObserverInterface##onDialogUXStateChanged() call. class TestObserver : public DialogUXStateObserverInterface { public: @@ -114,12 +118,15 @@ class StateChangeManager { * Checks that a state change does not occur by waiting for the timeout duration. * * @param observer The UX state observer. + * @param timeout An optional timeout parameter to wait for to make sure no state change has occured. */ - void assertNoStateChange(std::shared_ptr observer) { + void assertNoStateChange( + std::shared_ptr observer, + std::chrono::milliseconds timeout = SHORT_TIMEOUT) { ASSERT_TRUE(observer); bool stateChanged = false; // Will wait for the short timeout duration before succeeding - observer->waitForStateChange(SHORT_TIMEOUT, &stateChanged); + observer->waitForStateChange(timeout, &stateChanged); ASSERT_FALSE(stateChanged); } }; @@ -150,12 +157,12 @@ class DialogUXAggregatorTest }; /// Tests that an observer starts off in the IDLE state. -TEST_F(DialogUXAggregatorTest, testIdleAtBeginning) { +TEST_F(DialogUXAggregatorTest, test_idleAtBeginning) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); } /// Tests that a new observer added receives the current state. -TEST_F(DialogUXAggregatorTest, testInvalidAtBeginningForMultipleObservers) { +TEST_F(DialogUXAggregatorTest, test_invalidAtBeginningForMultipleObservers) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->addObserver(m_anotherTestObserver); @@ -164,7 +171,7 @@ TEST_F(DialogUXAggregatorTest, testInvalidAtBeginningForMultipleObservers) { } /// Tests that the removing observer functionality works properly by asserting no state change on a removed observer. -TEST_F(DialogUXAggregatorTest, testRemoveObserver) { +TEST_F(DialogUXAggregatorTest, test_removeObserver) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->addObserver(m_anotherTestObserver); @@ -177,7 +184,7 @@ TEST_F(DialogUXAggregatorTest, testRemoveObserver) { } /// Tests that multiple callbacks aren't issued if the state shouldn't change. -TEST_F(DialogUXAggregatorTest, aipIdleLeadsToIdleState) { +TEST_F(DialogUXAggregatorTest, test_aipIdleLeadsToIdleState) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::IDLE); @@ -185,7 +192,7 @@ TEST_F(DialogUXAggregatorTest, aipIdleLeadsToIdleState) { } /// Tests that the AIP recognizing state leads to the LISTENING state. -TEST_F(DialogUXAggregatorTest, aipRecognizeLeadsToListeningState) { +TEST_F(DialogUXAggregatorTest, test_aipRecognizeLeadsToListeningState) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); @@ -193,7 +200,7 @@ TEST_F(DialogUXAggregatorTest, aipRecognizeLeadsToListeningState) { } /// Tests that the AIP recognizing state leads to the LISTENING state. -TEST_F(DialogUXAggregatorTest, aipIdleLeadsToIdle) { +TEST_F(DialogUXAggregatorTest, test_aipIdleLeadsToIdle) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); @@ -203,16 +210,16 @@ TEST_F(DialogUXAggregatorTest, aipIdleLeadsToIdle) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); } -/// Tests that the AIP expecting speech state leads to the LISTENING state. -TEST_F(DialogUXAggregatorTest, aipExpectingSpeechLeadsToListeningState) { +/// Tests that the AIP expecting speech state leads to the EXPECTING state. +TEST_F(DialogUXAggregatorTest, test_aipExpectingSpeechLeadsToListeningState) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::EXPECTING_SPEECH); - assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::EXPECTING); } /// Tests that the AIP busy state leads to the THINKING state. -TEST_F(DialogUXAggregatorTest, aipBusyLeadsToThinkingState) { +TEST_F(DialogUXAggregatorTest, test_aipBusyLeadsToThinkingState) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); @@ -220,7 +227,7 @@ TEST_F(DialogUXAggregatorTest, aipBusyLeadsToThinkingState) { } /// Tests that BUSY state goes to IDLE after the specified timeout. -TEST_F(DialogUXAggregatorTest, busyGoesToIdleAfterTimeout) { +TEST_F(DialogUXAggregatorTest, test_busyGoesToIdleAfterTimeout) { std::shared_ptr anotherAggregator = std::make_shared(std::chrono::milliseconds(200)); @@ -236,7 +243,7 @@ TEST_F(DialogUXAggregatorTest, busyGoesToIdleAfterTimeout) { } /// Tests that the BUSY state remains in BUSY immediately if a message is received. -TEST_F(DialogUXAggregatorTest, busyThenReceiveRemainsInBusyImmediately) { +TEST_F(DialogUXAggregatorTest, test_busyThenReceiveRemainsInBusyImmediately) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); @@ -248,7 +255,7 @@ TEST_F(DialogUXAggregatorTest, busyThenReceiveRemainsInBusyImmediately) { } /// Tests that the BUSY state goes to IDLE after a message is received after a short timeout. -TEST_F(DialogUXAggregatorTest, busyThenReceiveGoesToIdleAfterShortTimeout) { +TEST_F(DialogUXAggregatorTest, test_busyThenReceiveGoesToIdleAfterShortTimeout) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); @@ -261,7 +268,7 @@ TEST_F(DialogUXAggregatorTest, busyThenReceiveGoesToIdleAfterShortTimeout) { } /// Tests that the BUSY state goes to IDLE after a SpeechSynthesizer speak state is received. -TEST_F(DialogUXAggregatorTest, busyThenReceiveThenSpeakGoesToSpeak) { +TEST_F(DialogUXAggregatorTest, test_busyThenReceiveThenSpeakGoesToSpeak) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); @@ -278,7 +285,7 @@ TEST_F(DialogUXAggregatorTest, busyThenReceiveThenSpeakGoesToSpeak) { * Tests that the BUSY state goes to SPEAKING but not IDLE after both a message is received and a SpeechSynthesizer * speak state is received. */ -TEST_F(DialogUXAggregatorTest, busyThenReceiveThenSpeakGoesToSpeakButNotIdle) { +TEST_F(DialogUXAggregatorTest, test_busyThenReceiveThenSpeakGoesToSpeakButNotIdle) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); @@ -293,8 +300,8 @@ TEST_F(DialogUXAggregatorTest, busyThenReceiveThenSpeakGoesToSpeakButNotIdle) { assertNoStateChange(m_testObserver); } -/// Tests that both SpeechSynthesizer and AudioInputProcessor finished/idle state leadss to the IDLE state. -TEST_F(DialogUXAggregatorTest, speakingAndRecognizingFinishedGoesToIdle) { +/// Tests that both SpeechSynthesizer and AudioInputProcessor finished/idle state leads to the IDLE state. +TEST_F(DialogUXAggregatorTest, test_speakingAndRecognizingFinishedGoesToIdle) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); @@ -313,7 +320,7 @@ TEST_F(DialogUXAggregatorTest, speakingAndRecognizingFinishedGoesToIdle) { } /// Tests that SpeechSynthesizer or AudioInputProcessor non-idle state prevents the IDLE state. -TEST_F(DialogUXAggregatorTest, nonIdleObservantsPreventsIdle) { +TEST_F(DialogUXAggregatorTest, test_nonIdleObservantsPreventsIdle) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); // AIP is active, SS is not. Expected: non idle @@ -337,7 +344,7 @@ TEST_F(DialogUXAggregatorTest, nonIdleObservantsPreventsIdle) { } /// Tests that a SpeechSynthesizer finished state does not go to the IDLE state after a very short timeout. -TEST_F(DialogUXAggregatorTest, speakingFinishedDoesNotGoesToIdleImmediately) { +TEST_F(DialogUXAggregatorTest, test_speakingFinishedDoesNotGoesToIdleImmediately) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); @@ -355,7 +362,7 @@ TEST_F(DialogUXAggregatorTest, speakingFinishedDoesNotGoesToIdleImmediately) { } /// Tests that a simple message receive does nothing. -TEST_F(DialogUXAggregatorTest, simpleReceiveDoesNothing) { +TEST_F(DialogUXAggregatorTest, test_simpleReceiveDoesNothing) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); m_aggregator->receive("", ""); @@ -371,6 +378,25 @@ TEST_F(DialogUXAggregatorTest, simpleReceiveDoesNothing) { assertNoStateChange(m_testObserver); } +/// Tests that the THINKING state remains in THINKING if SpeechSynthesizer reports GAINING_FOCUS and a new message is +/// received. +TEST_F(DialogUXAggregatorTest, test_thinkingThenReceiveRemainsInThinkingIfSpeechSynthesizerReportsGainingFocus) { + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); + + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::THINKING); + + m_aggregator->receive("", ""); + + m_aggregator->onStateChanged( + sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState::GAINING_FOCUS); + + // Make sure after SpeechSynthesizer reports GAINING_FOCUS, that it would stay in THINKING state + m_aggregator->receive("", ""); + + assertNoStateChange(m_testObserver, TRANSITION_FROM_THINKING_TIMEOUT); +} + } // namespace test } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/test/ExceptionEncounteredSenderTest.cpp b/AVSCommon/AVS/test/ExceptionEncounteredSenderTest.cpp index 1a7b1fee61..bcb7c74dd6 100644 --- a/AVSCommon/AVS/test/ExceptionEncounteredSenderTest.cpp +++ b/AVSCommon/AVS/test/ExceptionEncounteredSenderTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -223,7 +223,7 @@ bool ExceptionEncounteredSenderTest::testExceptionEncounteredSucceeds( * This function sends @c ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED and verifies that * @c ExceptionEncounteredSender::sendExceptionEncountered send the event. */ -TEST_F(ExceptionEncounteredSenderTest, errorTypeUnexpectedInformationReceived) { +TEST_F(ExceptionEncounteredSenderTest, test_errorTypeUnexpectedInformationReceived) { ASSERT_TRUE(testExceptionEncounteredSucceeds( UNPARSED_DIRECTIVE_JSON_STRING, avs::ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED, @@ -233,7 +233,7 @@ TEST_F(ExceptionEncounteredSenderTest, errorTypeUnexpectedInformationReceived) { * This function sends @c ExceptionErrorType::UNSUPPORTED_OPERATION and verifies that * @c ExceptionEncounteredSender::sendExceptionEncountered send the event. */ -TEST_F(ExceptionEncounteredSenderTest, errorTypeUnexpectedOperation) { +TEST_F(ExceptionEncounteredSenderTest, test_errorTypeUnexpectedOperation) { ASSERT_TRUE(testExceptionEncounteredSucceeds( UNPARSED_DIRECTIVE_JSON_STRING, avs::ExceptionErrorType::UNSUPPORTED_OPERATION, "Operation not supported")); } @@ -241,7 +241,7 @@ TEST_F(ExceptionEncounteredSenderTest, errorTypeUnexpectedOperation) { * This function sends @c ExceptionErrorType::INTERNAL_ERROR and verifies that * @c ExceptionEncounteredSender::sendExceptionEncountered send the event. */ -TEST_F(ExceptionEncounteredSenderTest, errorTypeInternalError) { +TEST_F(ExceptionEncounteredSenderTest, test_errorTypeInternalError) { ASSERT_TRUE(testExceptionEncounteredSucceeds( UNPARSED_DIRECTIVE_JSON_STRING, avs::ExceptionErrorType::INTERNAL_ERROR, "An error occurred with the device")); } diff --git a/AVSCommon/AVS/test/HandlerAndPolicyTest.cpp b/AVSCommon/AVS/test/HandlerAndPolicyTest.cpp index 5723797e72..f97b844e4c 100644 --- a/AVSCommon/AVS/test/HandlerAndPolicyTest.cpp +++ b/AVSCommon/AVS/test/HandlerAndPolicyTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -56,32 +56,35 @@ class HandlerAndPolicyTest : public ::testing::Test {}; /** * Invoke default constructor. Expect @c nameSpace and @c name properties are both empty strings. */ -TEST_F(HandlerAndPolicyTest, testDefaultConstructor) { +TEST_F(HandlerAndPolicyTest, test_defaultConstructor) { HandlerAndPolicy handlerAndPolicy; ASSERT_EQ(handlerAndPolicy.handler, nullptr); - ASSERT_EQ(handlerAndPolicy.policy, BlockingPolicy::NONE); + ASSERT_FALSE(handlerAndPolicy.policy.isValid()); } /** * Invoke constructor with member values. Expect properties match those provided to the constructor. */ -TEST_F(HandlerAndPolicyTest, testConstructorWithValues) { +TEST_F(HandlerAndPolicyTest, test_constructorWithValues) { auto handler = std::make_shared(); - HandlerAndPolicy handlerAndPolicy(handler, BlockingPolicy::NON_BLOCKING); + auto neitherNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); + HandlerAndPolicy handlerAndPolicy(handler, neitherNonBlockingPolicy); ASSERT_EQ(handlerAndPolicy.handler, handler); - ASSERT_EQ(handlerAndPolicy.policy, BlockingPolicy::NON_BLOCKING); + ASSERT_EQ(handlerAndPolicy.policy, neitherNonBlockingPolicy); } /** * Create empty and non-empty HandlerAndPolicy instances. Expect that empty instances are interpreted as false and * non-empty values are interpreted as true. */ -TEST_F(HandlerAndPolicyTest, testOperatorBool) { +TEST_F(HandlerAndPolicyTest, test_operatorBool) { auto handler = std::make_shared(); HandlerAndPolicy defaultHandlerAndPolicy; - HandlerAndPolicy firstHalfEmpty(nullptr, BlockingPolicy::BLOCKING); - HandlerAndPolicy secondHalfEmpty(handler, BlockingPolicy::NONE); - HandlerAndPolicy nonEmpty(handler, BlockingPolicy::BLOCKING); + auto audioBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true); + + HandlerAndPolicy firstHalfEmpty(nullptr, audioBlockingPolicy); + HandlerAndPolicy secondHalfEmpty(handler, BlockingPolicy()); + HandlerAndPolicy nonEmpty(handler, audioBlockingPolicy); ASSERT_FALSE(defaultHandlerAndPolicy); ASSERT_FALSE(firstHalfEmpty); ASSERT_FALSE(secondHalfEmpty); @@ -92,15 +95,16 @@ TEST_F(HandlerAndPolicyTest, testOperatorBool) { * Create instances with different and identical values. Expect that instances with different values test as * not equal and that instances with identical values test as equal. */ -TEST_F(HandlerAndPolicyTest, testOperatorEqualandNotEqual) { +TEST_F(HandlerAndPolicyTest, test_operatorEqualandNotEqual) { auto handler1 = std::make_shared(); auto handler2 = std::make_shared(); HandlerAndPolicy defaultHandlerAndPolicy; - HandlerAndPolicy handlerAndPolicy1(handler1, BlockingPolicy::NON_BLOCKING); - HandlerAndPolicy handlerAndPolicy1Clone(handler1, BlockingPolicy::NON_BLOCKING); - HandlerAndPolicy handlerAndPolicy2(handler1, BlockingPolicy::BLOCKING); - HandlerAndPolicy handlerAndPolicy3(handler2, BlockingPolicy::NON_BLOCKING); - HandlerAndPolicy handlerAndPolicy4(nullptr, BlockingPolicy::NON_BLOCKING); + auto audioNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, false); + HandlerAndPolicy handlerAndPolicy1(handler1, audioNonBlockingPolicy); + HandlerAndPolicy handlerAndPolicy1Clone(handler1, audioNonBlockingPolicy); + HandlerAndPolicy handlerAndPolicy2(handler1, BlockingPolicy(BlockingPolicy::MEDIUM_AUDIO, true)); + HandlerAndPolicy handlerAndPolicy3(handler2, audioNonBlockingPolicy); + HandlerAndPolicy handlerAndPolicy4(nullptr, audioNonBlockingPolicy); ASSERT_EQ(defaultHandlerAndPolicy, defaultHandlerAndPolicy); ASSERT_NE(defaultHandlerAndPolicy, handlerAndPolicy1); ASSERT_EQ(handlerAndPolicy1, handlerAndPolicy1Clone); diff --git a/AVSCommon/AVS/test/NamespaceAndNameTest.cpp b/AVSCommon/AVS/test/NamespaceAndNameTest.cpp index 738207c5aa..c7e5b87371 100644 --- a/AVSCommon/AVS/test/NamespaceAndNameTest.cpp +++ b/AVSCommon/AVS/test/NamespaceAndNameTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ class NamespaceAndNameTest : public ::testing::Test {}; /** * Invoke default constructor. Expect @c nameSpace and @c name properties are both empty strings. */ -TEST_F(NamespaceAndNameTest, testDefaultConstructor) { +TEST_F(NamespaceAndNameTest, test_defaultConstructor) { NamespaceAndName instance; ASSERT_TRUE(instance.nameSpace.empty()); ASSERT_TRUE(instance.name.empty()); @@ -56,7 +56,7 @@ TEST_F(NamespaceAndNameTest, testDefaultConstructor) { /** * Invoke constructor with member values. Expect properties match those provided to the constructor. */ -TEST_F(NamespaceAndNameTest, testConstructorWithValues) { +TEST_F(NamespaceAndNameTest, test_constructorWithValues) { NamespaceAndName instance(NAMESPACE_SPEECH_RECOGNIZER, NAME_RECOGNIZE); ASSERT_EQ(instance.nameSpace, NAMESPACE_SPEECH_RECOGNIZER); ASSERT_EQ(instance.name, NAME_RECOGNIZE); @@ -65,7 +65,7 @@ TEST_F(NamespaceAndNameTest, testConstructorWithValues) { /** * Create an @c std::unordered_map using NamespaceAndName values as keys. Expect that the keys map to distinct values. */ -TEST_F(NamespaceAndNameTest, testInUnorderedMap) { +TEST_F(NamespaceAndNameTest, test_inUnorderedMap) { std::unordered_map testMap; NamespaceAndName key1(NAMESPACE_SPEECH_RECOGNIZER, NAME_RECOGNIZE); NamespaceAndName key2(NAMESPACE_SPEAKER, NAME_SET_VOLUME); diff --git a/AVSCommon/CMakeLists.txt b/AVSCommon/CMakeLists.txt index 03c24ecbae..3df8548f31 100644 --- a/AVSCommon/CMakeLists.txt +++ b/AVSCommon/CMakeLists.txt @@ -5,20 +5,24 @@ find_package(CURL ${CURL_PACKAGE_CONFIG}) add_definitions("-DACSDK_LOG_MODULE=avsCommon") add_subdirectory("AVS") +add_subdirectory("SDKInterfaces") add_subdirectory("Utils") add_library(AVSCommon SHARED AVS/src/AVSDirective.cpp AVS/src/AVSMessage.cpp AVS/src/AVSMessageHeader.cpp - AVS/src/AbstractConnection.cpp + AVS/src/AbstractAVSConnectionManager.cpp + AVS/src/BlockingPolicy.cpp AVS/src/ExternalMediaPlayer/AdapterUtils.cpp AVS/src/AlexaClientSDKInit.cpp AVS/src/Attachment/Attachment.cpp AVS/src/Attachment/AttachmentManager.cpp + AVS/src/Attachment/AttachmentUtils.cpp AVS/src/Attachment/InProcessAttachment.cpp AVS/src/Attachment/InProcessAttachmentReader.cpp AVS/src/Attachment/InProcessAttachmentWriter.cpp + AVS/src/CapabilityConfiguration.cpp AVS/src/CapabilityAgent.cpp AVS/src/DialogUXStateAggregator.cpp AVS/src/EventBuilder.cpp @@ -26,15 +30,30 @@ add_library(AVSCommon SHARED AVS/src/HandlerAndPolicy.cpp AVS/src/MessageRequest.cpp AVS/src/NamespaceAndName.cpp + Utils/src/Bluetooth/SDPRecords.cpp + Utils/src/BluetoothEventBus.cpp Utils/src/Configuration/ConfigurationNode.cpp + Utils/src/DeviceInfo.cpp Utils/src/Executor.cpp Utils/src/FileUtils.cpp - Utils/src/JSONUtils.cpp + Utils/src/FormattedAudioStreamAdapter.cpp + Utils/src/JSON/JSONGenerator.cpp + Utils/src/JSON/JSONUtils.cpp + Utils/src/HTTP2/HTTP2GetMimeHeadersResult.cpp + Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp + Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp + Utils/src/HTTP2/HTTP2SendDataResult.cpp + Utils/src/LibcurlUtils/CallbackData.cpp Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp Utils/src/LibcurlUtils/CurlMultiHandleWrapper.cpp Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp Utils/src/LibcurlUtils/HttpPost.cpp + Utils/src/LibcurlUtils/HttpPut.cpp + Utils/src/LibcurlUtils/HTTPResponse.cpp Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp + Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp + Utils/src/LibcurlUtils/LibcurlHTTP2ConnectionFactory.cpp + Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp Utils/src/LibcurlUtils/LibcurlUtils.cpp Utils/src/Logger/ConsoleLogger.cpp Utils/src/Logger/Level.cpp @@ -47,14 +66,16 @@ add_library(AVSCommon SHARED Utils/src/Logger/LogStringFormatter.cpp Utils/src/Logger/ModuleLogger.cpp Utils/src/Logger/ThreadMoniker.cpp + Utils/src/MacAddressString.cpp Utils/src/Metrics.cpp + Utils/src/Network/InternetConnectionMonitor.cpp Utils/src/RequiresShutdown.cpp Utils/src/RetryTimer.cpp Utils/src/SafeCTimeAccess.cpp + Utils/src/Stopwatch.cpp Utils/src/Stream/StreamFunctions.cpp Utils/src/Stream/Streambuf.cpp Utils/src/StringUtils.cpp - Utils/src/TaskQueue.cpp Utils/src/TaskThread.cpp Utils/src/TimePoint.cpp Utils/src/TimeUtils.cpp @@ -66,6 +87,7 @@ target_include_directories(AVSCommon PUBLIC "${AVSCommon_SOURCE_DIR}/SDKInterfaces/include" "${AVSCommon_SOURCE_DIR}/Utils/include" "${RAPIDJSON_INCLUDE_DIR}" + "${MultipartParser_SOURCE_DIR}" ${CURL_INCLUDE_DIRS}) target_link_libraries(AVSCommon diff --git a/AVSCommon/SDKInterfaces/CMakeLists.txt b/AVSCommon/SDKInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..0130ac18bf --- /dev/null +++ b/AVSCommon/SDKInterfaces/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory("test") diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSConnectionManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSConnectionManagerInterface.h new file mode 100644 index 0000000000..1352adff88 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AVSConnectionManagerInterface.h @@ -0,0 +1,111 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AVSCONNECTIONMANAGERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AVSCONNECTIONMANAGERINTERFACE_H_ + +#include + +#include "AVSCommon/SDKInterfaces/ConnectionStatusObserverInterface.h" +#include "AVSCommon/SDKInterfaces/MessageObserverInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * This class reflects a connection to AVS and how it may be observed. + */ +class AVSConnectionManagerInterface { +public: + /// Type alias for brevity. + using ConnectionStatusObserverInterface = avsCommon::sdkInterfaces::ConnectionStatusObserverInterface; + /** + * Destructor. + */ + virtual ~AVSConnectionManagerInterface() = default; + + /** + * Enable the AVSConnectionManager object to make connections to AVS. Once enabled, the object will attempt to + * create a connection to AVS. If the object is already connected, this function will do nothing. + */ + virtual void enable() = 0; + + /** + * Disable the AVSConnectionManager object. If the object is currently connected to AVS, then calling this + * function will cause the connection to be closed. If the object is not connected, then calling this function + * will do nothing. + */ + virtual void disable() = 0; + + /** + * Returns if the object is enabled for making connections to AVS. + * + * @return Whether this Connection object is enabled to make connections. + */ + virtual bool isEnabled() = 0; + + /** + * This function causes the object, if enabled, to create new connection to AVS. If the object is already + * connected, then that connection will be closed and a new one created. If the object is not connected, but + * perhaps in the process of waiting for its next connection attempt, then its waiting policy will be reset and + * it will attempt to create a new connection immediately. + * If the object is disabled, then this function will do nothing. + */ + virtual void reconnect() = 0; + + /** + * Returns whether the AVS connection is established. If the connection is pending, @c false will be returned. + * + * @return Whether the AVS connection is established. + */ + virtual bool isConnected() const = 0; + + /** + * Adds an observer to be notified of message receptions. + * + * @param observer The observer object to add. + */ + virtual void addMessageObserver(std::shared_ptr observer) = 0; + + /** + * Removes an observer from being notified of message receptions. + * + * @param observer The observer object to remove. + */ + virtual void removeMessageObserver( + std::shared_ptr observer) = 0; + + /** + * Adds an observer to be notified of connection status changes. The observer will be notified of the current + * connection status before this function returns. + * + * @param observer The observer to add. + */ + virtual void addConnectionStatusObserver(std::shared_ptr observer) = 0; + + /** + * Removes an observer from being notified of connection status changes. + * + * @param observer The observer to remove. + */ + virtual void removeConnectionStatusObserver(std::shared_ptr observer) = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AVSCONNECTIONMANAGERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/AudioFactoryInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/AudioFactoryInterface.h index 14e864ea71..ccd0ece7b3 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/AudioFactoryInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/AudioFactoryInterface.h @@ -19,6 +19,7 @@ #include #include "AVSCommon/SDKInterfaces/Audio/AlertsAudioFactoryInterface.h" +#include "AVSCommon/SDKInterfaces/Audio/CommunicationsAudioFactoryInterface.h" #include "AVSCommon/SDKInterfaces/Audio/NotificationsAudioFactoryInterface.h" namespace alexaClientSDK { @@ -42,6 +43,11 @@ class AudioFactoryInterface { * This shares a factory that produces audio streams for the notifications components. */ virtual std::shared_ptr notifications() const = 0; + + /** + * This shares a factory that produces audio streams for the communications components. + */ + virtual std::shared_ptr communications() const = 0; }; } // namespace audio diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/CommunicationsAudioFactoryInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/CommunicationsAudioFactoryInterface.h new file mode 100644 index 0000000000..7a3ec988d7 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/CommunicationsAudioFactoryInterface.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_COMMUNICATIONSAUDIOFACTORYINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_COMMUNICATIONSAUDIOFACTORYINTERFACE_H_ + +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { + +/** + * This is an interface to produce sounds for the Communications interface. + */ +class CommunicationsAudioFactoryInterface { +public: + virtual ~CommunicationsAudioFactoryInterface() = default; + + virtual std::function()> callConnectedRingtone() const = 0; + virtual std::function()> callDisconnectedRingtone() const = 0; + virtual std::function()> outboundRingtone() const = 0; + virtual std::function()> dropInConnectedRingtone() const = 0; + virtual std::function()> callIncomingRingtone() const = 0; +}; + +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_COMMUNICATIONSAUDIOFACTORYINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerConfigurationInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerConfigurationInterface.h new file mode 100644 index 0000000000..b1a211e739 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerConfigurationInterface.h @@ -0,0 +1,106 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERCONFIGURATIONINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERCONFIGURATIONINTERFACE_H_ + +#include +#include + +#include +#include + +#include "EqualizerTypes.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { + +/** + * Interface to provide a configuration of equalizer capabilities supported by the device. + */ +class EqualizerConfigurationInterface { +public: + /** + * Destructor. + */ + virtual ~EqualizerConfigurationInterface() = default; + + /** + * Returns true if equalizer is enabled, false otherwise. + * @return True if equalizer is enabled, false otherwise. + */ + virtual bool isEnabled() const = 0; + + /** + * Returns a set of EQ bands supported by the device. + * + * @return A set of EQ bands supported by the device. + */ + virtual std::set getSupportedBands() const = 0; + + /** + * Returns a set of EQ modes supported by the device. + * + * @return A set of EQ modes supported by the device. + */ + virtual std::set getSupportedModes() const = 0; + + /** + * Returns the minimum band value supported by the device. + * + * @return The minimum band value supported by the device. + */ + virtual int getMinBandLevel() const = 0; + + /** + * Returns the maximum band value supported by the device. + * + * @return The maximum band value supported by the device. + */ + virtual int getMaxBandLevel() const = 0; + + /** + * Returns @c EqualizerState object defining default values for equalizer mode and band levels. These values should + * be used when resetting any band to its default level. + * + * @return @c EqualizerState object representing the default state. + */ + virtual EqualizerState getDefaultState() const = 0; + + /** + * Checks if band is supported by the device. + * + * @param band @c EqualizerBand to check. + * @return True if band is supported, false otherwise. + */ + virtual bool isBandSupported(EqualizerBand band) const = 0; + + /** + * Checks if mode is supported by the device. + * + * @param mode @c EqualizerMode to check. + * @return True if mode is supported, false otherwise. + */ + virtual bool isModeSupported(EqualizerMode mode) const = 0; +}; + +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERCONFIGURATIONINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerControllerListenerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerControllerListenerInterface.h new file mode 100644 index 0000000000..f9d1bab870 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerControllerListenerInterface.h @@ -0,0 +1,49 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERCONTROLLERLISTENERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERCONTROLLERLISTENERINTERFACE_H_ + +#include "EqualizerTypes.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { + +/** + * An interface to listen for @c EqualizerController state changes. + */ +class EqualizerControllerListenerInterface { +public: + /** + * Destructor. + */ + virtual ~EqualizerControllerListenerInterface() = default; + + /** + * Receives the new state of the @c EqualizerController. This callback is called after all changes has been applied. + * + * @param newState New state of the @c EqualizerController. + */ + virtual void onEqualizerStateChanged(const EqualizerState& newState) = 0; +}; + +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERCONTROLLERLISTENERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerInterface.h new file mode 100644 index 0000000000..40bcf97349 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerInterface.h @@ -0,0 +1,63 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERINTERFACE_H_ + +#include "EqualizerTypes.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { + +/** + * Interface performing the actual equalization of the audio. + */ +class EqualizerInterface { +public: + /** + * Destructor. + */ + virtual ~EqualizerInterface() = default; + + /** + * Changes the equalization parameters for the audio. + * + * @param bandLevelMap EQ bands and their levels to be applied. Levels for all bands supported must be provided. + */ + virtual void setEqualizerBandLevels(EqualizerBandLevelMap bandLevelMap) = 0; + + /** + * Returns the minimum band level (dB) supported by this equalizer. + * + * @return The minimum band level (dB) supported by this equalizer. + */ + virtual int getMinimumBandLevel() = 0; + + /** + * Returns the maximum band level (dB) supported by this equalizer. + * + * @return The maximum band level (dB) supported by this equalizer. + */ + virtual int getMaximumBandLevel() = 0; +}; + +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerModeControllerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerModeControllerInterface.h new file mode 100644 index 0000000000..e24c3c68a3 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerModeControllerInterface.h @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERMODECONTROLLERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERMODECONTROLLERINTERFACE_H_ + +#include "EqualizerTypes.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { + +/** + * Interface to handle equalizer modes. It is up to device manufacturer to implement the mode behavior. For example + * vendor may wish to apply: + * - Equalizer preset + * - Volume scaling + * - Audio effects such as reverb or surround. + */ +class EqualizerModeControllerInterface { +public: + /** + * Destructor. + */ + virtual ~EqualizerModeControllerInterface() = default; + + /** + * Changes the current equalizer mode. Equalizer state listeners will be notified only if this method returns true. + * It is safe to change equalizer band values from this function, but changing mode is not allowed. + * + * @param mode The @c EqualizerMode to set. + * @return True if mode has been successfully set, false otherwise. + */ + virtual bool setEqualizerMode(EqualizerMode mode) = 0; +}; + +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERMODECONTROLLERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerStorageInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerStorageInterface.h new file mode 100644 index 0000000000..3388093d50 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerStorageInterface.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERSTORAGEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERSTORAGEINTERFACE_H_ + +#include + +#include + +#include "EqualizerTypes.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { + +/** + * Interface to persist the last set to @c EqualizerController. + */ +class EqualizerStorageInterface { +public: + /** + * Destructor. + */ + virtual ~EqualizerStorageInterface() = default; + + /** + * Saves equalizer state to the storage. + * + * @param state @c EqualizerState to store. + */ + virtual void saveState(const EqualizerState& state) = 0; + + /** + * Loads a stored equalizer state from the storage. + * + * @return State retrieved from the storage or @c nullptr if no state is stored. + */ + virtual avsCommon::utils::error::SuccessResult loadState() = 0; + + /** + * Clears all the EQ settings from the storage. The next call to @c loadState() must return no state unless another + * state is saved between @c clear() and @c loadState(). + */ + virtual void clear() = 0; +}; + +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERSTORAGEINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerTypes.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerTypes.h new file mode 100644 index 0000000000..0a20f3a289 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Audio/EqualizerTypes.h @@ -0,0 +1,217 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERTYPES_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERTYPES_H_ + +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { + +/** + * Enum representing all the equalizer bands supported by SDK. Customer device may support only a subset of these. + */ +enum class EqualizerBand { + /// Bass equalizer band. + BASS, + /// Mid-range equalizer band. + MIDRANGE, + /// Treble equalizer band. + TREBLE +}; + +/** + * An iterable array that contains all the values of @c EqualizerBand in order from lower frequencies to higher. + */ +const std::array EqualizerBandValues = { + {EqualizerBand::BASS, EqualizerBand::MIDRANGE, EqualizerBand::TREBLE}}; + +/** + * Enum representing all the equalizer modes supported by SDK. Customer device may support only a subset of these. + * 'NONE' represents a special mode to reflect custom band values with no named preset. + */ +enum class EqualizerMode { + /// Equalizer mode representing default (no mode) behavior + NONE, + /// Movie equalizer mode. + MOVIE, + /// Music equalizer mode. + MUSIC, + /// Night equalizer mode. + NIGHT, + /// Sport equalizer mode. + SPORT, + /// TV equalizer mode. + TV +}; + +/** + * An iterable array that contains all the values of @c EqualizerMode + */ +const std::array EqualizerModeValues = {{EqualizerMode::NONE, + EqualizerMode::MOVIE, + EqualizerMode::MUSIC, + EqualizerMode::NIGHT, + EqualizerMode::SPORT, + EqualizerMode::TV}}; + +/// A collection of bands with their level values. This is an alias for @c EqualizerBand to band level (int) map. +using EqualizerBandLevelMap = std::unordered_map; + +/** + * Structure to represent Current state of the equalizer. + */ +struct EqualizerState { + /// Equalizer mode selected. Use @c EqualizerMode::NONE value to represent no specific mode. + EqualizerMode mode; + /// Device supported bands with their levels. + EqualizerBandLevelMap bandLevels; +}; + +inline bool operator==(const EqualizerState& state1, const EqualizerState& state2) { + return state1.mode == state2.mode && state1.bandLevels == state2.bandLevels; +} + +/** + * Provides a string representation for @c EqualizerBand value. + * + * @param band @c EqualizerBand to convert. + * @return A string representation for @c EqualizerBand value. + */ +inline std::string equalizerBandToString(EqualizerBand band) { + switch (band) { + case EqualizerBand::BASS: + return "BASS"; + case EqualizerBand::MIDRANGE: + return "MIDRANGE"; + case EqualizerBand::TREBLE: + return "TREBLE"; + } + + return "UNKNOWN"; +} + +/** + * Write a @c EqualizerBand value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param band The @c EqualizerBand value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, EqualizerBand band) { + return stream << equalizerBandToString(band); +} + +/** + * Provides a string representation for @c EqualizerMode value. + * + * @param mode @c EqualizerMode to convert. + * @return A string representation for @c EqualizerMode value. + */ +inline std::string equalizerModeToString(EqualizerMode mode) { + switch (mode) { + case EqualizerMode::NONE: + return "NONE"; + case EqualizerMode::MOVIE: + return "MOVIE"; + case EqualizerMode::MUSIC: + return "MUSIC"; + case EqualizerMode::NIGHT: + return "NIGHT"; + case EqualizerMode::SPORT: + return "SPORT"; + case EqualizerMode::TV: + return "TV"; + } + + return "UNKNOWN"; +} + +/** + * Write a @c EqualizerMode value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param mode The @c EqualizerMode value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, EqualizerMode mode) { + return stream << equalizerModeToString(mode); +} + +/** + * Parses a string to @c EqualizerBand value. + * + * @param stringValue a string containing the value to parse. + * @return @c SuccessResult describing the result of the operation + */ +inline utils::error::SuccessResult stringToEqualizerBand(const std::string& stringValue) { + if (stringValue == "BASS") { + return utils::error::SuccessResult::success(EqualizerBand::BASS); + } + if (stringValue == "MIDRANGE") { + return utils::error::SuccessResult::success(EqualizerBand::MIDRANGE); + } + if (stringValue == "TREBLE") { + return utils::error::SuccessResult::success(EqualizerBand::TREBLE); + } + + return utils::error::SuccessResult::failure(); +} + +/** + * Parses a string to @c EqualizerMode value. + * + * @param stringValue a string containing the value to parse. + * @param[out] mode Pointer to @c EqualizerMode to receive parsed result. If function fails, the value is not + * changed. + * @return true if parsing was successful, false otherwise. + */ +inline utils::error::SuccessResult stringToEqualizerMode(const std::string& stringValue) { + if (stringValue == "NONE") { + return utils::error::SuccessResult::success(EqualizerMode::NONE); + } + if (stringValue == "MOVIE") { + return utils::error::SuccessResult::success(EqualizerMode::MOVIE); + } + if (stringValue == "MUSIC") { + return utils::error::SuccessResult::success(EqualizerMode::MUSIC); + } + if (stringValue == "NIGHT") { + return utils::error::SuccessResult::success(EqualizerMode::NIGHT); + } + if (stringValue == "SPORT") { + return utils::error::SuccessResult::success(EqualizerMode::SPORT); + } + if (stringValue == "TV") { + return utils::error::SuccessResult::success(EqualizerMode::TV); + } + + return utils::error::SuccessResult::failure(); +} + +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERTYPES_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerInterface.h index ba76adaddc..672d3b637b 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerObserverInterface.h index 7828ca60b8..8e118e3fec 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthDelegateInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthDelegateInterface.h index 6a3e0ac60e..c2f828af60 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthDelegateInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthDelegateInterface.h @@ -61,12 +61,19 @@ class AuthDelegateInterface { virtual void removeAuthObserver(std::shared_ptr observer) = 0; /** - * Get the current LWA authoriation token. + * Get the current LWA authorization token. * * @return The current authorization token. The returned value will be empty if an authorization token * has yet to be acquired or if the most recently acquired token has expired. */ virtual std::string getAuthToken() = 0; + + /** + * Receive notification that an operation using the specified auth token experienced an authorization failure. + * + * @param token The token used to authorize the forbidden operation. + */ + virtual void onAuthFailure(const std::string& token) = 0; }; } // namespace sdkInterfaces diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthObserverInterface.h index 3c68f6de53..834a9b903d 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthObserverInterface.h @@ -56,10 +56,22 @@ class AuthObserverInterface { SERVER_ERROR, /// The request is missing a required parameter, has an invalid value, or is otherwise improperly formed. INVALID_REQUEST, + /// One of the values in the request was invalid. + INVALID_VALUE, /// The authorization code is invalid, expired, revoked, or was issued to a different client. AUTHORIZATION_EXPIRED, /// The client specified the wrong token type. - UNSUPPORTED_GRANT_TYPE + UNSUPPORTED_GRANT_TYPE, + /// Invalid code pair provided in Code-based linking token request. + INVALID_CODE_PAIR, + /// Waiting for user to authorize the specified code pair. + AUTHORIZATION_PENDING, + /// Client should slow down in the rate of requests polling for an access token. + SLOW_DOWN, + /// Internal error in client code. + INTERNAL_ERROR, + /// Client ID not valid for use with code based linking. + INVALID_CBL_CLIENT_ID }; /** @@ -70,6 +82,9 @@ class AuthObserverInterface { /** * Notification that an authorization state has changed. * + * @note Implementations of this method must not call AuthDelegate methods because the AuthDelegate + * may be in a 'locked' state at the time this call is made. + * * @param newState The new state of the authorization token. * @param error The error associated to the state change. */ @@ -86,19 +101,15 @@ class AuthObserverInterface { inline std::ostream& operator<<(std::ostream& stream, const AuthObserverInterface::State& state) { switch (state) { case AuthObserverInterface::State::UNINITIALIZED: - stream << "UNINTIALIZED"; - break; + return stream << "UNINTIALIZED"; case AuthObserverInterface::State::REFRESHED: - stream << "REFRESHED"; - break; + return stream << "REFRESHED"; case AuthObserverInterface::State::EXPIRED: - stream << "EXPIRED"; - break; + return stream << "EXPIRED"; case AuthObserverInterface::State::UNRECOVERABLE_ERROR: - stream << "UNRECOVERABLE_ERROR"; - break; + return stream << "UNRECOVERABLE_ERROR"; } - return stream; + return stream << "Unknown AuthObserverInterface::State!: " << state; } /** @@ -111,31 +122,35 @@ inline std::ostream& operator<<(std::ostream& stream, const AuthObserverInterfac inline std::ostream& operator<<(std::ostream& stream, const AuthObserverInterface::Error& error) { switch (error) { case AuthObserverInterface::Error::SUCCESS: - stream << "SUCCESS"; - break; + return stream << "SUCCESS"; case AuthObserverInterface::Error::UNKNOWN_ERROR: - stream << "UNKNOWN_ERROR"; - break; + return stream << "UNKNOWN_ERROR"; case AuthObserverInterface::Error::AUTHORIZATION_FAILED: - stream << "AUTHORIZATION_FAILED"; - break; + return stream << "AUTHORIZATION_FAILED"; case AuthObserverInterface::Error::UNAUTHORIZED_CLIENT: - stream << "UNAUTHORIZED_CLIENT"; - break; + return stream << "UNAUTHORIZED_CLIENT"; case AuthObserverInterface::Error::SERVER_ERROR: - stream << "SERVER_ERROR"; - break; + return stream << "SERVER_ERROR"; case AuthObserverInterface::Error::INVALID_REQUEST: - stream << "INVALID_REQUEST"; - break; + return stream << "INVALID_REQUEST"; + case AuthObserverInterface::Error::INVALID_VALUE: + return stream << "INVALID_VALUE"; case AuthObserverInterface::Error::AUTHORIZATION_EXPIRED: - stream << "AUTHORIZATION_EXPIRED"; - break; + return stream << "AUTHORIZATION_EXPIRED"; case AuthObserverInterface::Error::UNSUPPORTED_GRANT_TYPE: - stream << "UNSUPPORTED_GRANT_TYPE"; - break; + return stream << "UNSUPPORTED_GRANT_TYPE"; + case AuthObserverInterface::Error::INVALID_CODE_PAIR: + return stream << "INVALID_CODE_PAIR"; + case AuthObserverInterface::Error::AUTHORIZATION_PENDING: + return stream << "AUTHORIZATION_PENDING"; + case AuthObserverInterface::Error::SLOW_DOWN: + return stream << "SLOW_DOWN"; + case AuthObserverInterface::Error::INTERNAL_ERROR: + return stream << "INTERNAL_ERROR"; + case AuthObserverInterface::Error::INVALID_CBL_CLIENT_ID: + return stream << "INVALID_CBL_CLIENT_ID"; } - return stream; + return stream << "Unknown AuthObserverInterface::Error!: " << error; } } // namespace sdkInterfaces diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h new file mode 100644 index 0000000000..24a428d4a1 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h @@ -0,0 +1,194 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICEINTERFACE_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { + +/** + * clang-format off + * Represents the state of the device. The state diagram is as follows: + * + * +------UNPAIRED-------------+ + * | | + * +------UNPAIRED---+ | + * V | | + * FOUND -> PAIRED -> IDLE -> CONNECTED + * ^ | + * +DISCONNECTED-+ + * clang-format on + */ +enum class DeviceState { + // A device has been discovered. + FOUND, + // [Transitional] The device has been unpaired. + UNPAIRED, + // [Transitional] The device has successfully paired. + PAIRED, + // A paired device. + IDLE, + // [Transitional] A device has successfully disconnected. + DISCONNECTED, + // A device that has successfully connected. + CONNECTED +}; + +/** + * Converts the @c DeviceState enum to a string. + * + * @param The @c DeviceState to convert. + * @return A string representation of the @c DeviceState. + */ +inline std::string deviceStateToString(DeviceState state) { + switch (state) { + case DeviceState::FOUND: + return "FOUND"; + case DeviceState::UNPAIRED: + return "UNPAIRED"; + case DeviceState::PAIRED: + return "PAIRED"; + case DeviceState::IDLE: + return "IDLE"; + case DeviceState::DISCONNECTED: + return "DISCONNECTED"; + case DeviceState::CONNECTED: + return "CONNECTED"; + } + + return "UNKNOWN"; +} + +/** + * Overload for the @c DeviceState enum. This will write the @c DeviceState as a string to the provided stream. + * + * @param An ostream to send the DeviceState as a string. + * @param The @c DeviceState to convert. + * @return The stream. + */ +inline std::ostream& operator<<(std::ostream& stream, const DeviceState state) { + return stream << deviceStateToString(state); +} + +/// Represents a Bluetooth Device. +class BluetoothDeviceInterface { +public: + /// Destructor + virtual ~BluetoothDeviceInterface() = default; + + /** + * Getter for the MAC address. + * + * @return The MAC address of the Bluetooth Device. + */ + virtual std::string getMac() const = 0; + + /** + * Getter for the friendly name. + * + * @return The friendly name of the Bluetooth Device. + */ + virtual std::string getFriendlyName() const = 0; + + /** + * Getter for the @c DeviceState. + * + * @return The @c DeviceState of the current device. + */ + virtual DeviceState getDeviceState() = 0; + + /** + * Getter for the paired state of the device. This should return + * the state after any pending state changes have been resolved. + * + * @return A bool representing whether the device is paired. + */ + virtual bool isPaired() = 0; + + /** + * Initiate a pair with this device. + * + * @return Indicates whether pairing was successful. + */ + virtual std::future pair() = 0; + + /** + * Initiate an unpair with this device. + * + * @return Indicates whether the unpairing was successful. + */ + virtual std::future unpair() = 0; + + /** + * Getter for the paired state of the device. This should return + * the state after any pending state changes have been resolved. + * + * @return A bool representing whether the device is connected. + */ + virtual bool isConnected() = 0; + + /** + * Initiate a connect with this device. + * + * @return Indicates whether connecting was successful. + */ + virtual std::future connect() = 0; + + /** + * Initiate a disconnect with this device. + * + * @return Indicates whether disconnect was successful. + */ + virtual std::future disconnect() = 0; + + /// @return The Bluetooth Services that this device supports. + virtual std::vector> getSupportedServices() = 0; + + // TODO : Generic getService method. + /// @return A pointer to an instance of the @c A2DPSourceInterface if supported, else a nullptr. + virtual std::shared_ptr getA2DPSource() = 0; + + /// @return A pointer to an instance of the @c A2DPSinkInterface if supported, else a nullptr. + virtual std::shared_ptr getA2DPSink() = 0; + + /// @return A pointer to an instance of the @c AVRCPTargetInterface if supported, else a nullptr. + virtual std::shared_ptr getAVRCPTarget() = 0; + + /// @return A pointer to an instance of the @c AVRCPControllerInterface if supported, else a nullptr. + virtual std::shared_ptr getAVRCPController() = 0; +}; + +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICEINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceManagerInterface.h new file mode 100644 index 0000000000..b58f9eb5c4 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceManagerInterface.h @@ -0,0 +1,72 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICEMANAGERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICEMANAGERINTERFACE_H_ + +#include +#include + +#include "AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h" +#include "AVSCommon/SDKInterfaces/Bluetooth/BluetoothHostControllerInterface.h" +#include "AVSCommon/Utils/Bluetooth/BluetoothEventBus.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { + +/** + * This component is a starting point of any platform specific implementation of bluetooth functionality. It is + * responsible for ownership of @c BluetoothDeviceInterface objects and @c BluetootHostController objects. + * + */ +class BluetoothDeviceManagerInterface { +public: + /** + * Destructor + */ + virtual ~BluetoothDeviceManagerInterface() = default; + + /** + * Get @c BluetoothHostControllerInterface instance + * @return Pointer to a @c BluetoothHostControllerInterface instance + */ + virtual std::shared_ptr + getHostController() = 0; + + /** + * Get a list of devices the Host Controller is aware of. This list must contain: + * + * i) Paired devices. + * ii) Devices found during the scanning process. + */ + virtual std::list> + getDiscoveredDevices() = 0; + + /** + * Get the @c BluetoothEventBus used by this device manager to post bluetooth related events. + * + * @return A @c BluetoothEventBus object associated with the device manager. + */ + virtual std::shared_ptr getEventBus() = 0; +}; + +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICEMANAGERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceObserverInterface.h new file mode 100644 index 0000000000..f2de396932 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceObserverInterface.h @@ -0,0 +1,73 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICEOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICEOBSERVERINTERFACE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { + +/** + * This interface allows a derived class to know when a bluetooth device is connected or disconnected. + */ +class BluetoothDeviceObserverInterface { +public: + /** + * The observable attributes of the bluetooth device. + */ + struct DeviceAttributes { + /** + * Constructor. + */ + DeviceAttributes() = default; + + /// The name of the active bluetooth device + std::string name; + + /// The bluetooth services this device supports. + std::unordered_set supportedServices; + }; + + /** + * Destructor. + */ + virtual ~BluetoothDeviceObserverInterface() = default; + + /** + * Used to notify the observer when an active bluetooth device is connected. + * + * @param attributes The @c DeviceAttributes of the active bluetooth device. + */ + virtual void onActiveDeviceConnected(const DeviceAttributes& attributes) = 0; + + /** + * Used to notify the observer when an active bluetooth device is disconnected. + * + * @param attributes The @c DeviceAttributes of the active bluetooth device. + */ + virtual void onActiveDeviceDisconnected(const DeviceAttributes& attributes) = 0; +}; + +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHDEVICEOBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothHostControllerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothHostControllerInterface.h new file mode 100644 index 0000000000..76dd841db1 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothHostControllerInterface.h @@ -0,0 +1,100 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHHOSTCONTROLLERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHHOSTCONTROLLERINTERFACE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { + +/** + * An interface to represent the HostControllerInterface on the local system. + * This is responsible for Scanning and Discovery. + */ +class BluetoothHostControllerInterface { +public: + /// Destructor + virtual ~BluetoothHostControllerInterface() = default; + + /** + * Getter for the MAC address of the adapter. + * + * @return The MAC address of the adapter. + */ + virtual std::string getMac() const = 0; + + /** + * Getter for the friendly name of the adapter. + * + * @return The friendly name of the adapter. + */ + virtual std::string getFriendlyName() const = 0; + + /** + * Getter for the discoverability of the device. This must wait until + * any prior enterDiscoverableMode or exitDiscoverableMode methods have finished. + * + * @return Whether the device is currently discoverable by other devices. + */ + virtual bool isDiscoverable() const = 0; + + /** + * Set the adapter to become discoverable. + * + * @return Indicates whether the operation was successful. + */ + virtual std::future enterDiscoverableMode() = 0; + + /** + * Set the adapter to become non-discoverable. + * + * @return Indicates whether the operation was successful. + */ + virtual std::future exitDiscoverableMode() = 0; + + /** + * Getter for the scanning state of the device. This must wait until + * any prior startScan or stopScan methods have finished. + * + * @return Whether the device is currently scanning for other devices. + */ + virtual bool isScanning() const = 0; + + /** + * Set the adapter to start scanning. + * + * @return Indicates whether the operation was successful. + */ + virtual std::future startScan() = 0; + + /** + * Set the adapter to stop scanning. + * + * @return Indicates whether the operation was successful. + */ + virtual std::future stopScan() = 0; +}; + +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_BLUETOOTHHOSTCONTROLLERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSinkInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSinkInterface.h new file mode 100644 index 0000000000..b2f600e4a3 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSinkInterface.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_A2DPSINKINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_A2DPSINKINTERFACE_H_ + +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { + +/** + * Interface to support A2DP streaming from SDK to bluetooth device. + */ +class A2DPSinkInterface : public BluetoothServiceInterface { +public: + /// The Service UUID. + static constexpr const char* UUID = "0000110b-0000-1000-8000-00805f9b34fb"; + + /// The Service Name. + static constexpr const char* NAME = "AudioSink"; +}; + +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_A2DPSINKINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSourceInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSourceInterface.h new file mode 100644 index 0000000000..f50860fb4f --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSourceInterface.h @@ -0,0 +1,60 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_A2DPSOURCEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_A2DPSOURCEINTERFACE_H_ + +#include + +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h" +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { + +/** + * Interface to support A2DP streaming from bluetooth device to SDK + */ +class A2DPSourceInterface : public BluetoothServiceInterface { +public: + /// The Service UUID. + static constexpr const char* UUID = "0000110a-0000-1000-8000-00805f9b34fb"; + + /// The Service Name. + static constexpr const char* NAME = "AudioSource"; + + /** + * Returns the stream containing the decoded raw PCM data sent by the connected device. + * + * @return A shared_ptr to a @c FormattedAudioStreamAdapter object to be consumed. + */ + virtual std::shared_ptr getSourceStream() = 0; + + /** + * Destructor. + */ + virtual ~A2DPSourceInterface() = default; +}; + +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_A2DPSOURCEINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPControllerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPControllerInterface.h new file mode 100644 index 0000000000..835bdeac80 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPControllerInterface.h @@ -0,0 +1,54 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_AVRCPCONTROLLERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_AVRCPCONTROLLERINTERFACE_H_ + +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { + +/// Used to implement an instance of AVRCPController (Audio/Video Remote Control Profile). +class AVRCPControllerInterface : public BluetoothServiceInterface { +public: + /** + * The Service UUID. + * 110e is the legacy UUID used as both the identifier for the AVRCP Profile as well as the AVRCP Controller service + * before v1.3. 110f is the UUID used for AVRCP Controller service in newer versions of AVRCP. + * However, the 110e record must always be present, in later versions of AVRCP for backwards compabitibility. + * We will use 110e as the identifying record. + */ + static constexpr const char* UUID = "0000110e-0000-1000-8000-00805f9b34fb"; + + /// The Service Name. + static constexpr const char* NAME = "A/V_RemoteControl"; + + /** + * Destructor. + */ + virtual ~AVRCPControllerInterface() = default; +}; + +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_AVRCPCONTROLLERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h new file mode 100644 index 0000000000..c190137c9b --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h @@ -0,0 +1,127 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_AVRCPTARGETINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_AVRCPTARGETINTERFACE_H_ + +#include +#include + +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { + +// TODO: Move to own enum file. +/// An Enum representing AVRCP commands. +enum class AVRCPCommand { + /// A Play command. + PLAY, + + /// A Pause command. + PAUSE, + + /// A Next command. + NEXT, + + /// A Previous command. If issued at the beginning of a song, the previous track will be selected. + PREVIOUS +}; + +/** + * Converts the @c AVRCPCommand enum to a string. + * + * @param cmd The @c AVRCPCommand to convert. + * @return A string representation of the @c AVRCPCommand. + */ +inline std::string commandToString(AVRCPCommand cmd) { + switch (cmd) { + case AVRCPCommand::PLAY: + return "PLAY"; + case AVRCPCommand::PAUSE: + return "PAUSE"; + case AVRCPCommand::NEXT: + return "NEXT"; + case AVRCPCommand::PREVIOUS: + return "PREVIOUS"; + } + + return "UNKNOWN"; +} + +/** + * Overload for the @c AVRCPCommand enum. This will write the @c AVRCPCommand as a string to the provided stream. + * + * @param stream An ostream to send the DeviceState as a string. + * @param cmd The @c AVRCPCommand to convert. + * @return The stream. + */ +inline std::ostream& operator<<(std::ostream& stream, const AVRCPCommand cmd) { + return stream << commandToString(cmd); +} + +/// Used to implement an instance of AVRCPTarget (Audio/Video Remote Control Profile). +class AVRCPTargetInterface : public BluetoothServiceInterface { +public: + /// The Service UUID. + static constexpr const char* UUID = "0000110c-0000-1000-8000-00805f9b34fb"; + + /// The Service Name. + static constexpr const char* NAME = "A/V_RemoteControlTarget"; + + /** + * Sends a play command to the device supporting AVRCPTarget. + * + * @return A boolean indicating the success of the function. + */ + virtual bool play() = 0; + + /** + * Sends a pause command to the device supporting AVRCPTarget. + * + * @return A boolean indicating the success of the function. + */ + virtual bool pause() = 0; + + /** + * Sends a next command to the device supporting AVRCPTarget. + * + * @return A boolean indicating the success of the function. + */ + virtual bool next() = 0; + + /** + * Sends a previous command to the device supporting AVRCPTarget. + * + * @return A boolean indicating the success of the function. + */ + virtual bool previous() = 0; + + /** + * Destructor. + */ + virtual ~AVRCPTargetInterface() = default; +}; + +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_AVRCPTARGETINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h new file mode 100644 index 0000000000..69c9af4b6c --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_BLUETOOTHSERVICEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_BLUETOOTHSERVICEINTERFACE_H_ + +#include + +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/SDPRecordInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { + +/// Interface representing a BluetoothService. +class BluetoothServiceInterface { +public: + /** + * Returns an SDPRecord for the service. + * + * @return A @c SDPRecordInterface for the service. + */ + virtual std::shared_ptr getRecord() = 0; + + /// Destructor. + virtual ~BluetoothServiceInterface() = default; + + /// Called for any necessary setup of the service. + virtual void setup() = 0; + + /// Called for any necessary cleanup of the service. + virtual void cleanup() = 0; +}; + +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_BLUETOOTHSERVICEINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/SDPRecordInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/SDPRecordInterface.h new file mode 100644 index 0000000000..3a0e18334f --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/SDPRecordInterface.h @@ -0,0 +1,61 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_SDPRECORDINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_SDPRECORDINTERFACE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace bluetooth { +namespace services { + +/// Used to implement ServiceDiscoveryProtocol records. This allows identification of the service. +class SDPRecordInterface { +public: + /** + * The base UUID of a Bluetooth service. All service UUIDs are calculated from this. + * Services have a short uuid assigned that is either uuid16 or uuid32. For example: + * + * AudioSource (A2DP Source) + * Short UUID: 110a + * UUID: 0000110a-0000-1000-8000-00805f9b34fb + */ + static const std::string BASE_UUID() { + return "00000000-0000-1000-8000-00805f9b34fb"; + } + + /// Destructor. + virtual ~SDPRecordInterface() = default; + + /// @return The name of the service. + virtual std::string getName() const = 0; + + /// @return The UUID of the service. + virtual std::string getUuid() const = 0; + + /// @return The version of the service. + virtual std::string getVersion() const = 0; +}; + +} // namespace services +} // namespace bluetooth +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_BLUETOOTH_SERVICES_SDPRECORDINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallManagerInterface.h new file mode 100644 index 0000000000..9c429af071 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallManagerInterface.h @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CALLMANAGERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CALLMANAGERINTERFACE_H_ + +#include +#include + +#include +#include +#include +#include "AVSCommon/Utils/RequiresShutdown.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * This class provides an interface to the @c CallManager. + */ +class CallManagerInterface + : public utils::RequiresShutdown + , public avsCommon::avs::CapabilityAgent + , public avsCommon::sdkInterfaces::ConnectionStatusObserverInterface { +public: + /** + * Constructor. + * + * @param objectName The name of the class or object which requires shutdown calls. Used in log messages when + * problems are detected in shutdown or destruction sequences. + * @param avsNamespace The namespace of the CapabilityAgent. + * @param exceptionEncounteredSender Object to use to send Exceptions to AVS. + */ + CallManagerInterface( + const std::string& objectName, + const std::string& avsNamespace, + std::shared_ptr exceptionEncounteredSender); + + /** + * Destructor + */ + virtual ~CallManagerInterface() = default; + + /** + * Adds a CallStateObserverInterface to the group of observers. + * + * @param observer The observer to add. + */ + virtual void addObserver(std::shared_ptr observer) = 0; + + /** + * Removes a CallStateObserverInterface from the group of observers. + * + * @param observer The observer to remove. + */ + virtual void removeObserver(std::shared_ptr observer) = 0; + + /** + * Accepts an incoming call. + */ + virtual void acceptCall() = 0; + + /** + * Stops the call. + */ + virtual void stopCall() = 0; +}; + +inline CallManagerInterface::CallManagerInterface( + const std::string& objectName, + const std::string& avsNamespace, + std::shared_ptr exceptionEncounteredSender) : + utils::RequiresShutdown{objectName}, + avsCommon::avs::CapabilityAgent{avsNamespace, exceptionEncounteredSender} { +} + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CALLMANAGERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h new file mode 100644 index 0000000000..fc8e7cd829 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h @@ -0,0 +1,89 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CALLSTATEOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CALLSTATEOBSERVERINTERFACE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * An interface to allow being notified of changes to the state of a call. + */ +class CallStateObserverInterface { +public: + /// An enumeration representing the state of a call. + enum class CallState { + /// The call is connecting. + CONNECTING, + /// An incoming call is causing a ringtone to be played. + INBOUND_RINGING, + /// The call has successfully connected. + CALL_CONNECTED, + /// The call has ended. + CALL_DISCONNECTED, + /// No current call state to be relayed to the user. + NONE + }; + + /** + * Destructor + */ + virtual ~CallStateObserverInterface() = default; + + /** + * Allows the observer to react to a change in call state. + * + * @param state The new CallState. + */ + virtual void onCallStateChange(CallState state) = 0; +}; + +/** + * Write a @c CallState value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param state The @c CallState value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const CallStateObserverInterface::CallState& state) { + switch (state) { + case CallStateObserverInterface::CallState::CONNECTING: + stream << "CONNECTING"; + return stream; + case CallStateObserverInterface::CallState::INBOUND_RINGING: + stream << "INBOUND_RINGING"; + return stream; + case CallStateObserverInterface::CallState::CALL_CONNECTED: + stream << "CALL_CONNECTED"; + return stream; + case CallStateObserverInterface::CallState::CALL_DISCONNECTED: + stream << "CALL_DISCONNECTED"; + return stream; + case CallStateObserverInterface::CallState::NONE: + stream << "NONE"; + return stream; + } + return stream << "UNKNOWN STATE"; +} + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CALLSTATEOBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesDelegateInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesDelegateInterface.h new file mode 100644 index 0000000000..989c43f6b1 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesDelegateInterface.h @@ -0,0 +1,100 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CAPABILITIESDELEGATEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CAPABILITIESDELEGATEINTERFACE_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * CapabilitiesDelegateInterface is an interface with methods that provide clients a way to register capabilities + * implemented by agents and publish them so that Alexa is aware of the device's capabilities. + */ +class CapabilitiesDelegateInterface { +public: + enum class CapabilitiesPublishReturnCode { + /// The Capabilities API message went through without issues + SUCCESS, + /// The message did not go through because of issues that need fixing + FATAL_ERROR, + /// The message did not go through, but you can retry to see if you succeed + RETRIABLE_ERROR + }; + + /** + * Destructor + */ + virtual ~CapabilitiesDelegateInterface() = default; + + /** + * Registers device capabilities that a component is implementing. + * This only updates a local registry and does not actually send out a message to the Capabilities API endpoint. + * + * @param capability The capability configuration which houses the capabilities being implemented. + * @return true if registering was successful, else false. + */ + virtual bool registerCapability( + const std::shared_ptr& capability) = 0; + + /** + * Publishes device capabilities that were registered. + * This function actually sends out a message to the Capabilities API endpoint. + * + * @return A return code (CapabilitiesPublishReturnCode enum value) detailing the outcome of the publish message. + */ + virtual CapabilitiesPublishReturnCode publishCapabilities() = 0; + + /** + * Publishes capabilities asynchronously and will keep on retrying till it succeeds or there is a fatal error. + */ + virtual void publishCapabilitiesAsyncWithRetries() = 0; + + /** + * Specify an object to observe changes to the state of this CapabilitiesDelegate. + * During the call to this setter the observers onCapabilitiesStateChange() method will be called + * back with the current capabilities state. + * + * @param observer The object to observe the state of this CapabilitiesDelegate. + */ + virtual void addCapabilitiesObserver( + std::shared_ptr observer) = 0; + + /** + * Remove an observer + * + * @param observer The observer to remove. + */ + virtual void removeCapabilitiesObserver( + std::shared_ptr observer) = 0; + + /** + * Invalidates the capabilities reported to the AVS last. Capabilities information should be rebuilt and reported + * to the AVS during the next synchronization. + */ + virtual void invalidateCapabilities() = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CAPABILITIESDELEGATEINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesObserverInterface.h new file mode 100644 index 0000000000..59e0ab165a --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesObserverInterface.h @@ -0,0 +1,125 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CAPABILITIESOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CAPABILITIESOBSERVERINTERFACE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * This interface is used to observe changes to the state of the CapabilitiesDelegate. + */ +class CapabilitiesObserverInterface { +public: + /// The enum State describes the state of the CapabilitiesDelegate. + enum class State { + /// CapabilitiesDelegate not yet published. + UNINITIALIZED, + /// The Capabilities API message went through without issues. + SUCCESS, + /// The message did not go through because of issues that need fixing. + FATAL_ERROR, + /// The message did not go through, but you can retry to see if you succeed. + RETRIABLE_ERROR + }; + + /// The enum Error encodes possible errors which may occur when changing state. + enum class Error { + /// The state (and hence the error) has not been initialized. + UNINITIALIZED, + /// Success. + SUCCESS, + /// An unknown error occurred. + UNKNOWN_ERROR, + /// The authorization failed. + FORBIDDEN, + /// The server encountered a runtime error. + SERVER_INTERNAL_ERROR, + /// The request is missing a required parameter, has an invalid value, or is otherwise improperly formed. + BAD_REQUEST + }; + + /** + * Virtual destructor to assure proper cleanup of derived types. + */ + virtual ~CapabilitiesObserverInterface() = default; + + /** + * Notification that an CapabilitiesDelegate state has changed. + * + * @note Implementations of this method must not call CapabilitiesDelegate methods because the CapabilitiesDelegate + * may be in a 'locked' state at the time this call is made. If you do, then you may end up with a deadlock. + * + * @param newState The new state of the CapabilitiesDelegate. + * @param newError The error associated to the state change. + */ + virtual void onCapabilitiesStateChange(State newState, Error newError) = 0; +}; + +/** + * Write a @c State value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param state The state value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const CapabilitiesObserverInterface::State& state) { + switch (state) { + case CapabilitiesObserverInterface::State::UNINITIALIZED: + return stream << "UNINTIALIZED"; + case CapabilitiesObserverInterface::State::SUCCESS: + return stream << "SUCCESS"; + case CapabilitiesObserverInterface::State::FATAL_ERROR: + return stream << "FATAL_ERROR"; + case CapabilitiesObserverInterface::State::RETRIABLE_ERROR: + return stream << "RETRIABLE_ERROR"; + } + return stream << "Unknown CapabilitiesObserverInterface::State!: " << state; +} + +/** + * Write an @c Error value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param error The error value to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const CapabilitiesObserverInterface::Error& error) { + switch (error) { + case CapabilitiesObserverInterface::Error::UNINITIALIZED: + return stream << "UNINTIALIZED"; + case CapabilitiesObserverInterface::Error::SUCCESS: + return stream << "SUCCESS"; + case CapabilitiesObserverInterface::Error::UNKNOWN_ERROR: + return stream << "UNKNOWN_ERROR"; + case CapabilitiesObserverInterface::Error::FORBIDDEN: + return stream << "FORBIDDEN"; + case CapabilitiesObserverInterface::Error::SERVER_INTERNAL_ERROR: + return stream << "SERVER_INTERNAL_ERROR"; + case CapabilitiesObserverInterface::Error::BAD_REQUEST: + return stream << "CLIENT_ERROR_BAD_REQUEST"; + } + return stream << "Unknown CapabilitiesObserverInterface::Error!: " << error; +} + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CAPABILITIESOBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilityConfigurationInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilityConfigurationInterface.h new file mode 100644 index 0000000000..3348e19ef5 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilityConfigurationInterface.h @@ -0,0 +1,52 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CAPABILITYCONFIGURATIONINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CAPABILITYCONFIGURATIONINTERFACE_H_ + +#include +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * This interface provides the CapabilitiesDelegate access to the version and configurations of the capabilities + * being implemented by a capability agent. + */ +class CapabilityConfigurationInterface { +public: + /** + * Destructor. + */ + virtual ~CapabilityConfigurationInterface() = default; + + /** + * Returns the configurations of the capability interfaces being implemented. + * + * @return A set of CapabilityConfigurations + */ + virtual std::unordered_set> + getCapabilityConfigurations() = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_CAPABILITYCONFIGURATIONINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ConnectionStatusObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ConnectionStatusObserverInterface.h index af44ecb87d..4c218e04c8 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ConnectionStatusObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ConnectionStatusObserverInterface.h @@ -45,6 +45,15 @@ class ConnectionStatusObserverInterface { * This enum expresses the reasons a connection status may change. */ enum class ChangedReason { + /// The non-reason, to be used when no reason is specified (i.e. the 'unset' value). + NONE, + + /// The status changed to due to a successful operation. + SUCCESS, + + /// The status changed due to an error from which there is no recovery. + UNRECOVERABLE_ERROR, + /// The connection status changed due to the client interacting with the Connection public api. ACL_CLIENT_REQUEST, @@ -133,6 +142,15 @@ inline std::ostream& operator<<(std::ostream& stream, ConnectionStatusObserverIn */ inline std::ostream& operator<<(std::ostream& stream, ConnectionStatusObserverInterface::ChangedReason reason) { switch (reason) { + case ConnectionStatusObserverInterface::ChangedReason::NONE: + stream << "NONE"; + break; + case ConnectionStatusObserverInterface::ChangedReason::SUCCESS: + stream << "SUCCESS"; + break; + case ConnectionStatusObserverInterface::ChangedReason::UNRECOVERABLE_ERROR: + stream << "UNRECOVERABLE_ERROR"; + break; case ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST: stream << "ACL_CLIENT_REQUEST"; break; diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/DialogUXStateObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/DialogUXStateObserverInterface.h index de2d383b05..443261d3a8 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/DialogUXStateObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/DialogUXStateObserverInterface.h @@ -18,6 +18,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_DIALOGUXSTATEOBSERVERINTERFACE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_DIALOGUXSTATEOBSERVERINTERFACE_H_ +#include #include namespace alexaClientSDK { @@ -35,6 +36,9 @@ class DialogUXStateObserverInterface { /// Alexa is currently listening. LISTENING, + /// Alexa is currently expecting a response from the customer. + EXPECTING, + /** * A customer request has been completed and no more input is accepted. In this state, Alexa is waiting for a * response from AVS. @@ -81,6 +85,8 @@ inline std::string DialogUXStateObserverInterface::stateToString(DialogUXState s return "IDLE"; case DialogUXState::LISTENING: return "LISTENING"; + case DialogUXState::EXPECTING: + return "EXPECTING"; case DialogUXState::THINKING: return "THINKING"; case DialogUXState::SPEAKING: diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/DirectiveSequencerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/DirectiveSequencerInterface.h index f529403df1..5086e5daee 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/DirectiveSequencerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/DirectiveSequencerInterface.h @@ -57,10 +57,15 @@ class DirectiveSequencerInterface : public utils::RequiresShutdown { virtual ~DirectiveSequencerInterface() = default; /** - * Add the specified handler as a handler for its specified namespace, name, and policy. Note that implmentations + * Add the specified handler as a handler for its specified namespace, name, and policy. Note that implementations * of this should call the handler's getConfiguration() method to get the namespace(s), name(s), and policy(ies) of * the handler. If any of the mappings fail, the entire call is refused. * + * @note If the @c name of the configuration is "*", then this is considered a wildcard handler for the given + * @c namespace. For a given @c directive, the @c DirectiveSequencer will look first for a handler whose + * configuration exactly matches the @c directive's { namespace, name } pair. If no exact match is found, then + * the @c directive will be sent to the wildcard handler for the @c namespace, if one has been added. + * * @param handler The handler to add. * @return Whether the handler was added. */ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h index 613d92d4bb..152a62683c 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h @@ -71,6 +71,12 @@ enum class RequestType { /// rewind REWIND, + /// Enable Repeat of a track. + ENABLE_REPEAT_ONE, + + /// Disable Repeat of a track. + DISABLE_REPEAT_ONE, + /// Enable Loop on. ENABLE_REPEAT, @@ -83,12 +89,18 @@ enum class RequestType { /// Disable shuffle. DISABLE_SHUFFLE, - /// Mark a track as favorite.(thumbs up) + /// Mark a track as favorite.(thumbs up true) FAVORITE, - /// Mark a track as not a favorite.(thumbs down) + /// Unmark a track as favorite.(thumbs up false) + DESELECT_FAVORITE, + + /// Mark a track as not a favorite.(thumbs down true) UNFAVORITE, + /// Unmark a track as not a favorite.(thumbs down false) + DESELECT_UNFAVORITE, + /// Seek to a given offset. SEEK, @@ -275,7 +287,7 @@ struct AdapterPlaybackState { /// The playerId of an adapter which is the pre-negotiated business id for a partner music provider. std::string playerId; - /// The state of the default player - IDLE/STOPPED/PLAYING... + /// The players current state std::string state; /// The set of states the default player can move into from its current state. @@ -383,6 +395,11 @@ class ExternalMediaAdapterInterface : public avsCommon::utils::RequiresShutdown */ ExternalMediaAdapterInterface(const std::string& adapaterName); + /** + * Destructor. + */ + virtual ~ExternalMediaAdapterInterface() = default; + /// Method to initialize a third party library. virtual void init() = 0; @@ -438,20 +455,6 @@ class ExternalMediaAdapterInterface : public avsCommon::utils::RequiresShutdown */ virtual void handleAdjustSeek(std::chrono::milliseconds deltaOffset) = 0; - /** - * Method to set volume to a given value. - * - * @param volume The volume level to be set. - */ - virtual void handleSetVolume(int8_t volume) = 0; - - /** - * Method to mute/unmute the speaker. - * - * @param mute @c true mute the speaker @c false unmute a speaker. - */ - virtual void handleSetMute(bool mute) = 0; - /// Method to fetch the state(session state and playback state) of an adapter. virtual AdapterState getState() = 0; }; diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerObserverInterface.h new file mode 100644 index 0000000000..59ee8f00f7 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerObserverInterface.h @@ -0,0 +1,136 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_EXTERNALMEDIAPLAYEROBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_EXTERNALMEDIAPLAYEROBSERVERINTERFACE_H_ + +#include "AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace externalMediaPlayer { + +/** + * struct that includes the Session properties exposed to observers + */ +struct ObservableSessionProperties { + /** + * Default Constructor. + */ + ObservableSessionProperties(); + + /** + * Constructor + * @param pLoggedIn + * @param pUserName + */ + ObservableSessionProperties(bool loggedIn, const std::string& userName); + + /// Flag that identifies if a user is currently logged in or not. + bool loggedIn; + + /// The userName of the user currently logged in via a Login directive from the AVS. + std::string userName; +}; + +inline ObservableSessionProperties::ObservableSessionProperties() : loggedIn{false}, userName{""} { +} + +inline ObservableSessionProperties::ObservableSessionProperties(bool loggedIn, const std::string& userName) : + loggedIn{loggedIn}, + userName{userName} { +} + +inline bool operator==( + const ObservableSessionProperties& observableSessionPropertiesA, + const ObservableSessionProperties& observableSessionPropertiesB) { + return observableSessionPropertiesA.loggedIn == observableSessionPropertiesB.loggedIn && + observableSessionPropertiesA.userName == observableSessionPropertiesB.userName; +} + +/** + * struct that includes the PlaybackState properties exposed to observers + */ +struct ObservablePlaybackStateProperties { + /** + * Default Constructor. + */ + ObservablePlaybackStateProperties(); + + /** + * Constructor + * @param pState + * @param pTrackName + */ + ObservablePlaybackStateProperties(const std::string& state, const std::string& trackName); + + /// The players current state + std::string state; + + /// The display name for the currently playing trackname of the track. + std::string trackName; +}; + +inline ObservablePlaybackStateProperties::ObservablePlaybackStateProperties() : state{""}, trackName{""} {}; + +inline ObservablePlaybackStateProperties::ObservablePlaybackStateProperties( + const std::string& state, + const std::string& trackName) : + state{state}, + trackName{trackName} {}; + +inline bool operator==( + const ObservablePlaybackStateProperties& observableA, + const ObservablePlaybackStateProperties& observableB) { + return observableA.state == observableB.state && observableA.trackName == observableB.trackName; +} + +/** + * This interface allows a derived class to know when a new Login or PlaybackState has been provided + */ +class ExternalMediaPlayerObserverInterface { +public: + /** + * Destructor. + */ + virtual ~ExternalMediaPlayerObserverInterface() = default; + + /** + * This function is called when the login state is provided as a state observer + * @param playerId the ExternalMediaAdapter being reported on + * @param sessionStateProperties the observable session properties being reported + */ + virtual void onLoginStateProvided( + const std::string& playerId, + const avsCommon::sdkInterfaces::externalMediaPlayer::ObservableSessionProperties sessionStateProperties) = 0; + + /** + * This function is called when the playback state is provided as a state observer + * @param playerId the ExternalMediaAdapter being reported on + * @param playbackStateProperties the observable playback state properties being reported + */ + virtual void onPlaybackStateProvided( + const std::string& playerId, + const avsCommon::sdkInterfaces::externalMediaPlayer::ObservablePlaybackStateProperties + playbackStateProperties) = 0; +}; + +} // namespace externalMediaPlayer +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_EXTERNALMEDIAPLAYEROBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/FocusManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/FocusManagerInterface.h index 93abccd617..fc550ebc6f 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/FocusManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/FocusManagerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -40,6 +40,8 @@ namespace sdkInterfaces { * * stop foreground Channel - clients should call the stopForegroundActivity() method. * + * stop all activities - client should call stopAllActivities + * * All of these methods will notify the observer of the Channel of focus changes via an asynchronous callback to the * ChannelObserverInterface##onFocusChanged() method, at which point the client should make a user observable change * based on the focus it receives. @@ -52,11 +54,17 @@ class FocusManagerInterface { /// The default dialog Channel priority. static constexpr unsigned int DIALOG_CHANNEL_PRIORITY = 100; - /// The default Alerts Channel name. - static constexpr const char* ALERTS_CHANNEL_NAME = "Alerts"; + /// The default Communications Channel name. + static constexpr const char* COMMUNICATIONS_CHANNEL_NAME = "Communications"; + + /// The default Communications Channel priority. + static constexpr unsigned int COMMUNICATIONS_CHANNEL_PRIORITY = 150; + + /// The default Alert Channel name. + static constexpr const char* ALERT_CHANNEL_NAME = "Alert"; - /// The default Alerts Channel priority. - static constexpr unsigned int ALERTS_CHANNEL_PRIORITY = 200; + /// The default Alert Channel priority. + static constexpr unsigned int ALERT_CHANNEL_PRIORITY = 200; /// The default Content Channel name. static constexpr const char* CONTENT_CHANNEL_NAME = "Content"; @@ -114,6 +122,12 @@ class FocusManagerInterface { */ virtual void stopForegroundActivity() = 0; + /** + * This method will request to stop all active channels. This will be performed asynchronously, and so, if at the + * time performing the stop, the channel is owned by another interface, this channel won't get stopped. + */ + virtual void stopAllActivities() = 0; + /** * Add an observer to the focus manager. * diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/HTTPContentFetcherInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/HTTPContentFetcherInterface.h index 5d8c381b1f..7725c35d4f 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/HTTPContentFetcherInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/HTTPContentFetcherInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -18,7 +18,9 @@ #include +#include #include +#include namespace alexaClientSDK { namespace avsCommon { @@ -33,28 +35,157 @@ class HTTPContentFetcherInterface { enum class FetchOptions { /// Retrieves the content type part of the HTTP header. CONTENT_TYPE, - /// Retrieves the entire body of the remote location. ENTIRE_BODY }; + /// The state of payload fetching + enum class State { + /// Initialized but nothing was downloaded yet. + INITIALIZED, + /// Currently fetching the header + FETCHING_HEADER, + /// Done fetching the header. Ready to start fetching the body. + HEADER_DONE, + /// Currently fetching the body. + FETCHING_BODY, + /// Done fetching the body. At this point the content fetcher can only be used to read the headers. + BODY_DONE, + /// Some error happened at any stage and the content fetcher cannot be used anymore. + ERROR + }; + + /** + * A struct that represents the header that was retrieved from the HTTP connection. Objects that receive this + * struct are responsible for checking if the successful field is true before reading the other fields. + */ + struct Header { + /// If @c false, there was an error retrieving the header. For instance, the content fetcher may have reached + /// a timeout waiting for the server. If this field's value is @c false, all other field values should be + /// ignored. + bool successful; + /// The HTTP status code received. + avsCommon::utils::http::HTTPResponseCode responseCode; + /// The value of the Content-Type HTTP header. + std::string contentType; + /// The value of the Content-Length HTTP header. + ssize_t contentLength; + + Header() : + successful(false), + responseCode(avsCommon::utils::http::HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED), + contentType(""), + contentLength(0) { + } + }; + /** * Destructor. */ virtual ~HTTPContentFetcherInterface() = default; + /** + * The current content fetching state. In particular, a caller of @c getBody, an asynchronous function, can use this + * method to monitor the download progress. + * + * @return The current content fetching state. + */ + virtual State getState() = 0; + + /** + * Gets the URL associated with this content fetcher + * + * @return The content fetcher URL + */ + virtual std::string getUrl() const = 0; + + /** + * Waits until the header was fetched successfully. If any problem happened during header, returns @c false. After + * the header was already fetched, this method can be called multiple times and will return immediately. + * + * @param shouldShutdown A pointer to allow for the caller to asynchronously cancel the wait, if needed. this + * argument is optional. If it is a null pointer, the function will ignore it. + * @return The header struct. It is the caller's responsibility to check the struct to see if the header was + * retrieved successfully. + */ + virtual Header getHeader(std::atomic* shouldShutdown) = 0; + + /** + * Retrieves the body after the header was received. This method is asynchronous and the caller can monitor the + * download progress using the @c getState method. + * + * @param writer The writer to write the payload. + * @return @c true if the call is successful. + */ + virtual bool getBody(std::shared_ptr writer) = 0; + + /** + * Shuts down the content fetcher. + */ + virtual void shutdown() = 0; + /** * This function retrieves content from a remote location. No thread safety is guaranteed. * * @param option Flag indicating desired content. * @param writer An optional writer parameter to be used when writing to an external stream. - * @return A new @c HTTPContent object or @c nullptr if a failure occured. + * @param customHeaders An optional list of headers to be attached to the request. + * @return A new @c HTTPContent object or @c nullptr if a failure occurred. */ virtual std::unique_ptr getContent( FetchOptions option, - std::shared_ptr writer = nullptr) = 0; + std::unique_ptr writer = nullptr, + const std::vector& customHeaders = std::vector()) = 0; + + /** + * Returns a string that represents the User-Agent to be used in HTTP requests. + * + * @return User-Agent string to be used in HTTP requests. + */ + static std::string getUserAgent(); + + /** + * Produces the string representation of the state enum values. + * @param state The state enum value. + * @return Its string representation. + */ + static std::string stateToString(State state); }; +inline std::string HTTPContentFetcherInterface::getUserAgent() { + return "AvsDeviceSdk/" + utils::sdkVersion::getCurrentVersion(); +} + +inline std::string HTTPContentFetcherInterface::stateToString(HTTPContentFetcherInterface::State state) { + switch (state) { + case State::INITIALIZED: + return "INITIALIZED"; + case State::FETCHING_HEADER: + return "FETCHING_HEADER"; + case State::HEADER_DONE: + return "HEADER_DONE"; + case State::FETCHING_BODY: + return "FETCHING_BODY"; + case State::BODY_DONE: + return "BODY_DONE"; + case State::ERROR: + return "ERROR"; + } + return ""; +} + +/** + * Overwrites the << operator for @c HTTPContentFetcherInterface::State. + * + * @param os The output stream pointer. + * @param code The HTTPContentFetcherInterface::State to write to the output stream. + * @return The output stream pointer. + */ +inline std::ostream& operator<<(std::ostream& os, const HTTPContentFetcherInterface::State& state) { + os << HTTPContentFetcherInterface::stateToString(state); + return os; +} + } // namespace sdkInterfaces } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/InternetConnectionMonitorInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/InternetConnectionMonitorInterface.h new file mode 100644 index 0000000000..4c9da5e6e6 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/InternetConnectionMonitorInterface.h @@ -0,0 +1,52 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_INTERNETCONNECTIONMONITORINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_INTERNETCONNECTIONMONITORINTERFACE_H_ + +#include + +#include "AVSCommon/SDKInterfaces/InternetConnectionObserverInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * An interface for classes responsible for monitoring and reporting internet connection status. + */ +class InternetConnectionMonitorInterface { +public: + /** + * Add an observer to be notified of internet connection changes. + */ + virtual void addInternetConnectionObserver(std::shared_ptr observer) = 0; + + /** + * Remove an observer to be notified of internet connection changes. + */ + virtual void removeInternetConnectionObserver(std::shared_ptr observer) = 0; + + /** + * Destructor. + */ + virtual ~InternetConnectionMonitorInterface() = default; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_INTERNETCONNECTIONMONITORINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/InternetConnectionObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/InternetConnectionObserverInterface.h new file mode 100644 index 0000000000..ed6b43a379 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/InternetConnectionObserverInterface.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_INTERNETCONNECTIONOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_INTERNETCONNECTIONOBSERVERINTERFACE_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * This class allows a client to be notified of changes to an internet connection. + */ +class InternetConnectionObserverInterface { +public: + /** + * Destructor. + */ + virtual ~InternetConnectionObserverInterface() = default; + + /** + * Take necessary actions as a result of an internet connection change. + * + * @param connected Whether or not we are currently connected to the internet. + */ + virtual void onConnectionStatusChanged(bool connected) = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_INTERNETCONNECTIONOBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/KeyWordObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/KeyWordObserverInterface.h index f3e610373b..3a16483081 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/KeyWordObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/KeyWordObserverInterface.h @@ -17,6 +17,8 @@ #define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_KEYWORDOBSERVERINTERFACE_H_ #include +#include +#include #include "AVSCommon/AVS/AudioInputStream.h" @@ -50,12 +52,14 @@ class KeyWordObserverInterface { * If this is set to UNSPECIFIED_INDEX, then it should be ignored. * @param endIndex The optional absolute end index of the last part of the keyword within the stream of the @c * stream. If this is set to UNSPECIFIED_INDEX, then it should be ignored. + * @param KWDMetadata Wake word engine metadata. */ virtual void onKeyWordDetected( std::shared_ptr stream, std::string keyword, avs::AudioInputStream::Index beginIndex = UNSPECIFIED_INDEX, - avs::AudioInputStream::Index endIndex = UNSPECIFIED_INDEX) = 0; + avs::AudioInputStream::Index endIndex = UNSPECIFIED_INDEX, + std::shared_ptr> KWDMetadata = nullptr) = 0; }; } // namespace sdkInterfaces diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MessageObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MessageObserverInterface.h index 1e10d954e0..31d537ecb1 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MessageObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MessageObserverInterface.h @@ -16,12 +16,12 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_MESSAGEOBSERVERINTERFACE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_MESSAGEOBSERVERINTERFACE_H_ +#include + namespace alexaClientSDK { namespace avsCommon { namespace sdkInterfaces { -#include - /** * This class allows a client to receive messages from AVS. */ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MessageRequestObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MessageRequestObserverInterface.h index e1d8fef4fa..73fa1a487d 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MessageRequestObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MessageRequestObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -129,7 +129,7 @@ inline std::ostream& operator<<(std::ostream& stream, MessageRequestObserverInte case MessageRequestObserverInterface::Status::INVALID_AUTH: return stream << "INVALID_AUTH"; case MessageRequestObserverInterface::Status::BAD_REQUEST: - return stream << "BAD_REQUEST"; + return stream << "CLIENT_ERROR_BAD_REQUEST"; case MessageRequestObserverInterface::Status::SERVER_OTHER_ERROR: return stream << "SERVER_OTHER_ERROR"; } diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PlaybackHandlerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PlaybackHandlerInterface.h index e0990bbf6d..5becb19306 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PlaybackHandlerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PlaybackHandlerInterface.h @@ -28,12 +28,25 @@ namespace sdkInterfaces { */ class PlaybackHandlerInterface { public: + /** + * Destructor. + */ + virtual ~PlaybackHandlerInterface() = default; + /** * Used to notify the handler when a playback button is pressed. * * @param button The button that has been pressed. */ - virtual void onButtonPressed(avs::PlaybackButton button) = 0; + virtual void onButtonPressed(alexaClientSDK::avsCommon::avs::PlaybackButton button) = 0; + + /** + * Used to notify the handler when a playback toggle is pressed. + * + * @param toggle The toggle that has been pressed. + * @param action The boolean action for the toggle state + */ + virtual void onTogglePressed(alexaClientSDK::avsCommon::avs::PlaybackToggle toggle, bool action) = 0; }; } // namespace sdkInterfaces diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PlaybackRouterInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PlaybackRouterInterface.h index cb90784697..09a5682180 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PlaybackRouterInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PlaybackRouterInterface.h @@ -37,32 +37,26 @@ class PlaybackRouterInterface { virtual ~PlaybackRouterInterface() = default; /** - * This method can be called by the client when "Play" is pressed on a physical button or on the GUI. - * A PlayCommandIssued event message will be sent to the observer. + * This method can be called by the client when a Button is pressed on a physical button or on the GUI. + * A ButtonCommandIssued event message will be sent to the observer. + * + * @param button The PlaybackButton type being pressed */ - virtual void playButtonPressed() = 0; + virtual void buttonPressed(avsCommon::avs::PlaybackButton button) = 0; /** - * This method can be called by the client when "Pause" is pressed on a physical button or on the GUI. - * A PauseCommandIssued event message will be sent to the observer. + * This method can be called by the client when a Toggle is pressed on a physical button or on the GUI. + * A ToggleCommandIssued event message will be sent to the observer. + * + * @param toggle The PlaybackToggle type being pressed + * @param action The boolean action for the toggle state */ - virtual void pauseButtonPressed() = 0; - - /** - * This method can be called by the client when "Next" is pressed on a physical button or on the GUI. - * A NextCommandIssued event message will be sent to the observer. - */ - virtual void nextButtonPressed() = 0; - - /** - * This method can be called by the client when "Previous" is pressed on a physical button or on the GUI. - * A PreviousCommandIssued event message will be sent to the observer. - */ - virtual void previousButtonPressed() = 0; + virtual void togglePressed(avsCommon::avs::PlaybackToggle toggle, bool action) = 0; /** * This method sets the playback button press handler that any time a button is pressed * this handler will be called. + * * @param handler - The handler to call on future playback button presses. */ virtual void setHandler(std::shared_ptr handler) = 0; diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RevokeAuthorizationObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RevokeAuthorizationObserverInterface.h new file mode 100644 index 0000000000..5e238ad1f1 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RevokeAuthorizationObserverInterface.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_REVOKEAUTHORIZATIONOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_REVOKEAUTHORIZATIONOBSERVERINTERFACE_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +/** + * A RevokeAuthorizationObserverInterface is an interface class that clients can extend to receive revoke authorization + * requests. + */ +class RevokeAuthorizationObserverInterface { +public: + /** + * Destructor. + */ + virtual ~RevokeAuthorizationObserverInterface() = default; + + /** + * Used to notify the observer of revoke authorization requests. The client is expected to clear access tokens and + * any registration information the client may hold. It should put itself back into the registration flow, if + * appropriate for the device (Alexa For Business devices). + */ + virtual void onRevokeAuthorization() = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_REVOKEAUTHORIZATIONOBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerInterface.h index 346aa4bcbd..2ec689ea46 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerInterface.h @@ -34,10 +34,10 @@ class SpeakerInterface { * This enum provides the type of the @c SpeakerInterface. */ enum class Type { - /// Speaker source that should be synced with AVS. - AVS_SYNCED, - /// Speaker source that will not be synced with AVS. - LOCAL + /// Volume type reflecting AVS Speaker API volume. + AVS_SPEAKER_VOLUME, + /// Volume type reflecting AVS Alerts API volume. + AVS_ALERTS_VOLUME }; /** @@ -100,6 +100,11 @@ class SpeakerInterface { * @return The @c Type. */ virtual Type getSpeakerType() = 0; + + /** + * Destructor. + */ + virtual ~SpeakerInterface() = default; }; /** @@ -111,11 +116,11 @@ class SpeakerInterface { */ inline std::ostream& operator<<(std::ostream& stream, SpeakerInterface::Type type) { switch (type) { - case SpeakerInterface::Type::AVS_SYNCED: - stream << "AVS_SYNCED"; + case SpeakerInterface::Type::AVS_SPEAKER_VOLUME: + stream << "AVS_SPEAKER_VOLUME"; return stream; - case SpeakerInterface::Type::LOCAL: - stream << "LOCAL"; + case SpeakerInterface::Type::AVS_ALERTS_VOLUME: + stream << "AVS_ALERTS_VOLUME"; return stream; } stream << "UNKNOWN"; diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h index 18d14274b3..70e58d05ee 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h @@ -119,6 +119,11 @@ class SpeakerManagerInterface { * @param speaker */ virtual void addSpeaker(std::shared_ptr speaker) = 0; + + /** + * Destructor. + */ + virtual ~SpeakerManagerInterface() = default; }; } // namespace sdkInterfaces diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerObserverInterface.h index 2c791fd541..b14f264e7f 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerObserverInterface.h @@ -50,6 +50,11 @@ class SpeakerManagerObserverInterface { const Source& source, const SpeakerInterface::Type& type, const SpeakerInterface::SpeakerSettings& settings) = 0; + + /** + * Destructor. + */ + virtual ~SpeakerManagerObserverInterface() = default; }; /** diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Storage/MiscStorageInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Storage/MiscStorageInterface.h new file mode 100644 index 0000000000..f06f9b4300 --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Storage/MiscStorageInterface.h @@ -0,0 +1,243 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_STORAGE_MISCSTORAGEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_STORAGE_MISCSTORAGEINTERFACE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace storage { + +/** + * This class provides an interface to MiscStorage - a simple key/value database. + * Since this database is supposed to be shared by various components of the SDK, there could be + * conflicts in the table names across different SDK components. Hence, the APIs take the SDK component name + * as well as the table name so that table names can be unique within a specific SDK component. + */ +class MiscStorageInterface { +public: + /// The type of the key column in Misc DB + enum class KeyType { + /// Unknown type + UNKNOWN_KEY, + /// String key + STRING_KEY + }; + + /// The type of the value column in Misc DB + enum class ValueType { + /// Unknown type + UNKNOWN_VALUE, + /// String value + STRING_VALUE + }; + + /** + * Destructor + */ + virtual ~MiscStorageInterface() = default; + + /** + * Creates a new database. + * If a database is already being handled by this object or there is another internal error, then this function + * returns false. + * + * @return @c true If the database is created ok, or @c false if a database is already being handled by this object + * or there is a problem creating the database. + */ + virtual bool createDatabase() = 0; + + /** + * Open an existing database. If this object is already managing an open database, or there is a problem opening + * the database, this function returns false. + * + * @return @c true If the database is opened ok, @c false if this object is already managing an open database, or if + * there is another internal reason the database could not be opened. + */ + virtual bool open() = 0; + + /** + * Returns true if this object is already managing an open database, false otherwise. + * + * @return True if this object is already managing an open database, false otherwise. + */ + virtual bool isOpened() = 0; + + /** + * Close the currently open database, if one is open. + */ + virtual void close() = 0; + + /** + * Create a simple key/value pair table. If there is a problem creating + * the table, this function returns false. + * + * @param componentName The component name. + * @param tableName The table name. + * @param keyType The key type. + * @param valueType The value type. + * @return @c true If the table is created ok, @c false if the table couldn't be created. + */ + virtual bool createTable( + const std::string& componentName, + const std::string& tableName, + KeyType keyType, + ValueType valueType) = 0; + + /** + * Removes all the entries in the table. The table itself will continue to exist. + * + * @param componentName The component name. + * @param tableName The table name. + * @return @c true If the table is cleared ok, @c false if the table couldn't be cleared. + */ + virtual bool clearTable(const std::string& componentName, const std::string& tableName) = 0; + + /** + * Deletes the table. + * The table must be empty before you can delete the table. + * + * @param componentName The component name. + * @param tableName The table name. + * @return @c true If the table is deleted ok, @c false if the table couldn't be deleted. + */ + virtual bool deleteTable(const std::string& componentName, const std::string& tableName) = 0; + + /** + * Gets the value associated with a key in the table. + * + * @param componentName The component name. + * @param tableName The table name. + * @param key The key for the table entry. + * @param [out] value The value associated with the key in the table. + * @return true If the value was found out ok, @c false if not. + */ + virtual bool get( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + std::string* value) = 0; + + /** + * Adds a value in the table. + * + * @param componentName The component name. + * @param tableName The table name. + * @param key The key for the table entry. + * @param value The value of the table entry. + * @note @c value should be a string literal. + * @return @c true If the value was added ok, @c false if not. + */ + virtual bool add( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value) = 0; + + /** + * Updates a value in the table. + * + * @param tableName The table name. + * @param key The key for the table entry. + * @param value The value of the table entry. + * @note @c value should be a literal string. + * @return @c true If the value was updated ok, @c false if not (including if the entry does not exist). + */ + virtual bool update( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value) = 0; + + /** + * Puts a value in the table. + * Basically, this will add the entry for the key if it doesn't already exist, or it will + * update the entry for the key if it already exists. + * + * @param componentName The component name. + * @param tableName The table name. + * @param key The key for the table entry. + * @param value The value of the table entry. + * @note @c value should be a literal string. + * @return @c true If the value was put ok, @c false if not. + */ + virtual bool put( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value) = 0; + + /** + * Removes a value from the table. + * + * @param componentName The component name. + * @param tableName The table name. + * @param key The key for the table entry. + * @return @c true If the value was removed ok, @c false if not. + */ + virtual bool remove(const std::string& componentName, const std::string& tableName, const std::string& key) = 0; + + /** + * Checks if a key exists in the table. + * + * @param componentName The component name. + * @param tableName The table name. + * @param key The key for the table entry. + * @param [out] tableEntryExistsValue True if the key exists, @c false if not. + * @return @c true if the table entry's existence was found out ok, else false.. + */ + virtual bool tableEntryExists( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + bool* tableEntryExistsValue) = 0; + + /** + * Checks if a table exists in the DB. + * + * @param componentName The component name. + * @param tableName The table name. + * @param [out] tableExistsValue True if the table exists, @c false if not. + * @return @c true if the table's existence was found out ok, else false. + */ + virtual bool tableExists( + const std::string& componentName, + const std::string& tableName, + bool* tableExistsValue) = 0; + + /** + * Loads the table entries into a map. + * + * @param componentName The component name. + * @param tableName The table name. + * @param [out] valueContainer The container for the values in the table. + * @return @c true If the values were loaded ok, @c false if not. + */ + virtual bool load( + const std::string& componentName, + const std::string& tableName, + std::unordered_map* valueContainer) = 0; +}; + +} // namespace storage +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_STORAGE_MISCSTORAGEINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorInterface.h new file mode 100644 index 0000000000..e9b05789de --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorInterface.h @@ -0,0 +1,74 @@ +/* + * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_USERINACTIVITYMONITORINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_USERINACTIVITYMONITORINTERFACE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * This interface is used to notify an implementation of the user activity. Any component that interacts with the user + * (e.g. AudioInputProcessor) should register an instance of this interface to signal when user interaction is detected + * (e.g. SpeechStarted). + * + * This interface should also send the System.UserInactivityReport Event as defined here: + * https://developer.amazon.com/docs/alexa-voice-service/system.html#userinactivityreport + * and notify its observers when this occurs. + */ +class UserInactivityMonitorInterface { +public: + /// Destructor. + virtual ~UserInactivityMonitorInterface() = default; + + /// The function to be called when the user has become active. + virtual void onUserActive() = 0; + + /** + * Calculates how many seconds have elapsed since a user last interacted with the device. + * + * @return How many seconds have elapsed since a user last interacted with the device. + */ + virtual std::chrono::seconds timeSinceUserActivity() = 0; + + /** + * Adds an observer to be notified when the System.UserInactivityReport Event has been sent. + * + * @param observer The observer to be notified when the System.UserInactivityReport Event has been sent. + */ + virtual void addObserver( + std::shared_ptr observer) = 0; + + /** + * Removes an observer from the collection of observers which will be notified when the System.UserInactivityReport + * Event has been sent. + * + * @param observer The observer that should no longer be notified when the System.UserInactivityReport Event has + * been sent. + */ + virtual void removeObserver( + std::shared_ptr observer) = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_USERINACTIVITYMONITORINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserActivityNotifierInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorObserverInterface.h similarity index 55% rename from AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserActivityNotifierInterface.h rename to AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorObserverInterface.h index 147df9f0f7..7579956b25 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserActivityNotifierInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/UserInactivityMonitorObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,29 +13,31 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_USERACTIVITYNOTIFIERINTERFACE_H_ -#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_USERACTIVITYNOTIFIERINTERFACE_H_ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_USERINACTIVITYMONITOROBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_USERINACTIVITYMONITOROBSERVERINTERFACE_H_ namespace alexaClientSDK { namespace avsCommon { namespace sdkInterfaces { /** - * This interface is used to notify an implementation of the user activity. Any component that interacts with the user - * (e.g. AudioInputProcessor) should register an instance of this interface to signal when user interaction is detected - * (e.g. SpeechStarted). + * This interface allows a derived class to know when the System.UserInactivityReport Event has been sent. */ -class UserActivityNotifierInterface { +class UserInactivityMonitorObserverInterface { public: - /// Destructor. - virtual ~UserActivityNotifierInterface() = default; + /** + * Destructor. + */ + virtual ~UserInactivityMonitorObserverInterface() = default; - /// The function to be called when the user has become active. - virtual void onUserActive() = 0; + /** + * This function is called when the System.UserInactivityReport Event has been sent. + */ + virtual void onUserInactivityReportSent() = 0; }; } // namespace sdkInterfaces } // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_USERACTIVITYNOTIFIERINTERFACE_H_ +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_USERINACTIVITYMONITOROBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/EqualizerStorageInterfaceTest.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/EqualizerStorageInterfaceTest.h new file mode 100644 index 0000000000..2bdce121e0 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/EqualizerStorageInterfaceTest.h @@ -0,0 +1,70 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERSTORAGEINTERFACETEST_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERSTORAGEINTERFACETEST_H_ + +#include + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { +namespace test { + +/** + * Alias for the factory providing an instance of @c EqualizerStorageInterface. This could be a any code wrappable with + * std::function: static or member function, lambda, etc... + */ +using EqualizerStorageFactory = std::function()>; + +/** + * @c EqualizerStorageInterface tests. + * + * Usage: + * Go to the folder with the tests for your component. Append additional parameter "SDKInterfacesTests" (with quotes) + * to "discover_unit_tests()" CMake directive in CMakeLists.txt file. + * In *Test.cpp file in a global scope add the following line: + * INSTANTIATE_TEST_CASE_P(, EqualizerStorageInterfaceTest, ::testing::Values()); + * Where: + * is a test group's name you want to use. Without quotes. Example: MyEQTests + * is a comma-separated list of @c EqualizerStorageInterfaceFactory instances, one for each + * implementation you want to test. + * + * See example in EqualizerImplementations/test/MiscDBEqualizerStorageTest.cpp + */ +class EqualizerStorageInterfaceTest : public ::testing::TestWithParam { +public: + /// SetUp before each test case. + void SetUp() override; + +protected: + /** + * Instance of the @c EqualizerStorageInterface being tested. + */ + std::shared_ptr m_storage = nullptr; +}; + +} // namespace test +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_EQUALIZERSTORAGEINTERFACETEST_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockAlertsAudioFactory.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockAlertsAudioFactory.h new file mode 100644 index 0000000000..6bbc4f3e45 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockAlertsAudioFactory.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKALERTSAUDIOFACTORY_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKALERTSAUDIOFACTORY_H_ + +#include + +#include "AVSCommon/SDKInterfaces/Audio/AlertsAudioFactoryInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { +namespace test { + +/// Mock class that implements AlertsAudioFactoryInterface +class MockAlertsAudioFactory : public AlertsAudioFactoryInterface { + MOCK_CONST_METHOD0(alarmDefault, std::function()>()); + MOCK_CONST_METHOD0(alarmShort, std::function()>()); + MOCK_CONST_METHOD0(timerDefault, std::function()>()); + MOCK_CONST_METHOD0(timerShort, std::function()>()); + MOCK_CONST_METHOD0(reminderDefault, std::function()>()); + MOCK_CONST_METHOD0(reminderShort, std::function()>()); +}; + +} // namespace test +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKALERTSAUDIOFACTORY_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerConfigurationInterface.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerConfigurationInterface.h new file mode 100644 index 0000000000..7db9330b2e --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerConfigurationInterface.h @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERCONFIGURATIONINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERCONFIGURATIONINTERFACE_H_ + +#include + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { +namespace test { + +/** + * Mock class for @c EqualizerConfigurationInterface + */ +class MockEqualizerConfigurationInterface : public EqualizerConfigurationInterface { +public: + MOCK_CONST_METHOD0(getSupportedBands, std::set()); + MOCK_CONST_METHOD0(getSupportedModes, std::set()); + MOCK_CONST_METHOD0(getMinBandLevel, int()); + MOCK_CONST_METHOD0(getMaxBandLevel, int()); + MOCK_CONST_METHOD0(getDefaultState, EqualizerState()); + MOCK_CONST_METHOD1(isBandSupported, bool(EqualizerBand)); + MOCK_CONST_METHOD1(isModeSupported, bool(EqualizerMode)); +}; + +} // namespace test +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERCONFIGURATIONINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerControllerListenerInterface.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerControllerListenerInterface.h new file mode 100644 index 0000000000..01b18959de --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerControllerListenerInterface.h @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERCONTROLLERLISTENERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERCONTROLLERLISTENERINTERFACE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { +namespace test { + +/** + * Mock class for @c EqualizerControllerListenerInterface + */ +class MockEqualizerControllerListenerInterface : public EqualizerControllerListenerInterface { +public: + MOCK_METHOD1(onEqualizerStateChanged, void(const EqualizerState&)); +}; + +} // namespace test +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERCONTROLLERLISTENERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerInterface.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerInterface.h new file mode 100644 index 0000000000..f4fb547bf5 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerInterface.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERINTERFACE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { +namespace test { + +/** + * Mock class for @c EqualizerInterface + */ +class MockEqualizerInterface : public EqualizerInterface { +public: + MOCK_METHOD1(setEqualizerBandLevels, void(EqualizerBandLevelMap)); + MOCK_METHOD0(getMinimumBandLevel, int()); + MOCK_METHOD0(getMaximumBandLevel, int()); +}; + +} // namespace test +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerModeControllerInterface.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerModeControllerInterface.h new file mode 100644 index 0000000000..c4984fde74 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerModeControllerInterface.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERMODECONTROLLERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERMODECONTROLLERINTERFACE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { +namespace test { + +/** + * Mock class for @c EqualizerInterface + */ + +class MockEqualizerModeControllerInterface : public EqualizerModeControllerInterface { +public: + MOCK_METHOD1(setEqualizerMode, bool(avsCommon::sdkInterfaces::audio::EqualizerMode)); +}; + +} // namespace test +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERMODECONTROLLERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerStorageInterface.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerStorageInterface.h new file mode 100644 index 0000000000..a34b44f982 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Audio/MockEqualizerStorageInterface.h @@ -0,0 +1,45 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERSTORAGEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERSTORAGEINTERFACE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { +namespace test { + +/** + * Mock class for @c EqualizerStorageInterface + */ +class MockEqualizerStorageInterface : public EqualizerStorageInterface { +public: + MOCK_METHOD1(saveState, void(const EqualizerState&)); + MOCK_METHOD0(loadState, avsCommon::utils::error::SuccessResult()); + MOCK_METHOD0(clear, void()); +}; + +} // namespace test +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_AUDIO_MOCKEQUALIZERSTORAGEINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSConnectionManager.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSConnectionManager.h new file mode 100644 index 0000000000..77db66ed31 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAVSConnectionManager.h @@ -0,0 +1,50 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKAVSCONNECTIONMANAGER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKAVSCONNECTIONMANAGER_H_ + +#include + +#include "AVSCommon/SDKInterfaces/AVSConnectionManagerInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace test { + +/// Mock class that implements AVSCOnnectionManagerInterface. +class MockAVSConnectionManager : public AVSConnectionManagerInterface { +public: + MOCK_METHOD0(enable, void()); + MOCK_METHOD0(disable, void()); + MOCK_METHOD0(isEnabled, bool()); + MOCK_METHOD0(reconnect, void()); + MOCK_CONST_METHOD0(isConnected, bool()); + MOCK_METHOD1( + addMessageObserver, + void(std::shared_ptr observer)); + MOCK_METHOD1( + removeMessageObserver, + void(std::shared_ptr observer)); + MOCK_METHOD1(addConnectionStatusObserver, void(std::shared_ptr observer)); + MOCK_METHOD1(removeConnectionStatusObserver, void(std::shared_ptr observer)); +}; +} // namespace test +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKAVSCONNECTIONMANAGER_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockFocusManager.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockFocusManager.h index f3ede1512c..c2404fe9f3 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockFocusManager.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockFocusManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ class MockFocusManager : public FocusManagerInterface { MOCK_METHOD1( removeObserver, void(const std::shared_ptr& observer)); + MOCK_METHOD0(stopAllActivities, void()); }; } // namespace test diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockPlaybackHandler.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockPlaybackHandler.h index 1e60faf04f..946596f204 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockPlaybackHandler.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockPlaybackHandler.h @@ -31,6 +31,7 @@ namespace test { class MockPlaybackHandler : public PlaybackHandlerInterface { public: MOCK_METHOD1(onButtonPressed, void(avs::PlaybackButton button)); + MOCK_METHOD2(onTogglePressed, void(avs::PlaybackToggle toggle, bool action)); }; } // namespace test diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockPlaybackRouter.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockPlaybackRouter.h index d71ff9be6e..8ce88709b6 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockPlaybackRouter.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockPlaybackRouter.h @@ -30,10 +30,8 @@ namespace test { */ class MockPlaybackRouter : public PlaybackRouterInterface { public: - MOCK_METHOD0(playButtonPressed, void()); - MOCK_METHOD0(pauseButtonPressed, void()); - MOCK_METHOD0(nextButtonPressed, void()); - MOCK_METHOD0(previousButtonPressed, void()); + MOCK_METHOD1(buttonPressed, void(avsCommon::avs::PlaybackButton button)); + MOCK_METHOD2(togglePressed, void(avsCommon::avs::PlaybackToggle toggle, bool action)); MOCK_METHOD1(setHandler, void(std::shared_ptr handler)); MOCK_METHOD0(switchToDefaultHandler, void()); }; diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockUserActivityNotifier.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockRevokeAuthorizationObserver.h similarity index 64% rename from AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockUserActivityNotifier.h rename to AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockRevokeAuthorizationObserver.h index 245860372e..d19feba4cb 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockUserActivityNotifier.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockRevokeAuthorizationObserver.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,21 +13,24 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKUSERACTIVITYNOTIFIER_H_ -#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKUSERACTIVITYNOTIFIER_H_ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKREVOKEAUTHORIZATIONOBSERVER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKREVOKEAUTHORIZATIONOBSERVER_H_ -#include "AVSCommon/SDKInterfaces/UserActivityNotifierInterface.h" #include +#include "AVSCommon/SDKInterfaces/RevokeAuthorizationObserverInterface.h" + namespace alexaClientSDK { namespace avsCommon { namespace sdkInterfaces { namespace test { -/// Mock class that implements @c UserActivityNotifierInterface. -class MockUserActivityNotifier : public UserActivityNotifierInterface { +/* + * Mock class that implements @c RevokeAuthorizationObserverInterface. + */ +class MockRevokeAuthorizationObserver : public RevokeAuthorizationObserverInterface { public: - MOCK_METHOD0(onUserActive, void()); + MOCK_METHOD0(onRevokeAuthorization, void()); }; } // namespace test @@ -35,4 +38,4 @@ class MockUserActivityNotifier : public UserActivityNotifierInterface { } // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKUSERACTIVITYNOTIFIER_H_ +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKREVOKEAUTHORIZATIONOBSERVER_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerInterface.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerInterface.h new file mode 100644 index 0000000000..3e279ca01c --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerInterface.h @@ -0,0 +1,150 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKSPEAKERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKSPEAKERINTERFACE_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace test { + +using namespace avsCommon::avs::speakerConstants; +using namespace ::testing; + +/// Value for mute. +static const bool MUTE(true); + +/// String value for mute. +static const std::string MUTE_STRING("true"); + +/// Value for unmute. +static const bool UNMUTE(false); + +/// String value for unmute. +static const std::string UNMUTE_STRING("false"); + +/// Value for default volume settings. +static const SpeakerInterface::SpeakerSettings DEFAULT_SETTINGS{AVS_SET_VOLUME_MIN, UNMUTE}; + +/* + * Mock class that implements the SpeakerInterface. This is the delegate speaker class which + * implements the SpeakerInterface methods. + */ +class MockSpeaker : public SpeakerInterface { +public: + bool setVolume(int8_t volume) override; + bool adjustVolume(int8_t delta) override; + bool setMute(bool mute) override; + bool getSpeakerSettings(SpeakerInterface::SpeakerSettings* settings) override; + SpeakerInterface::Type getSpeakerType() override; + + /// Constructor. + MockSpeaker(SpeakerInterface::Type type); + +private: + /// The type of the Speaker interface. + SpeakerInterface::Type m_type; + + /// The current speaker settings of the interface. + SpeakerInterface::SpeakerSettings m_settings; +}; + +inline bool MockSpeaker::setVolume(int8_t volume) { + m_settings.volume = volume; + return true; +} + +inline bool MockSpeaker::adjustVolume(int8_t delta) { + int8_t curVolume = m_settings.volume + delta; + curVolume = std::min(curVolume, AVS_SET_VOLUME_MAX); + curVolume = std::max(curVolume, AVS_SET_VOLUME_MIN); + m_settings.volume = curVolume; + return true; +} + +inline bool MockSpeaker::setMute(bool mute) { + m_settings.mute = mute; + return true; +} + +inline bool MockSpeaker::getSpeakerSettings(SpeakerInterface::SpeakerSettings* settings) { + if (!settings) { + return false; + } + + settings->volume = m_settings.volume; + settings->mute = m_settings.mute; + + return true; +} + +inline SpeakerInterface::Type MockSpeaker::getSpeakerType() { + return m_type; +} + +inline MockSpeaker::MockSpeaker(SpeakerInterface::Type type) : m_type{type} { + m_settings = DEFAULT_SETTINGS; +} + +/* + * Mock class that implements the SpeakerInterface. This class delegates the Speaker + * methods to the MockSpeaker object. + */ +class MockSpeakerInterface : public SpeakerInterface { +public: + MOCK_METHOD1(setVolume, bool(int8_t)); + + MOCK_METHOD1(adjustVolume, bool(int8_t)); + + MOCK_METHOD1(setMute, bool(bool)); + + MOCK_METHOD1(getSpeakerSettings, bool(SpeakerInterface::SpeakerSettings*)); + + MOCK_METHOD0(getSpeakerType, SpeakerInterface::Type()); + + /// Delegate call from Mock Speaker object to an object with actual implementation. + void DelegateToReal(); + + /// Constructor. + MockSpeakerInterface(SpeakerInterface::Type type); + +private: + /// Implementation of Speaker object to handle calls. + MockSpeaker m_speaker; +}; + +inline void MockSpeakerInterface::DelegateToReal() { + ON_CALL(*this, setVolume(_)).WillByDefault(Invoke(&m_speaker, &SpeakerInterface::setVolume)); + ON_CALL(*this, adjustVolume(_)).WillByDefault(Invoke(&m_speaker, &SpeakerInterface::adjustVolume)); + ON_CALL(*this, setMute(_)).WillByDefault(Invoke(&m_speaker, &SpeakerInterface::setMute)); + ON_CALL(*this, getSpeakerSettings(_)).WillByDefault(Invoke(&m_speaker, &SpeakerInterface::getSpeakerSettings)); + ON_CALL(*this, getSpeakerType()).WillByDefault(Invoke(&m_speaker, &SpeakerInterface::getSpeakerType)); +} + +inline MockSpeakerInterface::MockSpeakerInterface(SpeakerInterface::Type type) : m_speaker{type} { +} + +} // namespace test +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKSPEAKERINTERFACE_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h new file mode 100644 index 0000000000..f84b325e29 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h @@ -0,0 +1,68 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKSPEAKERMANAGER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKSPEAKERMANAGER_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace test { + +class MockSpeakerManager : public SpeakerManagerInterface { +public: + MOCK_METHOD3( + setVolume, + std::future( + avsCommon::sdkInterfaces::SpeakerInterface::Type type, + int8_t volume, + bool forceNoNotifications)); + + MOCK_METHOD3( + adjustVolume, + std::future( + avsCommon::sdkInterfaces::SpeakerInterface::Type type, + int8_t delta, + bool forceNoNotifications)); + + MOCK_METHOD3( + setMute, + std::future(avsCommon::sdkInterfaces::SpeakerInterface::Type type, bool mute, bool forceNoNotifications)); + + MOCK_METHOD2( + getSpeakerSettings, + std::future( + avsCommon::sdkInterfaces::SpeakerInterface::Type type, + avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings* settings)); + + MOCK_METHOD1( + addSpeakerManagerObserver, + void(std::shared_ptr observer)); + MOCK_METHOD1( + removeSpeakerManagerObserver, + void(std::shared_ptr observer)); + MOCK_METHOD1(addSpeaker, void(std::shared_ptr speaker)); +}; + +} // namespace test +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKSPEAKERMANAGER_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockUserInactivityMonitor.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockUserInactivityMonitor.h new file mode 100644 index 0000000000..5186df40a5 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockUserInactivityMonitor.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKUSERINACTIVITYMONITOR_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKUSERINACTIVITYMONITOR_H_ + +#include "AVSCommon/SDKInterfaces/UserInactivityMonitorInterface.h" +#include "AVSCommon/SDKInterfaces/UserInactivityMonitorObserverInterface.h" +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace test { + +/// Mock class that implements @c UserInactivityMonitorInterface. +class MockUserInactivityMonitor : public UserInactivityMonitorInterface { +public: + MOCK_METHOD0(onUserActive, void()); + MOCK_METHOD0(timeSinceUserActivity, std::chrono::seconds()); + MOCK_METHOD1(addObserver, void(std::shared_ptr)); + MOCK_METHOD1( + removeObserver, + void(std::shared_ptr)); +}; + +} // namespace test +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKUSERINACTIVITYMONITOR_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockUserInactivityMonitorObserver.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockUserInactivityMonitorObserver.h new file mode 100644 index 0000000000..f8e562dfee --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockUserInactivityMonitorObserver.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKUSERINACTIVITYMONITOROBSERVER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKUSERINACTIVITYMONITOROBSERVER_H_ + +#include "AVSCommon/SDKInterfaces/UserInactivityMonitorObserverInterface.h" +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace test { + +/// Mock class that implements @c UserInactivityMonitorObserverInterface. +class MockUserInactivityMonitorObserver : public UserInactivityMonitorObserverInterface { +public: + MOCK_METHOD0(onUserInactivityReportSent, void()); +}; + +} // namespace test +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKUSERINACTIVITYMONITOROBSERVER_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/StubMiscStorage.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/StubMiscStorage.h new file mode 100644 index 0000000000..70ac6a61cb --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/StubMiscStorage.h @@ -0,0 +1,115 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_STORAGE_STUBMISCSTORAGE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_STORAGE_STUBMISCSTORAGE_H_ + +#include + +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace storage { +namespace test { + +/** + * In memory implementation for @c MiscStorageInterface. This class is not thread safe. + */ +class StubMiscStorage : public avsCommon::sdkInterfaces::storage::MiscStorageInterface { +public: + static std::shared_ptr create(); + + /// @name MiscStorageInterface functions + /// @{ + + bool createDatabase() override; + + bool open() override; + + bool isOpened() override; + + void close() override; + + bool createTable( + const std::string& componentName, + const std::string& tableName, + KeyType keyType, + ValueType valueType) override; + + bool clearTable(const std::string& componentName, const std::string& tableName) override; + + bool deleteTable(const std::string& componentName, const std::string& tableName) override; + + bool get(const std::string& componentName, const std::string& tableName, const std::string& key, std::string* value) + override; + + bool add( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value) override; + + bool update( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value) override; + + bool put( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value) override; + + bool remove(const std::string& componentName, const std::string& tableName, const std::string& key) override; + + bool tableEntryExists( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + bool* tableEntryExistsValue) override; + + bool tableExists(const std::string& componentName, const std::string& tableName, bool* tableExistsValue) override; + + bool load( + const std::string& componentName, + const std::string& tableName, + std::unordered_map* valueContainer) override; + ///@} + +private: + StubMiscStorage(); + + /// Container to keep stored values. The format of the key is "componentName:tableName:key". + std::unordered_map m_storage; + /// A collection of table prefixes to track if table exists. + std::unordered_set m_tables; + /// Flag indicating if database is opened. + bool m_isOpened; +}; + +} // namespace test +} // namespace storage +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_STORAGE_STUBMISCSTORAGE_H_ diff --git a/AVSCommon/SDKInterfaces/test/CMakeLists.txt b/AVSCommon/SDKInterfaces/test/CMakeLists.txt new file mode 100644 index 0000000000..81c9be2060 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(SDKInterfacesTests + src/EqualizerStorageInterfaceTest.cpp + src/StubMiscStorage.cpp + ) + +target_include_directories(SDKInterfacesTests PUBLIC + "${AVSCommon_INCLUDE_DIRS}" + ) + +target_link_libraries(SDKInterfacesTests AVSCommon gmock_main) + diff --git a/AVSCommon/SDKInterfaces/test/src/EqualizerStorageInterfaceTest.cpp b/AVSCommon/SDKInterfaces/test/src/EqualizerStorageInterfaceTest.cpp new file mode 100644 index 0000000000..c3d2edf6e0 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/src/EqualizerStorageInterfaceTest.cpp @@ -0,0 +1,78 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace audio { +namespace test { + +using namespace ::testing; +using namespace sdkInterfaces::audio::test; +using namespace sdkInterfaces::audio; + +void EqualizerStorageInterfaceTest::SetUp() { + auto factory = GetParam(); + m_storage = factory(); +} + +/** + * Save state to the storage and see if loadState() returns the saved state. + */ +TEST_P(EqualizerStorageInterfaceTest, test_saveState_ExpectLoadReturnSame) { + EqualizerState defaultState = {EqualizerMode::MOVIE, + EqualizerBandLevelMap({{EqualizerBand::TREBLE, 0}, {EqualizerBand::MIDRANGE, 1}})}; + m_storage->saveState(defaultState); + + auto loadedStateResult = m_storage->loadState(); + + EXPECT_EQ(loadedStateResult.value(), defaultState); + + EqualizerState state = {EqualizerMode::TV, + EqualizerBandLevelMap({{EqualizerBand::TREBLE, 10}, {EqualizerBand::MIDRANGE, 11}})}; + m_storage->saveState(state); + + loadedStateResult = m_storage->loadState(); + + EXPECT_EQ(loadedStateResult.value(), state); +} + +/** + * Perform cleaning of the data in the storage and see if next loadState() returns the default state. + */ +TEST_P(EqualizerStorageInterfaceTest, test_clearSavedData_ExpectAllDefaultsOnLoad) { + EqualizerState defaultState = {EqualizerMode::MOVIE, + EqualizerBandLevelMap({{EqualizerBand::TREBLE, 0}, {EqualizerBand::MIDRANGE, 1}})}; + + EqualizerState state = {EqualizerMode::MOVIE, + EqualizerBandLevelMap({{EqualizerBand::TREBLE, 10}, {EqualizerBand::BASS, 11}})}; + m_storage->saveState(state); + m_storage->clear(); + + auto loadedStateResult = m_storage->loadState(); + EXPECT_FALSE(loadedStateResult.isSucceeded()); +} + +} // namespace test +} // namespace audio +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/SDKInterfaces/test/src/StubMiscStorage.cpp b/AVSCommon/SDKInterfaces/test/src/StubMiscStorage.cpp new file mode 100644 index 0000000000..452a9e1179 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/src/StubMiscStorage.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace storage { +namespace test { + +bool StubMiscStorage::createDatabase() { + return true; +} + +bool StubMiscStorage::open() { + m_isOpened = true; + return true; +} + +void StubMiscStorage::close() { + m_isOpened = false; +} + +bool StubMiscStorage::createTable( + const std::string& componentName, + const std::string& tableName, + MiscStorageInterface::KeyType keyType, + MiscStorageInterface::ValueType valueType) { + std::string key = componentName + ":" + tableName; + m_tables.insert(key); + return true; +} + +bool StubMiscStorage::clearTable(const std::string& componentName, const std::string& tableName) { + std::string keyPrefix = componentName + ":" + tableName + ":"; + auto it = m_storage.begin(); + while (it != m_storage.end()) { + const std::string& key = it->first; + size_t pos = key.find(keyPrefix); + if (std::string::npos != pos) { + it = m_storage.erase(it); + } else { + ++it; + } + } + return true; +} + +bool StubMiscStorage::deleteTable(const std::string& componentName, const std::string& tableName) { + std::string key = componentName + ":" + tableName; + m_tables.erase(key); + return clearTable(componentName, tableName); +} + +bool StubMiscStorage::get( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + std::string* value) { + std::string keyStr = componentName + ":" + tableName + ":" + key; + auto it = m_storage.find(keyStr); + if (m_storage.end() == it) { + return false; + } + *value = it->second; + return true; +} + +bool StubMiscStorage::add( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value) { + return put(componentName, tableName, key, value); +} + +bool StubMiscStorage::update( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value) { + return put(componentName, tableName, key, value); +} + +bool StubMiscStorage::put( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value) { + std::string keyStr = componentName + ":" + tableName + ":" + key; + m_storage[keyStr] = value; + return true; +} + +bool StubMiscStorage::remove(const std::string& componentName, const std::string& tableName, const std::string& key) { + std::string keyStr = componentName + ":" + tableName + ":" + key; + m_storage.erase(keyStr); + return true; +} + +bool StubMiscStorage::tableEntryExists( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + bool* tableEntryExistsValue) { + std::string keyStr = componentName + ":" + tableName + ":" + key; + auto it = m_storage.find(keyStr); + *tableEntryExistsValue = m_storage.end() != it; + return true; +} + +bool StubMiscStorage::tableExists( + const std::string& componentName, + const std::string& tableName, + bool* tableExistsValue) { + std::string key = componentName + ":" + tableName; + bool exists = m_tables.end() != m_tables.find(key); + *tableExistsValue = exists; + return true; +} + +bool StubMiscStorage::load( + const std::string& componentName, + const std::string& tableName, + std::unordered_map* valueContainer) { + std::string keyStr = componentName + ":" + tableName + ":"; + size_t keyLen = keyStr.length(); + for (const auto& it : m_storage) { + const std::string& key = it.first; + if (key.substr(0, keyLen) == keyStr) { + std::string targetKey = key.substr(keyLen); + valueContainer->insert(std::pair(targetKey, it.second)); + } + } + *valueContainer = m_storage; + return true; +} + +std::shared_ptr StubMiscStorage::create() { + return std::shared_ptr(new StubMiscStorage()); +} + +StubMiscStorage::StubMiscStorage() : m_isOpened{false} { +} + +bool StubMiscStorage::isOpened() { + return m_isOpened; +} + +} // namespace test +} // namespace storage +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/CMakeLists.txt b/AVSCommon/Utils/CMakeLists.txt index ec82c64620..0130ac18bf 100644 --- a/AVSCommon/Utils/CMakeLists.txt +++ b/AVSCommon/Utils/CMakeLists.txt @@ -1 +1 @@ -acsdk_add_test_subdirectory_if_allowed() +add_subdirectory("test") diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/A2DPRole.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/A2DPRole.h new file mode 100644 index 0000000000..2833b4c491 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/A2DPRole.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_A2DPROLE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_A2DPROLE_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/// An Enum representing the current A2DP role. +enum class A2DPRole { + /// AVS device acting as an A2DPSink. + SINK, + /// AVS device acting as an A2DPSource. + SOURCE +}; + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_A2DPROLE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEventBus.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEventBus.h new file mode 100644 index 0000000000..5408840676 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEventBus.h @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_BLUETOOTHEVENTBUS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_BLUETOOTHEVENTBUS_H_ + +#include +#include +#include +#include +#include +#include + +#include "AVSCommon/Utils/Bluetooth/BluetoothEvents.h" +#include "AVSCommon/Utils/Bluetooth/BluetoothEventListenerInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/** + * Event bus class for Bluetooth CA. Publishes Bluetooth events to all listeners. + */ +class BluetoothEventBus { +public: + /** + * Constructor + */ + BluetoothEventBus(); + + /** + * A type representing a collection of @c EventListener objects. + */ + using ListenerList = std::list>; + + /** + * Send the event to @c EventBus. Method blocks until all the listeners process the event. The method is thread + * safe. + * + * @param event Event to be sent to @c EventBus + */ + void sendEvent(const BluetoothEvent& event); + + /** + * Adds a listener to the bus. + * + * @param eventTypes A list of event types to subscribe listener to. + * @param listener An @c EventListener object to add as a listener. Listener cannot be registered multiple + * times for the same BluetoothEventType. + */ + void addListener( + const std::vector& eventTypes, + std::shared_ptr listener); + + /** + * Removes a listener from the @c EventBus. + * + * @param eventTypes A list of event types to unsubscribe listener from. + * @param listener Listener object to unsubscribe. + */ + void removeListener( + const std::vector& eventTypes, + std::shared_ptr listener); + +private: + /// Mutex used to synchronize access to subscribed listener list + std::mutex m_mutex; + + /** + * A collection of @c EventListener objects grouped by event type id. Each listener may be subscribed + * to any number of event types. + */ + std::unordered_map m_listenerMap; +}; + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_BLUETOOTHEVENTBUS_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEventListenerInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEventListenerInterface.h new file mode 100644 index 0000000000..eaadd49451 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEventListenerInterface.h @@ -0,0 +1,49 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_BLUETOOTHEVENTLISTENERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_BLUETOOTHEVENTLISTENERINTERFACE_H_ + +#include "AVSCommon/Utils/Bluetooth/BluetoothEvents.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/** + * Base interface for the objects listening to events. + */ +class BluetoothEventListenerInterface { +public: + /** + * Destructor + */ + virtual ~BluetoothEventListenerInterface() = default; + + /** + * Method called to process an event of the specific type. + * + * @param event Event to be processed + */ + virtual void onEventFired(const BluetoothEvent& event) = 0; +}; + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_BLUETOOTHEVENTLISTENERINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEvents.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEvents.h new file mode 100644 index 0000000000..7fe4642185 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/BluetoothEvents.h @@ -0,0 +1,327 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_BLUETOOTHEVENTS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_BLUETOOTHEVENTS_H_ + +#include "AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h" +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSourceInterface.h" +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h" +#include "AVSCommon/Utils/Bluetooth/A2DPRole.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/// The different Bluetooth event types. +enum class BluetoothEventType { + /// Represents when a device is discovered. + DEVICE_DISCOVERED, + + /// Represents when a device is removed. + DEVICE_REMOVED, + + /// Represents when the state of a device changes. + DEVICE_STATE_CHANGED, + + /// Represents when the A2DP streaming state changes. + STREAMING_STATE_CHANGED, + + /// Represents when an AVRCP command has been received. + AVRCP_COMMAND_RECEIVED, + + /// When the BluetoothDeviceManager has initialized. + BLUETOOTH_DEVICE_MANAGER_INITIALIZED +}; + +/// Helper struct to allow enum class to be a key in collections +struct BluetoothEventTypeHash { + template + std::size_t operator()(T t) const { + return static_cast(t); + } +}; + +/// An Enum representing the current state of the stream. +enum class MediaStreamingState { + /// Currently not streaming. + IDLE, + /// Currently acquiring the stream. + PENDING, + /// Currently streaming. + ACTIVE +}; + +/// Base class for a @c BluetoothEvent. +class BluetoothEvent { +public: + /** + * Destructor. + */ + virtual ~BluetoothEvent() = default; + + /** + * Get event type + * @return Event type. + */ + BluetoothEventType getType() const; + + /** + * Get @c BluetoothDeviceInterface associated with the event. The value depends on the event type + * @return @c BluetoothDeviceInterface associated with the event or @c nullptr + */ + std::shared_ptr getDevice() const; + + /** + * Get @c DeviceState associated with the event + * @return @c DeviceState associated with the event + */ + avsCommon::sdkInterfaces::bluetooth::DeviceState getDeviceState() const; + + /** + * Get @c MediaStreamingState associated with the event + * @return @c MediaStreamingState associated with the event + */ + MediaStreamingState getMediaStreamingState() const; + + /** + * Get @c A2DPRole associated with the event + * @return @c A2DPRole associated with the event or null if not applicable. + */ + std::shared_ptr getA2DPRole() const; + + /** + * Get @c AVRCPCommand associated with the event + * @return @c AVRCPCommand associated with the event or null if not applicable + */ + std::shared_ptr getAVRCPCommand() const; + +protected: + /** + * Constructor + * @param type Event type + * @param device @c BluetoothDeviceInterface associated with the event + * @param deviceState @c DeviceState associated with the event + * @param mediaStreamingState @c MediaStreamingState associated with the event + * @param role The A2DP role the AVS device is acting as + * @param avrcpCommand The received AVRCPCommand if applicable + */ + BluetoothEvent( + BluetoothEventType type, + std::shared_ptr device = nullptr, + avsCommon::sdkInterfaces::bluetooth::DeviceState deviceState = + avsCommon::sdkInterfaces::bluetooth::DeviceState::IDLE, + MediaStreamingState mediaStreamingState = MediaStreamingState::IDLE, + std::shared_ptr role = nullptr, + std::shared_ptr avrcpCommand = nullptr); + +private: + /// Event type + BluetoothEventType m_type; + + /// @c BluetoothDeviceInterface associated with the event + std::shared_ptr m_device; + + /// @c DeviceState associated with the event + avsCommon::sdkInterfaces::bluetooth::DeviceState m_deviceState; + + /// @c MediaStreamingState associated with the event + MediaStreamingState m_mediaStreamingState; + + /// @c A2DPRole associated with the event + std::shared_ptr m_a2dpRole; + + /// @C AVRCPCommand that is received + std::shared_ptr m_avrcpCommand; +}; + +inline BluetoothEvent::BluetoothEvent( + BluetoothEventType type, + std::shared_ptr device, + avsCommon::sdkInterfaces::bluetooth::DeviceState deviceState, + MediaStreamingState mediaStreamingState, + std::shared_ptr a2dpRole, + std::shared_ptr avrcpCommand) : + m_type{type}, + m_device{device}, + m_deviceState{deviceState}, + m_mediaStreamingState{mediaStreamingState}, + m_a2dpRole{a2dpRole}, + m_avrcpCommand{avrcpCommand} { +} + +inline BluetoothEventType BluetoothEvent::getType() const { + return m_type; +} + +inline std::shared_ptr BluetoothEvent::getDevice() + const { + return m_device; +} + +inline avsCommon::sdkInterfaces::bluetooth::DeviceState BluetoothEvent::getDeviceState() const { + return m_deviceState; +} + +inline MediaStreamingState BluetoothEvent::getMediaStreamingState() const { + return m_mediaStreamingState; +} + +inline std::shared_ptr BluetoothEvent::getA2DPRole() const { + return m_a2dpRole; +} + +inline std::shared_ptr BluetoothEvent::getAVRCPCommand() + const { + return m_avrcpCommand; +} + +/** + * Event indicating that a new device was discovered. This must be sent when + * a new device is discovered with a reference to the @c BluetoothDeviceInterface. + */ +class DeviceDiscoveredEvent : public BluetoothEvent { +public: + /** + * Constructor + * @param device Device associated with the event + */ + explicit DeviceDiscoveredEvent( + const std::shared_ptr& device); +}; + +inline DeviceDiscoveredEvent::DeviceDiscoveredEvent( + const std::shared_ptr& device) : + BluetoothEvent( + BluetoothEventType::DEVICE_DISCOVERED, + device, + avsCommon::sdkInterfaces::bluetooth::DeviceState::IDLE, + MediaStreamingState::IDLE) { +} + +/** + * Event indicating that a device is removed from the underlying stack, if applicable. + * This signifies that the stack is no longer aware of the device. For example, + * if the stack forgets an unpaired device. This must be sent with a reference + * to the @c BluetoothDeviceInterface. + */ +class DeviceRemovedEvent : public BluetoothEvent { +public: + /** + * Constructor + * @param device Device associated with the event + */ + explicit DeviceRemovedEvent( + const std::shared_ptr& device); +}; + +inline DeviceRemovedEvent::DeviceRemovedEvent( + const std::shared_ptr& device) : + BluetoothEvent( + BluetoothEventType::DEVICE_REMOVED, + device, + avsCommon::sdkInterfaces::bluetooth::DeviceState::IDLE, + MediaStreamingState::IDLE) { +} + +/** + * Event indicating that a device undergoes a state transition. The available states are + * dictated by @c DeviceState. This must be sent with a reference to the @c BluetoothDeviceInterface. + */ +class DeviceStateChangedEvent : public BluetoothEvent { +public: + /** + * Constructor + * @param device Device associated with the event + * @param newState New state of the devices + */ + DeviceStateChangedEvent( + std::shared_ptr device, + avsCommon::sdkInterfaces::bluetooth::DeviceState newState); +}; + +inline DeviceStateChangedEvent::DeviceStateChangedEvent( + std::shared_ptr device, + avsCommon::sdkInterfaces::bluetooth::DeviceState newState) : + BluetoothEvent(BluetoothEventType::DEVICE_STATE_CHANGED, device, newState, MediaStreamingState::IDLE) { +} + +/** + * Event indicating that a device's streaming state has changed. This refers to the + * A2DP profile. This must be sent on transitions between @c MediaStreamingState. + */ +class MediaStreamingStateChangedEvent : public BluetoothEvent { +public: + /** + * Constructor + * @param newState New media streaming state + * @param role The A2DPRole associated with the device running the SDK + * @param device The peer device that is connected and whose streaming status changed + */ + explicit MediaStreamingStateChangedEvent( + MediaStreamingState newState, + A2DPRole role, + std::shared_ptr device); +}; + +inline MediaStreamingStateChangedEvent::MediaStreamingStateChangedEvent( + MediaStreamingState newState, + A2DPRole role, + std::shared_ptr device) : + BluetoothEvent( + BluetoothEventType::STREAMING_STATE_CHANGED, + device, + avsCommon::sdkInterfaces::bluetooth::DeviceState::IDLE, + newState, + std::make_shared(role)){}; + +/** + * Event indicating that an AVRCP command was received. + */ +class AVRCPCommandReceivedEvent : public BluetoothEvent { +public: + explicit AVRCPCommandReceivedEvent(avsCommon::sdkInterfaces::bluetooth::services::AVRCPCommand command); +}; + +inline AVRCPCommandReceivedEvent::AVRCPCommandReceivedEvent( + avsCommon::sdkInterfaces::bluetooth::services::AVRCPCommand command) : + BluetoothEvent( + BluetoothEventType::AVRCP_COMMAND_RECEIVED, + nullptr, + avsCommon::sdkInterfaces::bluetooth::DeviceState::IDLE, + MediaStreamingState::IDLE, + nullptr, + std::make_shared(command)) { +} + +/** + * Event indicating that the BluetoothDeviceManager has finished initialization. This should only be sent once. + */ +class BluetoothDeviceManagerInitializedEvent : public BluetoothEvent { +public: + explicit BluetoothDeviceManagerInitializedEvent(); +}; + +inline BluetoothDeviceManagerInitializedEvent::BluetoothDeviceManagerInitializedEvent() : + BluetoothEvent(BluetoothEventType::BLUETOOTH_DEVICE_MANAGER_INITIALIZED) { +} + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_BLUETOOTHEVENTS_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/FormattedAudioStreamAdapter.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/FormattedAudioStreamAdapter.h new file mode 100644 index 0000000000..a716653383 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/FormattedAudioStreamAdapter.h @@ -0,0 +1,85 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_FORMATTEDAUDIOSTREAMADAPTER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_FORMATTEDAUDIOSTREAMADAPTER_H_ + +#include +#include + +#include "AVSCommon/Utils/AudioFormat.h" +#include "AVSCommon/Utils/Bluetooth/FormattedAudioStreamAdapterListener.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/** + * A class providing the way to receive a sequence of audio data blocks and a format associated with it. + * + * This class should be used when you want to publish a real time audio stream of the specified format to zero or one + * recipient without buffering the data. The receiving party may start listening at any moment. With no listener set + * all the published data is lost. + */ +class FormattedAudioStreamAdapter { +public: + /** + * Constructor initializing the class with an @c AudioFormat. + * + * @param audioFormat @c AudioFormat describing the data being sent. + */ + explicit FormattedAudioStreamAdapter(const AudioFormat& audioFormat); + + /** + * Get @c AudioFormat associated with the class. + * + * @return @c AudioFormat associated with the class. + */ + AudioFormat getAudioFormat() const; + + /** + * Set the listener to receive data. + * + * @param listener the listener to receive data. + */ + void setListener(std::shared_ptr listener); + + /** + * Publish data to the listener. + * + * @param buffer Buffer containing the data + * @param size Size of the data block in bytes. The value must be greater than zero. + * @return number of bytes processed. + */ + size_t send(const unsigned char* buffer, size_t size); + +private: + /// The @c AudioFormat associated with the class. + AudioFormat m_audioFormat; + + /// the listener to receive data. + std::weak_ptr m_listener; + + /// Mutex to guard listener changes. + std::mutex m_readerFunctionMutex; +}; + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_FORMATTEDAUDIOSTREAMADAPTER_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/FormattedAudioStreamAdapterListener.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/FormattedAudioStreamAdapterListener.h new file mode 100644 index 0000000000..7bd3c6dce5 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/FormattedAudioStreamAdapterListener.h @@ -0,0 +1,53 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_FORMATTEDAUDIOSTREAMADAPTERLISTENER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_FORMATTEDAUDIOSTREAMADAPTERLISTENER_H_ + +#include "AVSCommon/Utils/AudioFormat.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/** + * Interface to be implemented by class listening for data from @c FormattedAudioStreamAdapter + */ +class FormattedAudioStreamAdapterListener { +public: + /** + * Method to receive data sent by @c FormattedAudioStreamAdapter + * @param audioFormat Audio format of the data sent + * @param buffer Pointer to the buffer containing the data + * @param size Length of the data in the buffer in bytes. + */ + virtual void onFormattedAudioStreamAdapterData( + avsCommon::utils::AudioFormat audioFormat, + const unsigned char* buffer, + size_t size) = 0; + + /** + * Destructor. + */ + virtual ~FormattedAudioStreamAdapterListener() = default; +}; + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_FORMATTEDAUDIOSTREAMADAPTERLISTENER_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/SDPRecords.h b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/SDPRecords.h new file mode 100644 index 0000000000..215fbd24bd --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Bluetooth/SDPRecords.h @@ -0,0 +1,117 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_SDPRECORDS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_SDPRECORDS_H_ + +#include + +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/SDPRecordInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/// Base class for an SDPRecord object used. +class SDPRecord : public sdkInterfaces::bluetooth::services::SDPRecordInterface { +public: + /** + * Constructor. + * + * @param name The Bluetooth Service name. + * @param uuid The service UUID. + * @param version The service version. + */ + SDPRecord(const std::string& name, const std::string& uuid, const std::string& version); + + /** + * Gets the service name. + * + * @return The service name. + */ + std::string getName() const override; + + /** + * Gets the full 128-bit service UUID. + */ + std::string getUuid() const override; + + /** + * Gets the version of the service. + */ + std::string getVersion() const override; + +protected: + /// The service name. + const std::string m_name; + + /// The 128-bit UUID. + const std::string m_uuid; + + /// The version. + const std::string m_version; +}; + +/// A SDP record representing A2DPSource. +class A2DPSourceRecord : public SDPRecord { +public: + /** + * Constructor + * + * @param version The version of the service. + */ + A2DPSourceRecord(const std::string& version); +}; + +/// A SDP record representing A2DPSink. +class A2DPSinkRecord : public SDPRecord { +public: + /** + * Constructor + * + * @param version The version of the service. + */ + A2DPSinkRecord(const std::string& version); +}; + +/// A SDP record representing AVRCPTarget. +class AVRCPTargetRecord : public SDPRecord { +public: + /** + * Constructor + * + * @param version The version of the service. + */ + AVRCPTargetRecord(const std::string& version); +}; + +/// A SDP record representing AVRCPController. +class AVRCPControllerRecord : public SDPRecord { +public: + /** + * Constructor + * + * @param version The version of the service. + */ + AVRCPControllerRecord(const std::string& version); +}; + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_BLUETOOTH_SDPRECORDS_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Configuration/ConfigurationNode.h b/AVSCommon/Utils/include/AVSCommon/Utils/Configuration/ConfigurationNode.h index 4b8ab9dc67..d0bcda78a2 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Configuration/ConfigurationNode.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Configuration/ConfigurationNode.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_CONFIGURATION_CONFIGURATIONNODE_H_ #include +#include #include #include #include @@ -74,7 +75,7 @@ class ConfigurationNode { * @note If @c initialize() has already been called since startup or the latest call to uninitialize(), this * function will reject the request and return @c false. * - * @param jsonConfigurationStreams Vector of @c istreams containing JSON documents from which to parse + * @param jsonStreams Vector of @c istreams containing JSON documents from which to parse * configuration parameters. Streams are processed in the order they appear in the vector. When a * value appears in more than one JSON stream the last processed stream's value overwrites the previous value * (and a debug log entry will be created). This allows for specifying default settings (by providing them @@ -83,7 +84,7 @@ class ConfigurationNode { * * @return Whether the initialization was successful. */ - static bool initialize(const std::vector& jsonStreams); + static bool initialize(const std::vector>& jsonStreams); /** * Uninitialize the global configuration. @@ -191,6 +192,38 @@ class ConfigurationNode { bool (rapidjson::Value::*isType)() const, Type (rapidjson::Value::*getType)() const) const; + /** + * Serialize the object into a string + * + * @return The serialized object. + */ + std::string serialize() const; + + /** + * Get the @c ConfigurationNode value that contains an array with @c key from this @c ConfigurationNode. + * + * @param key The key of the @c ConfigurationNode value to get. + * @return The @c ConfigurationNode value, or an empty node if this @c ConfigurationNode does not have + * a @c ConfigurationNode value for @c key that contains an array. + */ + ConfigurationNode getArray(const std::string& key) const; + + /** + * Get the size of the array from this @c ConfigurationNode. + * + * @return 0 if this @c ConfigurationNode is not an array. Else return the size of the array. + */ + std::size_t getArraySize() const; + + /** + * operator[] to get @c ConfigurationNode value from an array from @c index of this @c ConfigurationNode. + * + * @param index The index of the array of the @c ConfigurationNode to get. + * @return The @c ConfigurationNode value, or an empty node if this @c ConfigurationNode is not an array or the + * the index is out of range. + */ + ConfigurationNode operator[](const std::size_t index) const; + private: /** * Constructor. diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/DeviceInfo.h b/AVSCommon/Utils/include/AVSCommon/Utils/DeviceInfo.h new file mode 100644 index 0000000000..41a3d9d5b0 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/DeviceInfo.h @@ -0,0 +1,115 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_DEVICEINFO_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_DEVICEINFO_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { + +/** + * Class that defines a device's identification information. + */ +class DeviceInfo { +public: + /** + * Create a DeviceInfo. + * + * @param configurationRoot The global config object. + * @return If successful, returns a new DeviceInfo, otherwise @c nullptr. + */ + static std::unique_ptr create( + const avsCommon::utils::configuration::ConfigurationNode& configurationRoot); + + /** + * Create a DeviceInfo. + * + * @param clientId Client Id. + * @param productId Product Id. + * @param deviceSerialNumber DSN. + * @return If successful, returns a new DeviceInfo, otherwise @c nullptr. + */ + static std::unique_ptr create( + const std::string& clientId, + const std::string& productId, + const std::string& deviceSerialNumber); + + /** + * Gets the client Id. + * + * @return Client Id. + */ + std::string getClientId() const; + + /** + * Gets the product Id. + * + * @return Product Id. + */ + std::string getProductId() const; + + /** + * Gets the device serial number. + * + * @return Device serial number. + */ + std::string getDeviceSerialNumber() const; + + /** + * Operator == for @c DeviceInfo + * + * @param rhs The right hand side of the == operation. + * @return Whether or not this instance and @c rhs are equivalent. + */ + bool operator==(const DeviceInfo& rhs); + + /** + * Operator != for @c DeviceInfo + * + * @param rhs The right hand side of the != operation. + * @return Whether or not this instance and @c rhs are not equivalent. + */ + bool operator!=(const DeviceInfo& rhs); + +private: + /** + * DeviceInfo constructor. + * + * @param clientId Client Id. + * @param productId Product Id. + * @param deviceSerialNumber DSN. + */ + DeviceInfo(const std::string& clientId, const std::string& productId, const std::string& deviceSerialNumber); + + /// Client ID + std::string m_clientId; + + /// Product ID + std::string m_productId; + + /// DSN + std::string m_deviceSerialNumber; +}; + +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_DEVICEINFO_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Endian.h b/AVSCommon/Utils/include/AVSCommon/Utils/Endian.h new file mode 100644 index 0000000000..55efae8d13 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Endian.h @@ -0,0 +1,36 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ENDIAN_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ENDIAN_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { + +/// Function used to check the machine endianness. +inline bool littleEndianMachine() { + uint16_t original = 1; + uint8_t* words = reinterpret_cast(&original); + return words[0] == 1; +} + +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ENDIAN_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Error/FinallyGuard.h b/AVSCommon/Utils/include/AVSCommon/Utils/Error/FinallyGuard.h new file mode 100644 index 0000000000..229127a542 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Error/FinallyGuard.h @@ -0,0 +1,77 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ERROR_FINALLYGUARD_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ERROR_FINALLYGUARD_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace error { + +/** + * Define a class that can be used to run a function when the object goes out of scope. + * + * This simulates try-finally statements. The following structure: + * + * @code + * try { + * + * } finally { + * + * } + * @endcode + * + * can be replaced by: + * + * @code + * FinallyGuard guard { []{ }}; + * + * @endcode + */ +class FinallyGuard { +public: + /** + * Constructor. + * + * @param finallyFunction The function to be executed when the object goes out of scope. + */ + FinallyGuard(const std::function& finallyFunction); + + /** + * Destructor. Runs @c m_function during destruction. + */ + ~FinallyGuard(); + +private: + /// The function to be run when this object goes out of scope. + std::function m_function; +}; + +FinallyGuard::FinallyGuard(const std::function& finallyFunction) : m_function{finallyFunction} { +} + +FinallyGuard::~FinallyGuard() { + if (m_function) { + m_function(); + } +} + +} // namespace error +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ERROR_FINALLYGUARD_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Error/Result.h b/AVSCommon/Utils/include/AVSCommon/Utils/Error/Result.h new file mode 100644 index 0000000000..7954bc3257 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Error/Result.h @@ -0,0 +1,93 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ERROR_RESULT_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ERROR_RESULT_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace error { + +/** + * Class representing result of an operation. The result has a status and a value. + * + * @tparam TStatus Type of the status + * @tparam TValue Type of the value + */ +template +class Result { +public: + /** + * Constructor with both status and return value provided. + * + * @param status Status of the operation. + * @param value Value associated with the result. + */ + inline Result(TStatus status, TValue value); + + /** + * Constructor with only status provided + * + * @param status Result status. + */ + inline Result(TStatus status); + + /** + * Returns result status. + * + * @return Result status. + */ + inline TStatus status(); + + /** + * Returns a value associated with the result. + * + * @return Value associated with the result. + */ + inline TValue& value(); + +private: + /// Result status. + TStatus m_status; + + /// Value associated with the result. + TValue m_value; +}; + +template +Result::Result(TStatus status, TValue value) : m_status{status}, m_value(value) { +} + +template +Result::Result(TStatus status) : m_status{status} { +} + +template +TValue& Result::value() { + return m_value; +} + +template +TStatus Result::status() { + return m_status; +} + +} // namespace error +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ERROR_RESULT_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Error/SuccessResult.h b/AVSCommon/Utils/include/AVSCommon/Utils/Error/SuccessResult.h new file mode 100644 index 0000000000..f6e7e9281a --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Error/SuccessResult.h @@ -0,0 +1,101 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ERROR_SUCCESSRESULT_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ERROR_SUCCESSRESULT_H_ + +#include "Result.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace error { + +/** + * Version of @c Result class assuming status to be either success or failure. + * + * @tparam TValue Type of the value associated with the result. + */ +template +class SuccessResult : public utils::error::Result { +public: + /** + * Creates a succeeded result with a value. + * + * @param value Value to be associated with the result. + * @return Succeeded result with a value. + */ + inline static SuccessResult success(TValue value); + + /** + * Creates a failed result. + * + * @return Failed result. + */ + inline static SuccessResult failure(); + + /** + * Constructor with both success status and value provided. + * + * @param succeeded Success status. True for succes, false for failure. + * @param value Value to be associated with the result. + */ + inline SuccessResult(bool succeeded, TValue value); + + /** + * Returns true if result status is succeeded, false otherwise. + * + * @return True if result status is succeeded, false otherwise. + */ + inline bool isSucceeded(); + +protected: + /** + * Constructor with only success status provided. + * + * @param succeeded Success status. True for success, false for failure. + */ + explicit inline SuccessResult(bool succeeded); +}; + +template +SuccessResult::SuccessResult(bool succeeded) : Result(succeeded) { +} + +template +SuccessResult::SuccessResult(bool succeeded, TValue value) : Result(succeeded, value) { +} + +template +bool SuccessResult::isSucceeded() { + return Result::status(); +} + +template +SuccessResult SuccessResult::failure() { + return SuccessResult(false); +} + +template +SuccessResult SuccessResult::success(TValue value) { + return SuccessResult(true, value); +} + +} // namespace error +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_ERROR_SUCCESSRESULT_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/GuardedValue.h b/AVSCommon/Utils/include/AVSCommon/Utils/GuardedValue.h new file mode 100644 index 0000000000..f8b9466474 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/GuardedValue.h @@ -0,0 +1,78 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_GUARDEDVALUE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_GUARDEDVALUE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { + +/** + * Auxiliary class that uses mutex to synchronize non-trivially copyable object manipulation. + */ +template +class GuardedValue { +public: + /** + * Covert this object to value T. + */ + operator ValueT() const; + + /** + * Assign the underlying @c m_value to the new value. + * + * @param value The new value to be used. + */ + ValueT operator=(const ValueT& value); + + /** + * Builds the object with the given value. + * + * @param value The value to be used to initialize @c m_value. + */ + GuardedValue(const ValueT& value); + +private: + /// The mutex used to guard access to @c m_value + mutable std::mutex m_mutex; + + /// The actual value. + ValueT m_value; +}; + +template +ValueT GuardedValue::operator=(const ValueT& value) { + std::lock_guard lock{m_mutex}; + m_value = value; + return m_value; +} + +template +GuardedValue::operator ValueT() const { + std::lock_guard lock{m_mutex}; + return m_value; +} + +template +GuardedValue::GuardedValue(const ValueT& value) : m_value{value} { +} + +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_GUARDEDVALUE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP/HttpResponseCode.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP/HttpResponseCode.h new file mode 100644 index 0000000000..be899b25ec --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP/HttpResponseCode.h @@ -0,0 +1,201 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP_HTTPRESPONSECODE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP_HTTPRESPONSECODE_H_ + +#include +#include + +#include "AVSCommon/Utils/Logger/LoggerUtils.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http { + +enum HTTPResponseCode { + /// No HTTP response received. + HTTP_RESPONSE_CODE_UNDEFINED = 0, + + /// HTTP Success with reponse payload. + SUCCESS_OK = 200, + /// HTTP Succcess with no response payload. + SUCCESS_NO_CONTENT = 204, + + /// Multiple redirection choices + REDIRECTION_MULTIPLE_CHOICES = 300, + /// Content moved permanently + REDIRECTION_MOVED_PERMANENTLY = 301, + /// Content found on another URI + REDIRECTION_FOUND = 302, + /// See another - Should issue a GET to the other URI and assume that data was received on this request + REDIRECTION_SEE_ANOTHER = 303, + /// Content can be found on another URI, but the redirect should not be cached - cannot change HTTP method + REDIRECTION_TEMPORARY_REDIRECT = 307, + /// Content can be found on another URI, and the redirect should be cached - cannot change HTTP method + REDIRECTION_PERMANENT_REDIRECT = 308, + + /// HTTP code for invalid request by user. + CLIENT_ERROR_BAD_REQUEST = 400, + /// HTTP code for forbidden request by user. + CLIENT_ERROR_FORBIDDEN = 403, + + /// HTTP code for internal error by server which didn't fulfill the request. + SERVER_ERROR_INTERNAL = 500, + + /// First success code + SUCCESS_START_CODE = SUCCESS_OK, + /// Last success code + SUCCESS_END_CODE = 299, + + /// First code in redirection range. + REDIRECTION_START_CODE = REDIRECTION_MULTIPLE_CHOICES, + /// Last code in redirection range. + REDIRECTION_END_CODE = REDIRECTION_PERMANENT_REDIRECT +}; + +/** + * Checks if the status code is on the HTTP success range. + * + * @param code The status code to check + * @return @c true if it is on the success range + */ +inline bool isStatusCodeSuccess(HTTPResponseCode code) { + return (HTTPResponseCode::SUCCESS_START_CODE <= code) && (code <= HTTPResponseCode::SUCCESS_END_CODE); +} + +/** + * Checks if the status code is on the HTTP redirect range. + * + * @param code The status code to check + * @return @c true if it is on the redirect range + */ +inline bool isRedirect(HTTPResponseCode code) { + // Not all 3xx codes are redirects and we should avoid proxy codes for security reasons. Therefore the safest way + // to check redirection is verifying code by code. + return HTTPResponseCode::REDIRECTION_MULTIPLE_CHOICES == code || + HTTPResponseCode::REDIRECTION_MOVED_PERMANENTLY == code || HTTPResponseCode::REDIRECTION_FOUND == code || + HTTPResponseCode::REDIRECTION_SEE_ANOTHER == code || + HTTPResponseCode::REDIRECTION_TEMPORARY_REDIRECT == code || + HTTPResponseCode::REDIRECTION_PERMANENT_REDIRECT == code; +} + +/** + * Converts a response code in integer format to an @ HTTPResponseCode enumeration value. Gives an @c + * HTTP_RESPONSE_CODE_UNDEFINED if the response code is not mapped by any enumeration value. + * + * @param code The response code. + * @return The enumeration value. + */ +inline HTTPResponseCode intToHTTPResponseCode(int code) { + switch (code) { + case 200: + return HTTPResponseCode::SUCCESS_OK; + case 204: + return HTTPResponseCode::SUCCESS_NO_CONTENT; + case 300: + return HTTPResponseCode::REDIRECTION_MULTIPLE_CHOICES; + case 301: + return HTTPResponseCode::REDIRECTION_MOVED_PERMANENTLY; + case 302: + return HTTPResponseCode::REDIRECTION_FOUND; + case 303: + return HTTPResponseCode::REDIRECTION_SEE_ANOTHER; + case 307: + return HTTPResponseCode::REDIRECTION_TEMPORARY_REDIRECT; + case 308: + return HTTPResponseCode::REDIRECTION_PERMANENT_REDIRECT; + case 400: + return HTTPResponseCode::CLIENT_ERROR_BAD_REQUEST; + case 403: + return HTTPResponseCode::CLIENT_ERROR_FORBIDDEN; + case 500: + return HTTPResponseCode::SERVER_ERROR_INTERNAL; + } + logger::acsdkError( + logger::LogEntry("HttpResponseCodes", __func__).d("code", code).m("Unknown HTTP response code.")); + return HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED; +} + +/** + * Converts a response code enum value to the code in int. + * + * @param responseCode The enum value. + * @return The equivalent int value. + */ +inline int responseCodeToInt(HTTPResponseCode responseCode) { + return static_cast::type>(responseCode); +} + +/** + * Produces the string representation of a response code value. + * + * @param responseCode The enumeration value. + * @return The string representation. + */ +inline std::string responseCodeToString(HTTPResponseCode responseCode) { + switch (responseCode) { + case HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED: + return "HTTP_RESPONSE_CODE_UNDEFINED"; + case HTTPResponseCode::SUCCESS_OK: + return "SUCCESS_OK"; + case HTTPResponseCode::SUCCESS_NO_CONTENT: + return "SUCCESS_NO_CONTENT"; + case HTTPResponseCode::SUCCESS_END_CODE: + return "SUCCESS_END_CODE"; + case HTTPResponseCode::REDIRECTION_MULTIPLE_CHOICES: + return "REDIRECTION_MULTIPLE_CHOICES"; + case HTTPResponseCode::REDIRECTION_MOVED_PERMANENTLY: + return "REDIRECTION_MOVED_PERMANENTLY"; + case HTTPResponseCode::REDIRECTION_FOUND: + return "REDIRECTION_FOUND"; + case HTTPResponseCode::REDIRECTION_SEE_ANOTHER: + return "REDIRECTION_SEE_ANOTHER"; + case HTTPResponseCode::REDIRECTION_TEMPORARY_REDIRECT: + return "REDIRECTION_TEMPORARY_REDIRECT"; + case HTTPResponseCode::REDIRECTION_PERMANENT_REDIRECT: + return "REDIRECTION_PERMANENT_REDIRECT"; + case HTTPResponseCode::CLIENT_ERROR_BAD_REQUEST: + return "CLIENT_ERROR_BAD_REQUEST"; + case HTTPResponseCode::CLIENT_ERROR_FORBIDDEN: + return "CLIENT_ERROR_FORBIDDEN"; + case HTTPResponseCode::SERVER_ERROR_INTERNAL: + return "SERVER_ERROR_INTERNAL"; + } + logger::acsdkError(logger::LogEntry("HttpResponseCodes", __func__) + .d("longValue", static_cast(responseCode)) + .m("Unknown HTTP response code.")); + return ""; +} + +/** + * Overwrites the << operator for @c HTTPResponseCode. + * + * @param os The output stream pointer. + * @param code The HTTPResponseCode to write to the output stream. + * @return The output stream pointer. + */ +inline std::ostream& operator<<(std::ostream& os, const HTTPResponseCode& code) { + os << responseCodeToString(code); + return os; +} + +} // namespace http +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP_HTTPRESPONSECODE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionFactoryInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionFactoryInterface.h new file mode 100644 index 0000000000..32a4f05666 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionFactoryInterface.h @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONFACTORYINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONFACTORYINTERFACE_H_ + +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2ConnectionInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Interface for creating instances of @c HTTP2ConnectionInterface. + */ +class HTTP2ConnectionFactoryInterface { +public: + /** + * Destructor. + */ + virtual ~HTTP2ConnectionFactoryInterface() = default; + + /** + * Create an instance of HTTP2ConnectionInterface. + * + * @return An instance of HTTP2ConnectionInterface or nullptr if the operation failed. + */ + virtual std::shared_ptr createHTTP2Connection() = 0; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONFACTORYINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionInterface.h new file mode 100644 index 0000000000..a5bd265ab6 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionInterface.h @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONINTERFACE_H_ + +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2RequestConfig.h" +#include "AVSCommon/Utils/HTTP2/HTTP2RequestInterface.h" +#include "AVSCommon/Utils/HTTP2/HTTP2RequestType.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Interface for managing an HTTP2 connection. + */ +class HTTP2ConnectionInterface { +public: + /** + * Destructor. + */ + virtual ~HTTP2ConnectionInterface() = default; + + /** + * Create an HTTP2 request. Send it immediately. + * + * @param config The configuration object which defines the request. + * @return A new HTTP2GetRequest instance. + */ + virtual std::shared_ptr createAndSendRequest(const HTTP2RequestConfig& config) = 0; + + /** + * Terminate the HTTP2 connection, forcing immediate termination of any active requests. + */ + virtual void disconnect() = 0; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionStatus.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionStatus.h new file mode 100644 index 0000000000..64c27222fa --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ConnectionStatus.h @@ -0,0 +1,41 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONSTATUS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONSTATUS_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * The status of an HTTP2Connection. + */ +enum class HTTP2ConnectionStatus { + /// Establishing a connection to an endpoint. + CONNECTING, + /// Connected to an endpoint. + CONNECTED, + /// Not CONNECTING or CONNECTED to an endpoint. + DISCONNECTED +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2CONNECTIONSTATUS_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2GetMimeHeadersResult.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2GetMimeHeadersResult.h new file mode 100644 index 0000000000..33d0348b75 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2GetMimeHeadersResult.h @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2GETMIMEHEADERSRESULT_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2GETMIMEHEADERSRESULT_H_ + +#include +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2SendStatus.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Value returned from @c HTTP2MimeRequestSourceInterface::getMimeHeaderLines(), combining a status and a + * vector of header lines. + */ +struct HTTP2GetMimeHeadersResult { +public: + /// The status of the @c getMimeHeadersLines() operation. @see HTTP2SendStatus. + HTTP2SendStatus status; + + /// The headers returned from @c getMimeHeaderLines. Only non-empty if @c status == @c CONTINUE. + std::vector headers; + + /** + * Construct a HTTP2GetMimeHeadersResult with a status of CONTINUE and the header values to continue with. + * @param headers The headers to send. + * @return A HTTP2GetMimeHeadersResult with status CONTINUE and the specified header lines. + */ + HTTP2GetMimeHeadersResult(const std::vector& headers); + + /// Const PAUSE result. + static const HTTP2GetMimeHeadersResult PAUSE; + + /// Const COMPLETE result. + static const HTTP2GetMimeHeadersResult COMPLETE; + + /// Const ABORT result. + static const HTTP2GetMimeHeadersResult ABORT; + +private: + /** + * Constructor. + * + * @param status The status of the @c getMimeHeaders() operation. + * @param headers The headers to send. + */ + HTTP2GetMimeHeadersResult(HTTP2SendStatus status, const std::vector& headers); +}; + +inline HTTP2GetMimeHeadersResult::HTTP2GetMimeHeadersResult(const std::vector& headers) : + status{HTTP2SendStatus::CONTINUE}, + headers{headers} { +} + +inline HTTP2GetMimeHeadersResult::HTTP2GetMimeHeadersResult( + HTTP2SendStatus statusIn, + const std::vector& headersIn) : + status{statusIn}, + headers{headersIn} { +} + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2GETMIMEHEADERSRESULT_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeRequestEncoder.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeRequestEncoder.h new file mode 100644 index 0000000000..48f9c9008c --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeRequestEncoder.h @@ -0,0 +1,158 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMEREQUESTENCODER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMEREQUESTENCODER_H_ + +#include +#include +#include +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2MimeRequestSourceInterface.h" +#include "AVSCommon/Utils/HTTP2/HTTP2RequestSourceInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Class that adapts between HTTP2MimeRequestSourceInterface and a + * HTTP2RequestSourceInterface providing the encoding of discreet mime parts in + * to a single request stream. + */ +class HTTP2MimeRequestEncoder : public HTTP2RequestSourceInterface { +public: + /** + * Create an HTTP2MimeRequestEncoder. + * + * @param boundary The mime boundary to include between mime parts. + * @param source Pointer to an object providing the mime parts in sequence. + */ + HTTP2MimeRequestEncoder(const std::string& boundary, std::shared_ptr source); + + /** + * Destructor. + */ + ~HTTP2MimeRequestEncoder() = default; + + /// @name HTTP2RequestSourceInterface methods. + /// @{ + HTTP2SendDataResult onSendData(char* bytes, size_t size) override; + std::vector getRequestHeaderLines() override; + /// @} + +private: + /// The states that the encoder transitions through. + enum class State { + /// Just created + NEW, + /// Requesting the source for the headers for the first mime part. + GETTING_1ST_PART_HEADERS, + /// Sending the boundary before the first part. + SENDING_1ST_BOUNDARY, + /// Sending the headers for the current part. + SENDING_PART_HEADERS, + /// Sending data for the current part. + SENDING_PART_DATA, + /// Sending the boundary terminating the current part. + SENDING_END_BOUNDARY, + /// Requesting the source for the headers for the next part. + GETTING_NTH_PART_HEADERS, + /// Sending the CRLF between the latest boundary and the next part. + SENDING_CRLF_AFTER_BOUNDARY, + /// Sending the two dashes after the final boundary. + SENDING_TERMINATING_DASHES, + /// Done sending. + DONE, + /// Bad state. + ABORT + }; + + // Friend operator<<() to enable debug output. + friend std::ostream& operator<<(std::ostream& stream, State state); + + /** + * Set the state of the encoder. + * + * @param newState The new state to transition to. + */ + void setState(State newState); + + /** + * Copy a string in to the provided buffer, starting at the offset @m_stringIndex into the buffer, + * truncating the copy if necessary to not exceed the size of the buffer. @c m_stringIndex will + * be updated to the next byte after the copied string, and @c m_bytesCopied will have the count + * of copied bytes added to it. + * + * @param bytes The buffer to copy the string to. + * @param size The size of the buffer to copy the string to. + * @param text The string to send. + * @return Whether the end of the string was sent. + */ + bool sendString(char* bytes, size_t size, const std::string& text); + + /** + * Copy a string and a CRLF in to the provided buffer, starting at the offset @m_stringIndex into the buffer, + * truncating the copy if necessary to not exceed the size of the buffer. @c m_stringIndex will + * be updated to the next byte after the copied string and CRLF, and @c m_bytesCopied will have the count + * of copied bytes added to it. + * + * @param bytes The buffer to copy the string to. + * @param size The size of the buffer to copy the string to. + * @param text The string to send. + * @return Whether the end of the string was sent. + */ + bool sendStringAndCRLF(char* bytes, size_t size, const std::string& text); + + /** + * Create a HTTP2SendDataResult with HTTP2SendStatus::CONTINUE and a size of @c m_bytesCopied. + * + * @return An HTTP2SendDataResult with HTTP2SendStatus::CONTINUE and a size of @c m_bytesCopied. + */ + HTTP2SendDataResult continueResult(); + + /// Current state. + State m_state; + + /// The boundry string without a CRLF or two-dash prefix. + std::string m_rawBoundary; + + /// The boundary string with CRLF nd two-dash prefix to simplify emitting it in the encoded stream. + std::string m_prefixedBoundary; + + /// Shared pointer to the MimeRequestSource implementation. + std::shared_ptr m_source; + + /// Number of bytes accumulated in @c bytes during call to @c onSendData(). + size_t m_bytesCopied; + + /// Last result from calling getMimeHeaderLines. + HTTP2GetMimeHeadersResult m_getMimeHeaderLinesResult; + + /// Iterator to header line currently being sent. + std::vector::const_iterator m_headerLine; + + /// Current index in to @c m_boundary or the current header line. + size_t m_stringIndex; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMEREQUESTENCODER_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeRequestSourceInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeRequestSourceInterface.h new file mode 100644 index 0000000000..a8a5f303d4 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeRequestSourceInterface.h @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMEREQUESTSOURCEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMEREQUESTSOURCEINTERFACE_H_ + +#include +#include +#include +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2GetMimeHeadersResult.h" +#include "AVSCommon/Utils/HTTP2/HTTP2SendDataResult.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Interface for providing data to be sent as part of a Mime encoded HTTP2 request. + */ +class HTTP2MimeRequestSourceInterface { +public: + /** + * Default destructor. + */ + virtual ~HTTP2MimeRequestSourceInterface() = default; + + /** + * Get the header lines that should be output with this HTTP2 request. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @return The header lines that should be output with this request. + */ + virtual std::vector getRequestHeaderLines() = 0; + + /** + * Get the header lines that should be output with the next mime part. This will be called once before + * @c onSendMimePartData() is called for the first mime part and after each call to @c onSendMimePartData() + * that returns @c HTTP2SendDataResult.status == COMPLETE. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @return An @c HTTP2GetMimeHeadersResult specifying the status of the operation and a vector of header lines + * if the status was CONTINUE. + */ + virtual HTTP2GetMimeHeadersResult getMimePartHeaderLines() = 0; + + /** + * Notification to copy data to be mime encoded in to an HTTP2 request. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param bytes The buffer to receive the bytes to send. + * @param size The max number of bytes to copy. + * @return Result indicating the disposition of the operation and number of bytes copied. + * @see HTTPSendMimePartDataResult. + */ + virtual HTTP2SendDataResult onSendMimePartData(char* bytes, size_t size) = 0; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMEREQUESTSOURCEINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeResponseDecoder.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeResponseDecoder.h new file mode 100644 index 0000000000..2c93919a1f --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeResponseDecoder.h @@ -0,0 +1,86 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMERESPONSEDECODER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMERESPONSEDECODER_H_ + +#include + +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2MimeResponseSinkInterface.h" +#include "AVSCommon/Utils/HTTP2/HTTP2ResponseSinkInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Class that adapts between HTTPResponseSinkInterface and HTTP2MimeResponseSinkInterface providing + * mime decoding services. + */ +class HTTP2MimeResponseDecoder : public HTTP2ResponseSinkInterface { +public: + /** + * Constructor. + * + * @param sink Pointer to the object to receive the mime parts. + */ + HTTP2MimeResponseDecoder(std::shared_ptr sink); + + /** + * Destructor. + */ + ~HTTP2MimeResponseDecoder() = default; + + /// @name HTTP2ResponseSinkInterface methods. + /// @{ + bool onReceiveResponseCode(long responseCode) override; + bool onReceiveHeaderLine(const std::string& line) override; + HTTP2ReceiveDataStatus onReceiveData(const char* bytes, size_t size) override; + void onResponseFinished(HTTP2ResponseFinishedStatus status) override; + /// @} + +private: + /** We will also need to implements the callbacks for @class MultipartReader **/ + static void partBeginCallback(const MultipartHeaders& headers, void* userData); + static void partDataCallback(const char* buffer, size_t size, void* userData); + static void partEndCallback(void* userData); + + /// MIMEResponseSinkInterface implementation to pass MIME data to + std::shared_ptr m_sink; + /// Response code that has been received, or zero. + long m_responseCode; + /// Instance of a multipart MIME reader. + MultipartReader m_multipartReader; + /// Last parse status returned + HTTP2ReceiveDataStatus m_lastStatus; + /// MIME part callbacks on current(or last in case of PAUSE) chunk + size_t m_index; + /// Number of characters left to check at the beginning of the stream for a leading CRLF sequence. + size_t m_leadingCRLFCharsLeftToRemove; + /// needed to track if boundary has been set + bool m_boundaryFound; + /// last successful MIME part callback on current chunk + size_t m_lastSuccessIndex; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMERESPONSEDECODER_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeResponseSinkInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeResponseSinkInterface.h new file mode 100644 index 0000000000..fe2ff958e1 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2MimeResponseSinkInterface.h @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMERESPONSESINKINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMERESPONSESINKINTERFACE_H_ + +#include +#include +#include +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2ResponseSinkInterface.h" +#include "AVSCommon/Utils/HTTP2/HTTP2ReceiveDataStatus.h" +#include "AVSCommon/Utils/HTTP2/HTTP2ResponseFinishedStatus.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Interface for receiving a mime encoded HTTP2 response. + */ +class HTTP2MimeResponseSinkInterface { +public: + /** + * Default destructor. + */ + virtual ~HTTP2MimeResponseSinkInterface() = default; + + /** + * Notification that an HTTP response code was returned for the request. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param responseCode The response code received for the request. + * @return Whether receipt of the response should continue. + */ + virtual bool onReceiveResponseCode(long responseCode) = 0; + + /** + * Notification that an HTTP header line was received. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param line The HTTP response header line that was received. + * @return Whether receipt of the response should continue. + */ + virtual bool onReceiveHeaderLine(const std::string& line) = 0; + + /** + * Notification of the start of a new mime part. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param headers A multimap from header names to header values. + * @return Whether receipt of the response should continue. + */ + virtual bool onBeginMimePart(const std::multimap& headers) = 0; + + /** + * Notification of new body data received from an HTTP2 response. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param bytes The buffer containing the bytes to consume. + * @param size The number of bytes to consume. + * @return Status of the operation. @see HTTP2ReceiveDataStatus + */ + virtual HTTP2ReceiveDataStatus onReceiveMimeData(const char* bytes, size_t size) = 0; + + /** + * Notification of the end of the current mime part. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @return Whether receipt of the response should continue. + */ + virtual bool onEndMimePart() = 0; + + /** + * Notification of receipt of non-mime body data in an HTTP2 response. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param bytes The buffer containing the bytes to consume. + * @param size The number of bytes to consume. + * @return Status of the operation. @see HTTP2ReceiveDataStatus. + */ + virtual HTTP2ReceiveDataStatus onReceiveNonMimeData(const char* bytes, size_t size) = 0; + + /** + * Notification that the request/response cycle has finished and no further notifications will be provided. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param status The status with which the response finished. @see HTTP2ResponseFinishedStatus. + */ + virtual void onResponseFinished(HTTP2ResponseFinishedStatus status) = 0; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2MIMERESPONSESINKINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ReceiveDataStatus.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ReceiveDataStatus.h new file mode 100644 index 0000000000..5905d86b8c --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ReceiveDataStatus.h @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2RECEIVEDATASTATUS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2RECEIVEDATASTATUS_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Status returned from @c HTTP2ResponseSinkInterface::onReceiveData() and + * @c HTTP2MimeResponseSinkInterface::onReceiveMimePartData(). + */ +enum class HTTP2ReceiveDataStatus { + /// Successful receipt of all data passed in this call. + SUCCESS, + /// Not ready to receive data. Retry receiving this data later. + PAUSE, + /// Data not received. Abort receiving further data for this response. + ABORT +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2RECEIVEDATASTATUS_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestConfig.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestConfig.h new file mode 100644 index 0000000000..9a94fbac71 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestConfig.h @@ -0,0 +1,310 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTCONFIG_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTCONFIG_H_ + +#include +#include +#include +#include +#include + +#include "HTTP2RequestType.h" +#include "HTTP2RequestSourceInterface.h" +#include "HTTP2ResponseSinkInterface.h" +#include "HTTP2RequestType.h" + +#ifdef ACSDK_EMIT_SENSITIVE_LOGS +#define ACSDK_EMIT_CURL_LOGS +#endif + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Class for configuring an HTTP2 request. + */ +class HTTP2RequestConfig { +public: + /** + * Constructor. + * + * @param type The type of request. + * @param url The URL to receive the request. + * @param idPrefix Prefix used when creating the request's ID. + */ + HTTP2RequestConfig(HTTP2RequestType type, const std::string& url, const std::string& idPrefix); + + /** + * Specify the maximum time, in milliseconds, for the connection phase to the server to take. + * + * @param timeout The max amount of time, in milliseconds, for the connection phase to the server to take. + */ + void setConnectionTimeout(std::chrono::milliseconds timeout); + + /** + * Specify the maximum time the request is allowed to take. + * + * @param timeout The max amount of time, in milliseconds, that the request is allowed to take. + */ + void setTransferTimeout(std::chrono::milliseconds timeout); + + /** + * Specify the maximum time, in milliseconds, to wait between any read or write operations for this request. + * + * @param timeout The max amount of time, in milliseconds, between any read or write operations for this request. + */ + void setActivityTimeout(std::chrono::milliseconds timeout); + + /** + * Specify the priority of this request. + * + * @param The priority of the request. Higher values (max 255) specify higher priority. + * By default requests are assigned a priority of 16. + * @see https://tools.ietf.org/html/rfc7540#section-5.3 + */ + void setPriority(uint8_t priority); + + /** + * Specify the object to provide the data for this HTTP2 request. + * + * @param source The object to provide the data for this HTTP2 POST request. + */ + void setRequestSource(std::shared_ptr source); + + /** + * Specify the object to receive the response to this HTTP2 request. + * + * @param sink The object to receive the response to this HTTP2 request. + */ + void setResponseSink(std::shared_ptr sink); + + /** + * If this request expects that transfer will happen intermittently, set this property. (It is false by default.) + */ + void setIntermittentTransferExpected(); + + /** + * Set stream identification prefix to use for file names if extended curl logging is enabled. + * Request ID will be appended to it. + * @param logicalStreamIdPrefix logical stream Id prefix to use in CURL log file names. + */ + void setLogicalStreamIdPrefix(std::string logicalStreamIdPrefix); + + /** + * Get the type of the request (like GET or POST) + * + * @return the request type + */ + HTTP2RequestType getRequestType() const; + + /** + * Get the URL which is to receive the request. + * + * @return The URL to receive the request. + */ + std::string getUrl() const; + + /** + * Get the maximum time, in milliseconds, for the connection phase to the server to take. + * + * @return The max amount of time, in milliseconds, for the connection phase to the server to take. + */ + std::chrono::milliseconds getConnectionTimeout() const; + + /** + * Get the maximum time the request is allowed to take. + * + * @return The max amount of time, in milliseconds, that the request is allowed to take. + */ + std::chrono::milliseconds getTransferTimeout() const; + + /** + * Get the maximum time, in milliseconds, to wait between any read or write operations for this request. + * + * @param timeout The max amount of time, in milliseconds, between any read or write operations for this request. + */ + std::chrono::milliseconds getActivityTimeout() const; + + /** + * Get the priority of this request. + * + * @return The priority of the request. Higher values (max 255) specify higher priority. + * By default requests are assigned a priority of 16. + * @see https://tools.ietf.org/html/rfc7540#section-5.3 + */ + uint8_t getPriority() const; + + /** + * Get the object to provide the data for this HTTP2 request. + * + * @return The object to provide the data for this HTTP2 POST request. + */ + std::shared_ptr getSource() const; + + /** + * Get the object to receive the response to this HTTP2 request. + * + * @return The object to receive the response to this HTTP2 request. + */ + std::shared_ptr getSink() const; + + /** + * Whether this request expects that transfer will happen intermittently. + * @return Whether this request expects that transfer will happen intermittently. + */ + bool isIntermittentTransferExpected() const; + + /** + * Get the name used to identify the request. + * + * @return the name sued to identify the request. + */ + std::string getId() const; + +private: + /// The type of request. + const HTTP2RequestType m_type; + + /// The URL to receive the request. + const std::string m_url; + + /// Default priority for streams. @see https://tools.ietf.org/html/rfc7540#section-5.3 + static const uint8_t DEFAULT_PRIORITY = 16; + + /// The max amount of time, in milliseconds, for the connection phase to the server to take. + /// Value of std::chrono::milliseconds::zero() means "not set" + std::chrono::milliseconds m_connectionTimeout; + + /// The max amount of time, in milliseconds, that the request is allowed to take. + /// Value of std::chrono::milliseconds::zero() means "not set" + std::chrono::milliseconds m_transferTimeout; + + /// The max amount of time, in milliseconds, between any read or write operations for this request. + /// Value of std::chrono::milliseconds::zero() means "not set" + std::chrono::milliseconds m_activityTimeout; + + /// The priority of the request. Higher values (max 255) specify higher priority. + /// By default requests are assigned a priority of 16. + /// @see https://tools.ietf.org/html/rfc7540#section-5.3 + uint8_t m_priority; + + /// The object to provide the data for this HTTP2 POST request. + std::shared_ptr m_source; + + /// The object to receive the response to this HTTP2 request. + std::shared_ptr m_sink; + + /// Whether this requests expects intermittent transfers. + /// If true, the transfer thread may be put to sleep even when this request isn't paused. + bool m_isIntermittentTransferExpected; + + /// The ID to assign to the request. + + std::string m_id; +}; + +inline HTTP2RequestConfig::HTTP2RequestConfig( + HTTP2RequestType type, + const std::string& url, + const std::string& idPrefix) : + m_type(type), + m_url(url), + m_connectionTimeout{std::chrono::milliseconds::zero()}, + m_transferTimeout{std::chrono::milliseconds::zero()}, + m_activityTimeout{std::chrono::milliseconds::zero()}, + m_priority{DEFAULT_PRIORITY}, + m_isIntermittentTransferExpected{false} { + static std::atomic nextId{1}; + m_id = idPrefix + std::to_string(std::atomic_fetch_add(&nextId, uint64_t{2})); +}; + +inline void HTTP2RequestConfig::setConnectionTimeout(std::chrono::milliseconds timeout) { + m_connectionTimeout = timeout; +} + +inline void HTTP2RequestConfig::setTransferTimeout(std::chrono::milliseconds timeout) { + m_transferTimeout = timeout; +} + +inline void HTTP2RequestConfig::setActivityTimeout(std::chrono::milliseconds timeout) { + m_activityTimeout = timeout; +} + +inline void HTTP2RequestConfig::setPriority(uint8_t priority) { + m_priority = priority; +} + +inline void HTTP2RequestConfig::setRequestSource(std::shared_ptr source) { + m_source = std::move(source); +} + +inline void HTTP2RequestConfig::setResponseSink(std::shared_ptr sink) { + m_sink = std::move(sink); +} + +inline void HTTP2RequestConfig::setIntermittentTransferExpected() { + m_isIntermittentTransferExpected = true; +} + +inline std::string HTTP2RequestConfig::getId() const { + return m_id; +} + +inline HTTP2RequestType HTTP2RequestConfig::getRequestType() const { + return m_type; +} + +inline std::string HTTP2RequestConfig::getUrl() const { + return m_url; +}; + +inline std::chrono::milliseconds HTTP2RequestConfig::getConnectionTimeout() const { + return m_connectionTimeout; +} + +inline std::chrono::milliseconds HTTP2RequestConfig::getTransferTimeout() const { + return m_transferTimeout; +} + +inline std::chrono::milliseconds HTTP2RequestConfig::getActivityTimeout() const { + return m_activityTimeout; +} + +inline uint8_t HTTP2RequestConfig::getPriority() const { + return m_priority; +} + +inline std::shared_ptr HTTP2RequestConfig::getSource() const { + return m_source; +} + +inline std::shared_ptr HTTP2RequestConfig::getSink() const { + return m_sink; +} + +inline bool HTTP2RequestConfig::isIntermittentTransferExpected() const { + return m_isIntermittentTransferExpected; +} + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTCONFIG_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestInterface.h new file mode 100644 index 0000000000..0d367fdece --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestInterface.h @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTINTERFACE_H_ + +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2RequestSourceInterface.h" +#include "AVSCommon/Utils/HTTP2/HTTP2ResponseSinkInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Interface for making an HTTP2 request. + */ +class HTTP2RequestInterface { +public: + /** + * Destructor. + */ + virtual ~HTTP2RequestInterface() = default; + + /** + * Cancel this @c HTTP2Request. + * + * @return Whether cancelling this request was triggered. + */ + virtual bool cancel() = 0; + + /** + * Get an integer uniquely identifying this request. + * + * @return An integer uniquely identifying this request. + */ + virtual std::string getId() const = 0; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestSourceInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestSourceInterface.h new file mode 100644 index 0000000000..d1977e2f01 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestSourceInterface.h @@ -0,0 +1,69 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTSOURCEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTSOURCEINTERFACE_H_ + +#include +#include +#include +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2SendDataResult.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Interface for providing data to be sent as part of an HTTP2 request. + */ +class HTTP2RequestSourceInterface { +public: + /** + * Default destructor. + */ + virtual ~HTTP2RequestSourceInterface() = default; + + /** + * Get the header lines that should be output with this HTTP2 request. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @return The header lines that should be output with this request. + */ + virtual std::vector getRequestHeaderLines() = 0; + + /** + * Notification of the need to provide body data for an HTTP2 request. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param bytes The buffer to receive the bytes to send. + * @param size The max number of bytes to copy. + * @return Result indicating the disposition of the operation and number of bytes copied. @see HTTPSendDataResult. + */ + virtual HTTP2SendDataResult onSendData(char* bytes, size_t size) = 0; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTSOURCEINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestType.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestType.h new file mode 100644 index 0000000000..cbf20daa2b --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2RequestType.h @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTTYPE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTTYPE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Type of request mde over an HTTP2Connection. + */ +enum class HTTP2RequestType { + /// A HTTP2 GET request. + GET, + /// A HTTP2 POST request. + POST +}; + +inline std::ostream& operator<<(std::ostream& stream, HTTP2RequestType type) { + switch (type) { + case HTTP2RequestType::GET: + return stream << "GET"; + case HTTP2RequestType::POST: + return stream << "POST"; + } + return stream << "UNKNOWN(" << static_cast(type) << ")"; +} + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2REQUESTTYPE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ResponseFinishedStatus.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ResponseFinishedStatus.h new file mode 100644 index 0000000000..ff21844170 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ResponseFinishedStatus.h @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2RESPONSEFINISHEDSTATUS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2RESPONSEFINISHEDSTATUS_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Status provided when an HTTP2 response is finished. + */ +enum class HTTP2ResponseFinishedStatus { + /// Receipt of the response was completed. + COMPLETE, + /// Receipt of the response was not completed due to a timeout. + TIMEOUT, + /// Receipt of the response was not completed because the operation was cancelled. + CANCELLED, + /// Receipt of the response was not completed due to an internal error. + INTERNAL_ERROR +}; + +/** + * Write a @c HTTP2ResponseFinishedStatus value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param status The status to write to the @c ostream as a string. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, HTTP2ResponseFinishedStatus status) { + switch (status) { + case HTTP2ResponseFinishedStatus::COMPLETE: + return stream << "COMPLETE"; + case HTTP2ResponseFinishedStatus::TIMEOUT: + return stream << "TIMEOUT"; + case HTTP2ResponseFinishedStatus::CANCELLED: + return stream << "CANCELLED"; + case HTTP2ResponseFinishedStatus::INTERNAL_ERROR: + return stream << "INTERNAL_ERROR"; + } + return stream << ""; +} + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2RESPONSEFINISHEDSTATUS_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ResponseSinkInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ResponseSinkInterface.h new file mode 100644 index 0000000000..74ddb9c971 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2ResponseSinkInterface.h @@ -0,0 +1,90 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2RESPONSESINKINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2RESPONSESINKINTERFACE_H_ + +#include +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2ReceiveDataStatus.h" +#include "AVSCommon/Utils/HTTP2/HTTP2ResponseFinishedStatus.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Interface for consuming data returned from an @c HTTP2RequestInterface. + */ +class HTTP2ResponseSinkInterface { +public: + /** + * Default destructor. + */ + virtual ~HTTP2ResponseSinkInterface() = default; + + /** + * Notification that an HTTP response code was returned for the request. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param responseCode The response code received for the request. + * @return Whether receipt of the response should continue. + */ + virtual bool onReceiveResponseCode(long responseCode) = 0; + + /** + * Notification than an HTTP header line was received. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param line The HTTP response header line that was received. + * @return Whether receipt of the response should continue. + */ + virtual bool onReceiveHeaderLine(const std::string& line) = 0; + + /** + * Notification of receipt of body data in an HTTP2 response. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param bytes The buffer containing the bytes to consume. + * @param size The number of bytes to consume. + * @return Status of the operation. @see HTTP2ReceiveDataStatus. + */ + virtual HTTP2ReceiveDataStatus onReceiveData(const char* bytes, size_t size) = 0; + + /** + * Notification that the request/response cycle has finished and no further notifications will be provided. + * + * @note Calls to this method may block network operations for the associated instance of HTTP2ConnectionInterface, + * so they should return quickly. + * + * @param status The status with which receiving the response finished. @see HTTP2ResponseFinishedStatus. + */ + virtual void onResponseFinished(HTTP2ResponseFinishedStatus status) = 0; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2RESPONSESINKINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2SendDataResult.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2SendDataResult.h new file mode 100644 index 0000000000..9c2cccc250 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2SendDataResult.h @@ -0,0 +1,77 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2SENDDATARESULT_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2SENDDATARESULT_H_ + +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2SendStatus.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Value returned from various methods that send data, combining a status and a size. + */ +struct HTTP2SendDataResult { + /// The status of the send data operation. @see HTTP2SendStatus. + HTTP2SendStatus status; + + /// The number of bytes copied. This value should only be non-zero if @c status == @c CONTINUE. + size_t size; + + /** + * Construct a HTTP2SendDataResult with a status of CONTINUE and the specified size + * + * @param size The count of bytes sent. + */ + explicit HTTP2SendDataResult(size_t size); + + /// Const PAUSE result. + static const HTTP2SendDataResult PAUSE; + + /// Const COMPLETE result. + static const HTTP2SendDataResult COMPLETE; + + /// Const ABORT result. + static const HTTP2SendDataResult ABORT; + +private: + /** + * Construct a HTTP2SendDataResult. + * + * @param status The status of the send operation. + * @param size The count of bytes sent. + */ + HTTP2SendDataResult(HTTP2SendStatus status, size_t size); +}; + +inline HTTP2SendDataResult::HTTP2SendDataResult(size_t sizeIn) : status{HTTP2SendStatus::CONTINUE}, size{sizeIn} { +} + +inline HTTP2SendDataResult::HTTP2SendDataResult(HTTP2SendStatus statusIn, size_t sizeIn) : + status{statusIn}, + size{sizeIn} { +} + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2SENDDATARESULT_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2SendStatus.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2SendStatus.h new file mode 100644 index 0000000000..2e4b502fbf --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTP2/HTTP2SendStatus.h @@ -0,0 +1,43 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2SENDSTATUS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2SENDSTATUS_H_ + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/** + * Status returned from various send operations. + */ +enum class HTTP2SendStatus { + /// Operation succeeded. Continue. + CONTINUE, + /// Operation delayed. Retry later. + PAUSE, + /// Operation completed successfully. + COMPLETE, + /// Operation failed. Abort. + ABORT +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTP2_HTTP2SENDSTATUS_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/HTTPContent.h b/AVSCommon/Utils/include/AVSCommon/Utils/HTTPContent.h index 7a9b3fca1e..ac29785de7 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/HTTPContent.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/HTTPContent.h @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTPCONTENT_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_HTTPCONTENT_H_ +#include #include #include #include @@ -27,30 +28,102 @@ namespace avsCommon { namespace utils { /** - * This struct encapsulates content received from HTTP request, specifically the status code, the content-type, and the + * This class encapsulates content received from HTTP request, specifically the status code, the content-type, and the * actual content of the response. */ -struct HTTPContent { +class HTTPContent { +public: /** - * This function blocks until @c statusCode is set and checks whether it is equal to 200, indicating an HTTP sucess - * code. + * Constructor. * - * @return @c true if `statuscode == 200`, else @c false. + * @param httpStatusCode The future for the HTTP status code. + * @param httpContentType The future for the HTTP content type. + * @param stream The Attachment from which to read the HTTP content from or @c nullptr if no data was fetched. */ - operator bool() const; + HTTPContent( + std::future httpStatusCode, + std::future httpContentType, + std::shared_ptr stream); + /** + * This function checks if the status code is HTTP success or not. This function blocks until status code is + * set. + * + * @return @c true if status code is 2xx, else @c false. + */ + bool isStatusCodeSuccess(); + + /** + * This function returns the status code. This function blocks until status code is set. + * + * @return The value of the status code. + */ + long getStatusCode(); + + /** + * This function checks if the status code is ready to be read. + * + * @param timeout The timeout to wait for to see if status code is ready. + * @return @c true if status code is ready, else @c false. + */ + bool isReady(const std::chrono::milliseconds timeout) const; + + /** + * This function returns the content type. This function blocks until content type is set. + * + * @return The value of content type. + */ + std::string getContentType(); + + /** + * This function returns the attachment from which to read the HTTP content. + * + * @return An @c InProcessAttachment or @c nullptr if no data was fetched. + */ + std::shared_ptr getDataStream(); + +private: /// A @c long representing the HTTP status code. - mutable std::future statusCode; + mutable std::shared_future m_statusCode; /// A @c string representing the content-type of the HTTP content. - std::future contentType; + std::shared_future m_contentType; /// An @c Attachment from which to read the HTTP content from or @c nullptr if no data was fetched. - std::shared_ptr dataStream; + std::shared_ptr m_dataStream; }; -inline HTTPContent::operator bool() const { - return statusCode.get() == 200; +inline HTTPContent::HTTPContent( + std::future httpStatusCode, + std::future httpContentType, + std::shared_ptr stream) : + m_statusCode{std::move(httpStatusCode)}, + m_contentType{std::move(httpContentType)}, + m_dataStream{stream} {}; + +inline long HTTPContent::getStatusCode() { + auto statusCodeFuture = m_statusCode; + return statusCodeFuture.get(); +} + +inline bool HTTPContent::isStatusCodeSuccess() { + auto statusCode = getStatusCode(); + return (statusCode >= 200) && (statusCode < 300); +} + +inline bool HTTPContent::isReady(const std::chrono::milliseconds timeout) const { + auto statusCodeFuture = m_statusCode; + auto status = statusCodeFuture.wait_for(timeout); + return std::future_status::ready == status; +} + +inline std::string HTTPContent::getContentType() { + auto contentTypeFuture = m_contentType; + return contentTypeFuture.get(); +} + +inline std::shared_ptr HTTPContent::getDataStream() { + return m_dataStream; } } // namespace utils diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONGenerator.h b/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONGenerator.h new file mode 100644 index 0000000000..0bb9d50b75 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONGenerator.h @@ -0,0 +1,142 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_JSON_JSONGENERATOR_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_JSON_JSONGENERATOR_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace json { + +/** + * Utility class that can be used to build a json string. + * + * E.g.: To build the following json: {"param1":"value","param2":{"param2.1":100}}. Use: + * + * JsonGenerator generator; + * generator.addMember("param1", "value"); + * generator.startObject("param2"); + * generator.addMember("param2.1", 100); + * generator.toString(); + * + * For debugging purpose, you may be able to get the partial string by calling: + * + * generator.toString(false) + * + * @note This class is NOT thread safe. + * @note This class doesn't support arrays yet. + */ +class JsonGenerator { +public: + /** + * Constructor. + */ + JsonGenerator(); + + /** + * Default destructor. + */ + ~JsonGenerator() = default; + + /** + * Checks whether the generator has been finalized (i.e., no changes can be made to the current json). + * + * @return @c true if it has been finalized, @c false otherwise + */ + bool isFinalized(); + + /** + * Starts a new json object with the given key. + * + * @param key The new object name. + * @return @c true if it succeeds to create a new object and @c false if it fails. + */ + bool startObject(const std::string& key); + + /** + * Close the last object that has been opened. + * + * @return @c true if the last object was closed @c false if it fails. + */ + bool finishObject(); + + ///@{ + /** + * Add a new member with the key and value. + * + * @param key The name of the member. + * @param value The value of the member. + * @return @c true if it succeeded to add the new member and @c false otherwise. + */ + bool addMember(const std::string& key, const char* value); + bool addMember(const std::string& key, const std::string& value); + bool addMember(const std::string& key, int64_t value); + bool addMember(const std::string& key, uint64_t value); + bool addMember(const std::string& key, int value); + bool addMember(const std::string& key, unsigned int value); + bool addMember(const std::string& key, bool value); + ///@} + + /** + * Adds a raw json as a value to the given key. + * + * @param key The object key to the raw json provided. + * @param json A string representation of a @b valid json. + * @param validate Enable json validation for the raw json parameter. + * @return @c true if it succeeded to add the raw json and @c false otherwise. + */ + bool addRawJsonMember(const std::string& key, const std::string& json, bool validate = true); + + /** + * Return the string representation of the object. + * + * @param finalize If set to @c true the object will be finalized and the string returned will be a complete json + * document. If @c false, the returned string will represent the current state of the json generation which could + * be partial. + * @note Once the object has been finalized, no changes can be made to the generator. + * @return The string representation of the json object. + */ + std::string toString(bool finalize = true); + +private: + /// Checks if the writer is still open and ready to be used. + inline bool checkWriter(); + + /** + * Method used to finalize the json. This will close all the open objects including the root object. + * + * @return @c true if it succeeded to finalize the generator. + */ + bool finalize(); + + /// The string buffer used to store the json string. + rapidjson::StringBuffer m_buffer; + + /// The json writer. + rapidjson::Writer m_writer; +}; + +} // namespace json +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_JSON_JSONGENERATOR_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONUtils.h b/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONUtils.h index f16aa66090..d42a53fde7 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONUtils.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONUtils.h @@ -16,9 +16,11 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_JSON_JSONUTILS_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_JSON_JSONUTILS_H_ -#include +#include #include +#include + #include "AVSCommon/Utils/Logger/LoggerUtils.h" namespace alexaClientSDK { @@ -76,6 +78,16 @@ bool convertToValue(const rapidjson::Value& documentNode, std::string* value); */ bool convertToValue(const rapidjson::Value& documentNode, int64_t* value); +/** + * Converts a given rapidjson value node to a 64-bit unsigned integer. The node must + * be unsigned int type. + * + * @param documentNode A logical node within a parsed JSON document which rapidjson understands. + * @param[out] value The output parameter which will be assigned the unsigned int value. + * @return @c true If the node was successfully converted, @c false otherwise. + */ +bool convertToValue(const rapidjson::Value& documentNode, uint64_t* value); + /** * Converts a given rapidjson value node to a bool. The node must be Bool type. * diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CallbackData.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CallbackData.h new file mode 100644 index 0000000000..c0a7b48dfc --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CallbackData.h @@ -0,0 +1,90 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_CALLBACKDATA_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_CALLBACKDATA_H_ + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +/** + * Class to handle CURL callback data + */ +class CallbackData { +public: + /** + * Constructor. + */ + CallbackData() = default; + + /** + * Constructor that initializes with a value. + */ + CallbackData(const char* data); + + /** + * This will append new data to the existing instance. + * + * @param data The data that needs to be appended. + * @param dataSize The size of the data to be appended. + * @return The size of data that was appended. + */ + size_t appendData(const char* data, size_t dataSize); + + /** + * This will append new data to the existing instance. + * + * @param data The data that needs to be appended. + * @return The size of data that was appended. + */ + size_t appendData(const char* data); + + /** + * This will clear the data. + */ + void clearData(); + + /** + * Will return the callback data in the passed in container. + * + * @param dataContainer The container in which the callback data will be returned. + * @param dataSize The expected size of the data to be returned. + * @return Size of the returned callback data. + */ + size_t getData(char* dataContainer, size_t dataSize); + + /** + * Returns the callback data size. + * + * @return Callback data size. + */ + size_t getSize(); + +private: + /// Callback data + std::vector m_memory; +}; + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_CALLBACKDATA_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlEasyHandleWrapper.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlEasyHandleWrapper.h index be830bf33f..72a78344c4 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlEasyHandleWrapper.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlEasyHandleWrapper.h @@ -23,6 +23,15 @@ #include #include +/// Whether or not curl logs should be emitted. +#ifdef ACSDK_EMIT_SENSITIVE_LOGS + +#define ACSDK_EMIT_CURL_LOGS +#include +#include + +#endif + namespace alexaClientSDK { namespace avsCommon { namespace utils { @@ -35,7 +44,7 @@ class CurlEasyHandleWrapper { public: /** * Callbacks to libcurl typically follow the below pattern. - * size_t callback(char* data, size_t size, size_t nmemb, void* user) + * size_t callback(char* buffer, size_t blockSize, size_t numBlocks, void* userData) * @param buffer A pointer to the data buffer to either read or write to * @param blockSize The size of a "block" of data (ala fwrite) * @param numBlocks The number of "blocks" to read or write @@ -43,6 +52,18 @@ class CurlEasyHandleWrapper { */ using CurlCallback = size_t (*)(char* buffer, size_t blockSize, size_t numBlocks, void* userData); + /** + * Debug Callbacks to libcurl typically follow the below pattern. + * size_t callback(CURL* handle, curl_infotype infoType, char *buffer, size_t blockSize, void *userData) + * @param handle The CURL handle + * @param handle The CURL info type + * @param buffer A pointer to the data buffer to either read or write to + * @param blockSize The size of a "block" of data (ala fwrite) + * @param userData Some user data passed in with CURLOPT_DEBUGDATA + */ + using CurlDebugCallback = + int (*)(CURL* handle, curl_infotype infoType, char* buffer, size_t blockSize, void* userData); + /** * Definitions for HTTP action types */ @@ -50,16 +71,21 @@ class CurlEasyHandleWrapper { /// HTTP GET kGET, /// HTTP POST - kPOST + kPOST, + /// HTTP PUT + kPUT }; /** - * Default constructor + * Default constructor, optionally passing in an explicit Id. + * + * @param id name to use to identify this handle. If none provided, an automatically generated + * one will be assigned. */ - CurlEasyHandleWrapper(); + CurlEasyHandleWrapper(std::string id = ""); /** - * Default destructor + * Destructor */ ~CurlEasyHandleWrapper(); @@ -72,9 +98,11 @@ class CurlEasyHandleWrapper { *
  • POST headers
  • *
  • CURL post form
  • * + * @param id name to use to identify this handle. If none provided, an automatically generated + * one will be assigned. * @return Whether the reset was successful */ - bool reset(); + bool reset(std::string id = ""); /** * Used to get the underlying CURL easy handle. The handle returned @@ -91,6 +119,13 @@ class CurlEasyHandleWrapper { */ bool isValid(); + /** + * Get the ID for this handle. + * + * @return The ID for this handle. + */ + std::string getId() const; + /* * Adds an HTTP Header to the current easy handle * @@ -123,16 +158,6 @@ class CurlEasyHandleWrapper { */ bool setTransferType(TransferType type); - /** - * Adds a POST field to the current multipart form named @c fieldName with a string - * value contained in payload - * - * @param fieldName The POST field name - * @param payload The string to send - * @return Whether the addition was successful - */ - bool setPostContent(const std::string& fieldName, const std::string& payload); - /** * Sets a timeout, in seconds, for how long the stream transfer is allowed to take. * If not set explicitly, there will be no timeout. @@ -142,17 +167,6 @@ class CurlEasyHandleWrapper { */ bool setTransferTimeout(const long timeoutSeconds); - /** - * Adds a POST field to the current multipart form named @c fieldName with a chunked - * transfer encoded data stream. The readCallback set in setReadCallback will be called - * when data is required. - * - * @param fieldName The POST field name. - * @param userData User data passed into the read callback. - * @return Whether the addition was successful. - */ - bool setPostStream(const std::string& fieldName, void* userData); - /** * Sets the data to be sent in the next POST operation. * @@ -209,6 +223,37 @@ class CurlEasyHandleWrapper { template bool setopt(CURLoption option, ParamType param); + /** + * URL encode a string. + * + * @param in The string to encode. + * @return The encoded string. + */ + std::string urlEncode(const std::string& in) const; + + /** + * Get the HTTP response code. + * + * @return The HTTP response code. + */ + long getHTTPResponseCode(); + + /** + * Perform whatever has been setup in the handle. + * + * @return The CURL response code. + */ + CURLcode perform(); + + /** + * Call @c curl_easy_pause() for this handle. + * + * @param mask The mask value to pass through to @c curl_easy_pause(). + * @see https://curl.haxx.se/libcurl/c/curl_easy_pause.html + * @return The CURL response code. + */ + CURLcode pause(int mask); + private: /** * Frees and sets the following attributes to NULL: @@ -227,6 +272,34 @@ class CurlEasyHandleWrapper { */ bool setDefaultOptions(); +#ifdef ACSDK_EMIT_CURL_LOGS + /** + * Initialize capturing this streams activities in a log file. + */ + void initStreamLog(); + + /** + * Callback that is invoked when @c libcurl has debug information to provide. + * + * @param handle @c libcurl handle of the transfer being reported upon. + * @param type The type of data being reported. + * @param data Pointer to the data being provided. + * @param size Size (in bytes) of the data being reported. + * @param user User pointer used to pass which HTTP2Stream is being reported on. + * @return Always returns zero. + */ + static int debugFunction(CURL* handle, curl_infotype type, char* data, size_t size, void* user); + + /// File to log the stream I/O to + std::unique_ptr m_streamLog; + /// File to dump data streamed in + std::unique_ptr m_streamInDump; + /// File to dump data streamed out + std::unique_ptr m_streamOutDump; + /// Object to format log strings correctly. + avsCommon::utils::logger::LogStringFormatter m_logFormatter; +#endif + /// The associated libcurl easy handle CURL* m_handle; /// A list of headers needed to be added at the HTTP level @@ -235,6 +308,13 @@ class CurlEasyHandleWrapper { curl_slist* m_postHeaders; /// The associated multipart post curl_httppost* m_post; + /// The last post used in curl_formadd + curl_httppost* m_lastPost; + /// Name for this handle. + std::string m_id; + + /// If no id is provided by the user, we will generate it from this counter. + static std::atomic m_idGenerator; }; template diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HTTPResponse.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HTTPResponse.h new file mode 100644 index 0000000000..74d84ea212 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HTTPResponse.h @@ -0,0 +1,54 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPRESPONSE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPRESPONSE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +/** + * Structure used to capture the values resulting from an HTTP request. + */ +struct HTTPResponse { +public: + /** + * Default constructor. + */ + HTTPResponse(); + + /// The HTTP status code returned by the server. + long code; + + /// The body of the response returned by the server. + std::string body; + + /// Serialize the object + std::string serialize(); +}; + +inline HTTPResponse::HTTPResponse() : code(0) { +} + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPRESPONSE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPost.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPost.h index fbf770b246..aceb6e0cfa 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPost.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPost.h @@ -34,7 +34,16 @@ namespace libcurlUtils { /// LIBCURL based implementation of HttpPostInterface. class HttpPost : public HttpPostInterface { public: - /// HttpPost destructor + /** + * Create a new HttpPost instance, passing ownership of the new instance on to the caller. + * + * @return Returns an std::unique_ptr to the new HttpPost instance, or @c nullptr of the operation failed. + */ + static std::unique_ptr create(); + + /** + * HttpPost destructor + */ ~HttpPost() = default; /** @@ -52,24 +61,35 @@ class HttpPost : public HttpPostInterface { */ HttpPost& operator=(const HttpPost& rhs) = delete; - /** - * Create a new HttpPost instance, passing ownership of the new instance on to the caller. - * - * @return Retruns an std::unique_ptr to the new HttpPost instance, or @c nullptr of the operation failed. - */ - static std::unique_ptr create(); - - bool addHTTPHeader(const std::string& header) override; - long doPost(const std::string& url, const std::string& data, std::chrono::seconds timeout, std::string& body) override; + HTTPResponse doPost( + const std::string& url, + const std::vector headerLines, + const std::vector>& data, + std::chrono::seconds timeout) override; + + HTTPResponse doPost( + const std::string& url, + const std::vector headerLines, + const std::string& data, + std::chrono::seconds timeout) override; + private: /** * Default HttpPost constructor. */ HttpPost() = default; + /** + * Build POST data from a vector of key value pairs. + * + * @param data Vector of key, value pairs from which to build the POST data. + * @return + */ + std::string buildPostData(const std::vector>& data) const; + /** * Callback function used to accumulate the body of the HTTP Post response * This is called when doPost() is holding @c m_mutex. @@ -88,7 +108,7 @@ class HttpPost : public HttpPostInterface { /// CURL handle with which to make requests CurlEasyHandleWrapper m_curl; - /// String used to accumuate the response body. + /// String used to accumulate the response body. std::string m_bodyAccumulator; }; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPostInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPostInterface.h index d29bb471bc..657e33e913 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPostInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPostInterface.h @@ -18,6 +18,10 @@ #include #include +#include +#include + +#include "HTTPResponse.h" namespace alexaClientSDK { namespace avsCommon { @@ -30,14 +34,6 @@ class HttpPostInterface { /// Virtual destructor to assure proper cleanup of derived types. virtual ~HttpPostInterface() = default; - /** - * Adds a HTTP Header to the CURL handle - * - * @param header The HTTP header to add to the POST request. - * @returns @c true if the addition was successful @c false otherwise. - */ - virtual bool addHTTPHeader(const std::string& header) = 0; - /** * Perform an HTTP Post request returning the response body as a string. This method blocks for the duration * of the request. @@ -53,6 +49,39 @@ class HttpPostInterface { const std::string& data, std::chrono::seconds timeout, std::string& body) = 0; + + /** + * Perform an HTTP Post request returning the response body as a string. This method + * blocks for the duration of the request. + * + * @param url The URL to send the POST to. + * @param headerLines vector of strings to add as header lines. + * @param data Key, value pairs describing the POST data to send in the request. This keys and values will + * be URL encoded by this method. + * @param timeout The maximum amount of time (in seconds) to wait for the request to complete. + * @return An object describing the response to the request. + */ + virtual HTTPResponse doPost( + const std::string& url, + const std::vector headerLines, + const std::vector>& data, + std::chrono::seconds timeout) = 0; + + /** + * Perform an HTTP Post request returning the response body as a string. This method + * blocks for the duration of the request. + * + * @param url The URL to send the POST to. + * @param headerLines vector of strings to add as header lines. + * @param data A string containing the POST data to send in the request. + * @param timeout The maximum amount of time (in seconds) to wait for the request to complete. + * @return An object describing the response to the request. + */ + virtual HTTPResponse doPost( + const std::string& url, + const std::vector headerLines, + const std::string& data, + std::chrono::seconds timeout) = 0; }; } // namespace libcurlUtils diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPut.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPut.h new file mode 100644 index 0000000000..33dc278261 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPut.h @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPPUT_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPPUT_H_ + +#include +#include +#include + +#include + +#include "AVSCommon/Utils/LibcurlUtils/CurlEasyHandleWrapper.h" +#include "AVSCommon/Utils/LibcurlUtils/HttpPutInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +/// LIBCURL based implementation of HttpPutInterface. +class HttpPut : public HttpPutInterface { +public: + /** + * Create a new HttpPut instance, passing ownership of the new instance on to the caller. + * + * @return Returns an std::unique_ptr to the new HttpPut instance, or @c nullptr of the operation failed. + */ + static std::unique_ptr create(); + + /** + * HttpPut destructor + */ + ~HttpPut() = default; + + /** + * Deleted copy constructor. + * + * @param rhs The 'right hand side' to not copy. + */ + HttpPut(const HttpPut& rhs) = delete; + + /** + * Deleted assignment operator. + * + * @param rhs The 'right hand side' to not copy. + * @return The object assigned to. + */ + HttpPut& operator=(const HttpPut& rhs) = delete; + + HTTPResponse doPut(const std::string& url, const std::vector& headers, const std::string& data) + override; + +private: + /** + * Default HttpPut constructor. + */ + HttpPut() = default; + + /// Mutex to serialize access to @c m_curl. + std::mutex m_mutex; + + /// CURL handle with which to make requests + CurlEasyHandleWrapper m_curl; +}; + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPPUT_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPutInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPutInterface.h new file mode 100644 index 0000000000..7c46133c1c --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpPutInterface.h @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPPUTINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPPUTINTERFACE_H_ + +#include +#include + +#include "HTTPResponse.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +/// Minimal interface for making HTTP PUT requests. +class HttpPutInterface { +public: + /// Virtual destructor to assure proper cleanup of derived types. + virtual ~HttpPutInterface() = default; + + /** + * Perform an HTTP Put request returning the response body as a string. This method blocks for the duration + * of the request. + * + * @param url The URL to send the PUT to. + * @param headers vector of strings to add in the header. + * @param data The PUT data to send in the request. + * @return An object describing the response to the PUT request. + */ + virtual HTTPResponse doPut( + const std::string& url, + const std::vector& headers, + const std::string& data) = 0; +}; + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPPUTINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpResponseCodes.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpResponseCodes.h index 684c2eddd1..34253af05b 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpResponseCodes.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HttpResponseCodes.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -16,6 +16,14 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPRESPONSECODES_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_HTTPRESPONSECODES_H_ +/** + * An enum to represent HTTP response codes. + * + * ATTENTION - This enum has been deprecated and only exists to provide backward compatibility with code that was + * designed to work with past versions of the AVS C++ SDK. + * + * @deprecated Use the @c alexaClientSDK::avsCommon::utils::http::HTTPResponseCode instead. + */ enum HTTPResponseCode { /// No HTTP response received. HTTP_RESPONSE_CODE_UNDEFINED = 0, @@ -23,8 +31,14 @@ enum HTTPResponseCode { SUCCESS_OK = 200, /// HTTP Succcess with no response payload. SUCCESS_NO_CONTENT = 204, + /// This is used to detect the first Redirection (3xx) code + REDIRECTION_START_CODE = 300, + /// This is used to detect the last Redirection (3xx) code + REDIRECTION_END_CODE = 308, /// HTTP code for invalid request by user. BAD_REQUEST = 400, + /// HTTP code for forbidden request by user. + FORBIDDEN = 403, /// HTTP code for internal error by server which didn't fulfill the request. SERVER_INTERNAL_ERROR = 500 }; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibCurlHttpContentFetcher.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibCurlHttpContentFetcher.h index af91b4fa0c..09e4f33cf6 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibCurlHttpContentFetcher.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibCurlHttpContentFetcher.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -37,13 +37,23 @@ class LibCurlHttpContentFetcher : public avsCommon::sdkInterfaces::HTTPContentFe public: LibCurlHttpContentFetcher(const std::string& url); + /// @name HTTPContentFetcherInterface methods + /// @{ + State getState() override; + std::string getUrl() const override; + Header getHeader(std::atomic* shouldShutdown) override; + bool getBody(std::shared_ptr writer) override; + void shutdown() override; + /// @} + /** * @copydoc * In this implementation, the function may only be called once. Subsequent calls will return @c nullptr. */ std::unique_ptr getContent( FetchOptions option, - std::shared_ptr writer) override; + std::unique_ptr writer = nullptr, + const std::vector& customHeaders = std::vector()) override; /* * Destructor. @@ -60,44 +70,49 @@ class LibCurlHttpContentFetcher : public avsCommon::sdkInterfaces::HTTPContentFe /// A no-op callback to not parse HTTP bodies. static size_t noopCallback(char* data, size_t size, size_t nmemb, void* userData); + /// The content fetching state + State m_state; + + /** + * Helper method to get custom HTTP headers list. + * + * @param customHeaders Custom HTTP headers to add. + * @return @c curl_slist of custom headers if customHeaders are not empty, otherwise NULL. + */ + curl_slist* getCustomHeaderList(std::vector customHeaders); + /// The URL to fetch from. - std::string m_url; + const std::string m_url; /// A libcurl wrapper. CurlEasyHandleWrapper m_curlWrapper; - /// A promise to the caller of @c getContent() that the HTTP status code will be set. - std::promise m_statusCodePromise; + /// A promise for header loading + std::promise m_headerPromise; - /// A promise to the caller of @c getContent() that the HTTP content type will be set. - std::promise m_contentTypePromise; + /// A future for header loading + std::shared_future m_headerFuture; - /** - * A flag to indicate that the body callback has begun. This is used so that we know when header parsing has - * finished and we can satisfy the promises. - */ - bool m_bodyCallbackBegan; + /// The fetched header + HTTPContentFetcherInterface::Header m_header; /** * The writer used to write the HTTP body to, if desired by the caller of @c getContent(). */ std::shared_ptr m_streamWriter; - /** - * The last status code parsed in an HTTP response header. Since we follow redirects, we only want the last status - * code. - */ - long m_lastStatusCode; + /// Number of bytes that has been received in the ongoing request. + ssize_t m_currentContentReceivedLength; - /** - * The last content type parsed in an HTTP response header. Since we follow redirects, we only want the last content - * type. - */ - std::string m_lastContentType; + /// Number of bytes that has been received since the first request. + ssize_t m_totalContentReceivedLength; /// Flag to indicate that the data-fetch operation has completed. std::atomic m_done; + /// Flag to indicate that the @c LibCurlHttpContentFetcher is being shutdown. + std::atomic m_isShutdown; + /** * Internal thread that does the curl_easy_perform. The reason for using a thread is that curl_easy_perform may * block forever if the URL specified is a live stream. @@ -106,6 +121,39 @@ class LibCurlHttpContentFetcher : public avsCommon::sdkInterfaces::HTTPContentFe /// Flag to indicate that a call to @c getContent() has been made. Subsequent calls will not be accepted. std::atomic_flag m_hasObjectBeenUsed; + + /// A mutex to ensure that all state transitions on the m_state variable are thead-safe. + std::mutex m_stateMutex; + + /** + * A mutex to ensure that concurrent calls to getBody are thread-safe. The @c getBody() function should be called + * only once in order to ensure that the content fetcher receives a single attachment writer at the right state. + */ + std::mutex m_getBodyMutex; + + /** + * Writes an error in the logs warning us that an attempt to perform an invalid state transition was made. + * + * @param currentState The current state. + * @param newState The state to which the transition attempt was made. + */ + void reportInvalidStateTransitionAttempt(State currentState, State newState); + + /** + * Performs the state transition, guaranteeing it to be atomic in regard with the header and body future and the + * header successful field. If the transition is invalid, refrains from performing the transition and logs an error + * message. + * + * @param newState The new state. + * @param value The value of the the futures and the header successful field. + */ + void stateTransition(State newState, bool value); + + /** + * Checks if the content fetcher is still waiting for the @c getBody method to be called. + * @return @c true if it is still waiting for the method call. + */ + bool waitingForBodyRequest(); }; } // namespace libcurlUtils diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h new file mode 100644 index 0000000000..15f6255164 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h @@ -0,0 +1,178 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_LIBCURLHTTP2CONNECTION_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_LIBCURLHTTP2CONNECTION_H_ + +#include +#include +#include +#include +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2ConnectionInterface.h" +#include "CurlMultiHandleWrapper.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +class LibcurlHTTP2Request; + +class LibcurlHTTP2Connection : public avsCommon::utils::http2::HTTP2ConnectionInterface { +public: + /** + * Create an @c LibcurlHTTP2Connection. + * + * @return The new @c LibcurlHTTP2Connection or nullptr if the operation fails. + */ + static std::shared_ptr create(); + + /** + * Destructor. + */ + ~LibcurlHTTP2Connection(); + + /// @name HTTP2ConnectionInterface methods. + /// @{ + std::shared_ptr createAndSendRequest( + const http2::HTTP2RequestConfig& config) override; + void disconnect() override; + /// @} + +protected: + /** + * Constructor + */ + LibcurlHTTP2Connection(); + +private: + /** + * Adds a configured stream into this connection. + * @param stream Request object with a configured curl handle. + * + * @return @c libcurl code indicating the result of this operation. + */ + bool addStream(std::shared_ptr stream); + + /** Set up the multi-handle. Re-assign a new multi-handle, if m_multi is already non-null. + * + * @return true if successful, false if a failure was encountered + */ + bool createMultiHandle(); + + /** Release a stream. + * + * @param stream The stream to release. + * @return Whether the operation was successful. + */ + bool releaseStream(LibcurlHTTP2Request& stream); + + /** + * Main network loop. Repeatedly call curl_multi_perform in order to transfer data on the incorporated streams. + */ + void networkLoop(); + + /** + * Find out whether or not the network loop is stopping. + * + * @return Whether or not the network loop is stopping. + */ + bool isStopping(); + + /** + * Safely set m_isStopping to break out of the network loop. + */ + void setIsStopping(); + + /** + * Checks if any active streams have finished and reports the response code and completion status for them. + */ + void cleanupFinishedStreams(); + + /** Checks for streams that have been cancelled, removes them, and reports CANCELLED completion status. + * Checks for streams that have not progressed within their timeout, removes them, and reports the response code + * (if any) and completion status (TIMEOUT). + */ + void cleanupCancelledAndStalledStreams(); + + /** + * Determine whether all non-intermittent streams are paused. An intermittent stream would be a persistent + * downchannel stream, for instace. + * @return True if all non-intermittent streams are paused, false otherwise. + */ + bool areStreamsPaused(); + + /** + * UnPause all the active streams. + */ + void unPauseActiveStreams(); + + /** + * Cancel a stream and report CANCELLED completion status. + * + * @param stream The stream to cancel. + * @return Whether the operation was successful. + */ + bool cancelStream(LibcurlHTTP2Request& stream); + + /** + * Release any streams that are active and report CANCELLED completion status. + */ + void cancelAllStreams(); + + /** + * Take the next request from the queue; + * + * @return next request, or nullPtr if queue is empty or the network loop is stopping. + */ + std::shared_ptr dequeueRequest(); + + /** + * Dequeue next request and add it to the multi-handle + */ + void processNextRequest(); + + /// Main thread for this class. + std::thread m_networkThread; + + /// Represents a CURL multi handle. Intended to only be accessed by the network loop thread. + std::unique_ptr m_multi; + + /// Serializes concurrent access to the m_requestQueue and m_isStopping members. + std::mutex m_mutex; + + /// Used to notify the network loop thread that there is at least one request queued or that the loop + /// has been instructed to stop. + std::condition_variable m_cv; + + /// The list of streams that either do not have HTTP response headers, or have outstanding response data. + /// Only accessed from the network loop thread. + std::map> m_activeStreams; + + /// Queue of requests send. Serialized by @c m_mutex. + std::deque> m_requestQueue; + + /// Set to true when we want to exit the network loop. + bool m_isStopping; +}; + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_LIBCURLHTTP2CONNECTION_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2ConnectionFactory.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2ConnectionFactory.h new file mode 100644 index 0000000000..3aac1bf1ac --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2ConnectionFactory.h @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_LIBCURLHTTP2CONNECTIONFACTORY_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_LIBCURLHTTP2CONNECTIONFACTORY_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +/** + * A class that produces @c LibcurlHTTP2Connection instances. + */ +class LibcurlHTTP2ConnectionFactory : public avsCommon::utils::http2::HTTP2ConnectionFactoryInterface { +public: + /// @name HTTP2ConnectionFactoryInterface methods. + /// @{ + std::shared_ptr createHTTP2Connection() override; + /// *} +}; + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_LIBCURLHTTP2CONNECTIONFACTORY_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Request.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Request.h new file mode 100644 index 0000000000..8ce7124fea --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Request.h @@ -0,0 +1,196 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_LIBCURLHTTP2REQUEST_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_LIBCURLHTTP2REQUEST_H_ + +#include +#include +#include + +#include +#include + +#include "CurlEasyHandleWrapper.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +class LibcurlHTTP2Request : public alexaClientSDK::avsCommon::utils::http2::HTTP2RequestInterface { +public: + /** + * Constructor. + * @param config + * @param id Name used to identify this request. + */ + LibcurlHTTP2Request(const alexaClientSDK::avsCommon::utils::http2::HTTP2RequestConfig& config, std::string id = ""); + + /// @name HTTP2RequestInterface methods. + /// @{ + bool cancel() override; + std::string getId() const override; + /// @} + + /** + * Gets the CURL easy handle associated with this stream + * + * @returns The associated curl easy handle for the stream + */ + CURL* getCurlHandle(); + + /** + * Notify the HTTP2ResponseSinkInterface that the transfer is complete with + * the appropriate HTTP2ResponseFinishedStatus code. + * + * @param status The finished status to report. + */ + void reportCompletion(http2::HTTP2ResponseFinishedStatus status); + + /** + * If the response code has been received and not previously reported, notify the + * HTTP2ResponseSinkInterface that the response code has been received. + */ + void reportResponseCode(); + + /** + * Return whether or not the activity timeout has been reached. + * + * @return Whether or not the activity timeout has been reached. + */ + bool hasProgressTimedOut() const; + + /** + * Whether this request expects that transfer will happen intermittently. + * + * @return Whether this request expects that transfer will happen intermittently. + */ + bool isIntermittentTransferExpected() const; + + /** + * Sets the time of last transfer to the current time. + */ + inline void setTimeOfLastTransfer(); + + /** + * Un-pause read and write for this request. + */ + void unPause(); + + /** + * Return whether this stream has pending transfers. + * + * @return whether this stream has been paused. + */ + bool isPaused() const; + + /** + * Return whether this request has been cancelled. + * + * @return whether this request has been cancelled. + */ + bool isCancelled() const; + +private: + /** + * Callback that gets executed when data is received. + * + * @see CurlEasyHandleWrapper::CurlCallback for details + * + * @param data Pointer to the received data. + * @param size Size of data members. + * @param nmemb Number of data members. + * @param userData Context passed back from @c libcurl. Should always be a pointer to @c LibcurlHTTP2Request. + * @return @c size if the operation was successful and @c size bytes were received, @c CURL_WRITEFUNC_PAUSE + * if the bytes could not be processed during the call and @c libcurl should call back later to try again, + * or some other value (e.g. 0), to abort the request. + */ + static size_t writeCallback(char* data, size_t size, size_t nmemb, void* userData); + + /** + * Callback that gets executed when HTTP headers are received. + * + * @see CurlEasyHandleWrapper::CurlCallback for details + * + * @param data Buffer containing header. Not null terminated. + * @param size Size of members in buffer. + * @param nmemb Number of members in buffer. + * @param userData Context passed back from @c libcurl. Should always be a pointer to @c LibcurlHTTP2Request. + * @return @c size if the operation was successful, or any other value to abort the request. + */ + static size_t headerCallback(char* data, size_t size, size_t nmemb, void* userData); + + /** + * Callback that gets executed to acquire data to send. + * + * @see CurlEasyHandleWrapper::CurlCallback for details + * + * @param data Buffer to receive data to send. + * @param size Size of members in buffer. + * @param nmemb Number of members in buffer. + * @param userData Context passed back from @c libcurl. Should always be a pointer to @c LibcurlHTTP2Request. + * @return Zero to indicate the end of the data to send, 1 to size * nmemb to indicate the number of bytes + * read in to @c data, or CURL_READFUNC_PAUSE to indicate that no data was read but @c libcurl should call + * back later to try again. + */ + static size_t readCallback(char* data, size_t size, size_t nmemb, void* userData); + + /** + * Returns the HTTP response code to this request. + * + * @returns The HTTP response code if one has been received, 0 if not, and < 0 if there is an error + */ + long getResponseCode(); + + /// Provides request headers and body + std::shared_ptr m_source; + + /// Receives responses + std::shared_ptr m_sink; + + /// Initially false; set to true after the response code has been reported. + bool m_responseCodeReported; + + /// Max time the stream may make no progress before @c hasProgressTimedOut() returns true. + std::chrono::milliseconds m_activityTimeout; + + /// Last time something was transferred. + std::chrono::steady_clock::time_point m_timeOfLastTransfer; + + /// The underlying curl easy handle. + CurlEasyHandleWrapper m_stream; + + /// Whether this request expects that transfer will happen intermittently. + /// If true, the transfer thread may be put to sleep even when this request isn't paused. + bool m_isIntermittentTransferExpected; + + /// Whether this stream has any paused transfers. + bool m_isPaused; + + /// Whether this request has been cancelled. + std::atomic_bool m_isCancelled; +}; + +void LibcurlHTTP2Request::setTimeOfLastTransfer() { + m_timeOfLastTransfer = std::chrono::steady_clock::now(); +} + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LIBCURLUTILS_LIBCURLHTTP2REQUEST_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ConsoleLogger.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ConsoleLogger.h index 202d8aca74..6d65e3e318 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ConsoleLogger.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ConsoleLogger.h @@ -27,8 +27,13 @@ namespace logger { /** * A very simple (e.g. not asynchronous) @c Logger that logs to console. + * + * Inheriting @c std::ios_base::Init ensures that the standard iostreams objects are properly initialized before @c + * ConsoleLogger uses them. */ -class ConsoleLogger : public Logger { +class ConsoleLogger + : public Logger + , private std::ios_base::Init { public: /** * Return the one and only @c ConsoleLogger instance. diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h index 2618e6c52c..d9afd8fed2 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h @@ -18,6 +18,7 @@ #include #include + #include "AVSCommon/Utils/Logger/LogEntryStream.h" namespace alexaClientSDK { @@ -30,6 +31,7 @@ class LogEntry { public: /** * Constructor. + * * @param source The name of the source of this log entry. * @param event The name of the event that this log entry describes. */ @@ -37,11 +39,30 @@ class LogEntry { /** * Constructor. + * * @param source The name of the source of this log entry. * @param event The name of the event that this log entry describes. */ LogEntry(const std::string& source, const std::string& event); + /** + * Add a @c key, @c value pair to the metadata of this log entry. + * + * @param key The key identifying the value to add to this LogEntry. + * @param value The value to add to this LogEntry. + * @return This instance to facilitate adding more information to this log entry. + */ + LogEntry& d(const std::string& key, const char* value); + + /** + * Add a @c key, @c value pair to the metadata of this log entry. + * + * @param key The key identifying the value to add to this LogEntry. + * @param value The value to add to this LogEntry. + * @return This instance to facilitate adding more information to this log entry. + */ + LogEntry& d(const char* key, char* value); + /** * Add a @c key, @c value pair to the metadata of this log entry. * @param key The key identifying the value to add to this LogEntry. @@ -52,6 +73,16 @@ class LogEntry { /** * Add data (hence the name 'd') in the form of a @c key, @c value pair to the metadata of this log entry. + * + * @param key The key identifying the value to add to this LogEntry. + * @param value The value to add to this LogEntry. + * @return This instance to facilitate adding more information to this log entry. + */ + LogEntry& d(const std::string& key, const std::string& value); + + /** + * Add data (hence the name 'd') in the form of a @c key, @c value pair to the metadata of this log entry. + * * @param key The key identifying the value to add to this LogEntry. * @param value The value to add to this LogEntry. * @return This instance to facilitate adding more information to this log entry. @@ -60,6 +91,16 @@ class LogEntry { /** * Add data (hence the name 'd') in the form of a @c key, @c value pair to the metadata of this log entry. + * + * @param key The key identifying the value to add to this LogEntry. + * @param value The boolean value to add to this LogEntry. + * @return This instance to facilitate adding more information to this log entry. + */ + LogEntry& d(const std::string& key, bool value); + + /** + * Add data (hence the name 'd') in the form of a @c key, @c value pair to the metadata of this log entry. + * * @param key The key identifying the value to add to this LogEntry. * @param value The boolean value to add to this LogEntry. * @return This instance to facilitate adding more information to this log entry. @@ -68,6 +109,7 @@ class LogEntry { /** * Add data (hence the name 'd') in the form of a @c key, @c value pair to the metadata of this log entry. + * * @param key The key identifying the value to add to this LogEntry. * @param value The value to add to this LogEntry. * @return This instance to facilitate adding more information to this log entry. @@ -89,6 +131,7 @@ class LogEntry { /** * Add an arbitrary message to the end of the text of this LogEntry. Once this has been called no other * additions should be made to this LogEntry. + * * @param message The message to add to the end of the text of this LogEntry. * @return This instance to facilitate passing this instance on. */ @@ -97,6 +140,7 @@ class LogEntry { /** * Add an arbitrary message to the end of the text of this LogEntry. Once this has been called no other * additions should be made to this LogEntry. + * * @param message The message to add to the end of the text of this LogEntry. * @return This instance to facilitate passing this instance on. */ @@ -104,6 +148,7 @@ class LogEntry { /** * Get the rendered text of this LogEntry. + * * @return The rendered text of this LogEntry. The returned buffer is only guaranteed to be valid for * the lifetime of this LogEntry, and only as long as no further modifications are made to it. */ @@ -121,6 +166,7 @@ class LogEntry { * Our metadata and subsequent optional message is of the form: * =[,=]:[] * ...so we need to reserve ',', '=' and ':'. We escape those vales with '\' so we escape '\' as well. + * * @param in The string to escape and append. */ void appendEscapedString(const char* in); diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogLevelObserverInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogLevelObserverInterface.h index 74ae2252e3..f528d6f044 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogLevelObserverInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogLevelObserverInterface.h @@ -35,6 +35,11 @@ class LogLevelObserverInterface { * @param status The updated logLevel */ virtual void onLogLevelChanged(Level level) = 0; + + /** + * Destructor. + */ + virtual ~LogLevelObserverInterface() = default; }; } // namespace logger diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Logger.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Logger.h index fd24bfc068..5128f76c8e 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Logger.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Logger.h @@ -88,7 +88,7 @@ namespace logger { * static const std::string TAG = "MyClass"; * #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) * - * When an event is to be logged, a wrapper macro named @c ACDK_ is invoked with an expression that starts + * When an event is to be logged, a wrapper macro named @c ACSDK_ is invoked with an expression that starts * with an invocation of the @c LX macro. The value of is the name of the @c LogLevel value to associate * with the @c LogEntry. Here is an example of a very simple log line that logs a "somethingHappened" event from * the source @c TAG with a severity level of @c INFO. @@ -182,6 +182,19 @@ class Logger { */ void log(Level level, const LogEntry& entry); + /** + * Send a log entry to this Logger while program is exiting. + * + * Use this method if the code may be run while destroying a static object. This method should not rely in any + * other static object. + * + * @note The user code should still ensure that the Logger object itself is valid. + * + * @param level The severity Level to associate with this log entry. + * @param entry Object used to build the text of this log entry. + */ + void logAtExit(Level level, const LogEntry& entry); + /** * Emit a log entry. * NOTE: This method must be thread-safe. diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/SinkObserverInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/SinkObserverInterface.h index 4273370904..62668a7fd4 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/SinkObserverInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/SinkObserverInterface.h @@ -37,6 +37,11 @@ class SinkObserverInterface { * @param sink The updated sink @c Logger */ virtual void onSinkChanged(const std::shared_ptr& sink) = 0; + + /** + * Destructor. + */ + virtual ~SinkObserverInterface() = default; }; } // namespace logger diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ThreadMoniker.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ThreadMoniker.h index c009645f56..672ae675e2 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ThreadMoniker.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ThreadMoniker.h @@ -16,7 +16,10 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LOGGER_THREADMONIKER_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LOGGER_THREADMONIKER_H_ +#include #include +#include +#include namespace alexaClientSDK { namespace avsCommon { @@ -25,33 +28,79 @@ namespace logger { /** * Class to provide @c std::this_thread access to unique name for itself. + * * The name ThreadMoniker is used instead of ThreadId to avoid confusion with platform specific thread identifiers * or the @c std::thread::id values rendered as a string. */ class ThreadMoniker { public: /** - * Constructor. + * Get the moniker for @c std::this_thread. + * + * @return The moniker for @c std::this_thread. */ - ThreadMoniker(); + static inline std::string getThisThreadMoniker(); /** - * Get the moniker for @c std::this_thread. + * Generate a unique moniker. * - * @return The moniker for @c std::this_thread. + * @return A new unique moniker. */ - static inline const std::string& getThisThreadMoniker(); + static std::string generateMoniker(); + + /** + * Set the moniker for @c std::this_thread. This method should be called before @c getThisThreadMoniker() in order + * to take effect. + * + * @param moniker The moniker for @c std::this_thread. + */ + static inline void setThisThreadMoniker(const std::string& moniker); private: + /** + * Constructor. + * + * @param moniker Optional moniker for this thread. If no moniker is provided, a new moniker will be provided. + */ + ThreadMoniker(const std::string& moniker = std::string()); + + /** + * Return the @c ThreadMoniker object for the current thread. + * + * @param moniker Use this moniker to initialize the @c ThreadMoniker if it doesn't exist already. + * @return The moniker for the @c std::this_thread. + */ + static inline const ThreadMoniker& getMonikerObject(const std::string& moniker = std::string()); + + /** + * Return the @c ThreadMoniker object for the current thread for OS that don't support thread local variables. + * + * @param moniker Use this moniker to initialize the @c ThreadMoniker if it doesn't exist already. + * @return The moniker for the @c std::this_thread. + */ + static const ThreadMoniker& getMonikerObjectFromMap(const std::string& moniker = std::string()); + /// The current thread's moniker. std::string m_moniker; +}; + +std::string ThreadMoniker::getThisThreadMoniker() { + return getMonikerObject().m_moniker; +} +void ThreadMoniker::setThisThreadMoniker(const std::string& moniker) { + getMonikerObject(moniker); +} + +const ThreadMoniker& ThreadMoniker::getMonikerObject(const std::string& moniker) { +#ifdef _WIN32 + return getMonikerObjectFromMap(moniker); +#else /// Per-thread static instance so that @c m_threadMoniker.m_moniker is @c std::this_thread's moniker. - static thread_local ThreadMoniker m_threadMoniker; -}; + static thread_local ThreadMoniker m_threadMoniker{moniker}; -const std::string& ThreadMoniker::getThisThreadMoniker() { - return m_threadMoniker.m_moniker; + return m_threadMoniker; +#endif } } // namespace logger diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MacAddressString.h b/AVSCommon/Utils/include/AVSCommon/Utils/MacAddressString.h new file mode 100644 index 0000000000..51b9dd303a --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MacAddressString.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MACADDRESSSTRING_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MACADDRESSSTRING_H_ + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { + +/** + * A class used to validate a MAC address string before construction. + */ +class MacAddressString { +public: + /// Default copy-constructor so objects can be passed by value. + MacAddressString(const MacAddressString&) = default; + + /** + * Factory that validates the MAC address before constructing the actual object. + * + * @params macAddress user supplied MacAddress + * @return nullptr if the input MAC address is illegal, otherwise a unique_ptr to a MacAddressString object that can + * be used to get the desired string. + */ + static std::unique_ptr create(const std::string& macAddress); + + std::string getString() const; + +private: + /// The constructor will only be called with a legal macAddress input. We don't check here because this function is + /// private and is only called from the public create(...) factory method. + MacAddressString(const std::string& macAddress); + + /// a well formed MAC address string + const std::string m_macAddress; +}; + +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MACADDRESSSTRING_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h index 1179d93c76..5be5b5d9cd 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -39,18 +39,17 @@ class MediaPlayerObserverInterface; * A @c MediaPlayerInterface allows for sourcing, playback control, navigation, and querying the state of media content. * A @c MediaPlayerInterface implementation must only handle one source at a time. * - * Each playback controlling API call (i.e. @c play(), @c pause(), @c stop(), @c resume()) which returns @c true will - * result in a callback to the observer. If @c false is returned from these calls, no callback will occur. Callbacks - * should not be made on the caller's thread prior to returning from a call. + * Each playback controlling API call (i.e. @c play(), @c pause(), @c stop(), @c resume()) that succeeds will also + * result in a callback to the observer. To see how to tell when a method succeeded, please refer to the documentation + * of each method. * * An implementation can call @c onPlaybackError() at any time. If an @c onPlaybackError() callback occurs while a * plaback controlling API call is waiting for a callback, the original callback must not be made, and the - * implementation should rever to a stopped state. Any subsequent operations after an @c onPlaybackError() callback + * implementation should revert to a stopped state. Any subsequent operations after an @c onPlaybackError() callback * must be preceded by a new @c setSource() call. * * Implementations must make a call to @c onPlaybackStopped() with the previous @c SourceId when a new source is - * set if the previous source was in a non-stopped state. Any calls to a @c MediaPlayerInterface after an @c - * onPlaybackStopped() call will fail, as the MediaPlayer has "reset" its state. + * set if the previous source was in a non-stopped state. * * @c note A @c MediaPlayerInterface implementation must be able to support the various audio formats listed at: * https://developer.amazon.com/docs/alexa-voice-service/recommended-media-support.html. @@ -93,13 +92,15 @@ class MediaPlayerInterface { * * @param url The url to set as the source. * @param offset An optional offset parameter to start playing from when a @c play() call is made. + * @param repeat An optional parameter to play the url source in a loop. * * @return The @c SourceId that represents the source being handled as a result of this call. @c ERROR will be * returned if the source failed to be set. */ virtual SourceId setSource( const std::string& url, - std::chrono::milliseconds offset = std::chrono::milliseconds::zero()) = 0; + std::chrono::milliseconds offset = std::chrono::milliseconds::zero(), + bool repeat = false) = 0; /** * Set an @c istream source to play. The source should be set before making calls to any of the playback control diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h index cbfd3f5aac..45a3349b3a 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -33,6 +33,14 @@ namespace mediaPlayer { * A player observer will receive notifications when the player starts playing or when it stops playing a stream. * A pointer to the @c MediaPlayerObserverInterface needs to be provided to a @c MediaPlayer for it to notify the * observer. + * + * @warning An observer should never call a method from the observed media player while handling a callback. + * This may cause a deadlock while trying to re-acquire a mutex. + * + * @warning Be aware that there is a high risk of deadlock if the observer calls media player functions while holding + * an exclusive lock. The deadlock may happen because the call to media player functions may end up calling the same + * observer which will try to acquire the same lock that it already has. One way to avoid this issue is by using a + * recursive lock. */ class MediaPlayerObserverInterface { public: diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Network/InternetConnectionMonitor.h b/AVSCommon/Utils/include/AVSCommon/Utils/Network/InternetConnectionMonitor.h new file mode 100644 index 0000000000..416505a886 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Network/InternetConnectionMonitor.h @@ -0,0 +1,132 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_NETWORK_INTERNETCONNECTIONMONITOR_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_NETWORK_INTERNETCONNECTIONMONITOR_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace network { + +/** + * A class to monitor internet connection and notify observers of connection status changes. + */ +class InternetConnectionMonitor : public sdkInterfaces::InternetConnectionMonitorInterface { +public: + /** + * Creates a InternetConnectionMonitor. + * + * @param contentFetcherFactory The content fetcher that will make the test run to an S3 endpoint. + * @return A unique_ptr to the InternetConnectionMonitor instance. + */ + static std::unique_ptr create( + std::shared_ptr contentFetcherFactory); + + /** + * Destructor. + */ + ~InternetConnectionMonitor(); + + /// @name InternetConnectionMonitorInterface Methods + /// @{ + void addInternetConnectionObserver( + std::shared_ptr observer) override; + void removeInternetConnectionObserver( + std::shared_ptr observer) override; + /// @} + +private: + /** + * Constructor. + * + * @param contentFetcherFactory The content fetcher that will make the test run to an S3 endpoint. + */ + InternetConnectionMonitor( + std::shared_ptr contentFetcherFactory); + + /** + * Begin monitoring internet connection. + */ + void startMonitoring(); + + /** + * Stop monitoring internet connection. + */ + void stopMonitoring(); + + /** + * Test internet connection by connecting to an S3 endpoint and fetching HTTP content. + * The HTTP content is scanned for a validation string. + * + * @note The URL tested is http://spectrum.s3.amazonaws.com/kindle-wifi/wifistub.html, the Kindle reachability probe + * page. + */ + void testConnection(); + + /** + * Update the connection status. + * + * @param connected The new connection status. + */ + void updateConnectionStatus(bool connected); + + /** + * Notify observers of connection status. + * + * @note This should only be called while holding a lock to ensure synchronicity. + */ + void notifyObserversLocked(); + + /// The set of connection observers. + std::unordered_set> m_observers; + + /// The current internet connection status. + bool m_connected; + + /// The period (in seconds) after which the monitor should re-test internet connection. + std::chrono::seconds m_period; + + /// The timer that will call testConnection() every m_period seconds. + avsCommon::utils::timing::Timer m_connectionTestTimer; + + /// A flag to tell the HTTP content fetcher that it is time to shutdown. + std::atomic m_isShuttingDown; + + /// The content fetcher factory that will produce a content fetcher. + std::shared_ptr m_contentFetcherFactory; + + /// Mutex to serialize access to m_connected and m_observers. + std::mutex m_mutex; +}; + +} // namespace network +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_NETWORK_INTERNETCONNECTIONMONITOR_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Optional.h b/AVSCommon/Utils/include/AVSCommon/Utils/Optional.h new file mode 100644 index 0000000000..7d13d31e66 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Optional.h @@ -0,0 +1,228 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_OPTIONAL_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_OPTIONAL_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { + +/** + * Auxiliary class that implements an optional object, where the value may or may not be present. + * + * @note @c ValueT MUST have a copy constructor. + * @note Method @c value() is available only for @c ValueT types that have a default constructor. + * @warning This class is not thread-safe. + */ +template +class Optional { +public: + /** + * Creates an optional object with no valid value. + */ + Optional(); + + /** + * Creates an optional object with a valid value. + * + * @param value Object used to initialize the new optional value. + * @note This method required @c ValueT to have a copy constructor is available. + */ + Optional(const ValueT& value); + + /** + * Copy constructor. + * + * @param other Object used to initialize the new optional object. + */ + Optional(const Optional& other); + + /** + * Sets optional value to the given ValueT. + * + * @param value Object that will be assigned to the optional value. + * @note ValueT MUST have an assignment operator defined. + */ + void set(const ValueT& value); + + /** + * Modifies the optional object so it no longer holds any valid value. + */ + void reset(); + + /** + * Checks whether the optional contains a value or not. + * + * @return true if it contains a value, false if it does not contain a value. + */ + bool hasValue() const; + + /** + * Gets the value if present or return other. @c ValueT must have a copy constructor. + * + * @param other Object that will be returned if this optional does not hold a value. + * @return This optional value if *this has a value; otherwise, return @c other. + */ + ValueT valueOr(const ValueT& other) const; + + /** + * Gets the value if present or return other. @c ValueT must have a default constructor. + * + * @return The object being held by this if @c m_object is valid; otherwise, return @c ValueT(). + */ + ValueT value() const; + + /** + * Optional destructor. + */ + ~Optional(); + + /** + * Assignment operator. + * + * @param rhs The optional object source of the assignment. + * @return A reference to @c *this. + */ + Optional& operator=(const Optional& rhs); + + /** + * Equality operator. + * + * @param rhs The object to compare *this against. + * @return @c true if both objects don't hold a value, and @c false if only one object holds a value. If both + * optionals hold valid values, return the result of operator== for their values. + */ + bool operator==(const Optional& rhs) const; + + /** + * Inequality operator. + * + * @param rhs The object to compare *this against. + * @return @c true if only one object holds a value, and @c false if both objects don't hold a value. If both + * optionals hold valid values, return the result of operator!= for their values. + */ + bool operator!=(const Optional& rhs) const; + +private: + /// Boolean flag indicating whether the value exists or not. + bool m_hasValue; + + /// Place holder for the actual value. + /// + /// @note We chose to use @c std::aligned_storage so we can control the underlying object lifecycle without the + /// burden of always using the heap. + typename std::aligned_storage::type m_value; +}; + +template +Optional::Optional() : m_hasValue{false} { +} + +template +Optional::Optional(const ValueT& other) : m_hasValue{true} { + // Create object in the allocated space. + new (&m_value) ValueT(other); +} + +template +Optional::Optional(const Optional& other) : m_hasValue{other.m_hasValue} { + if (hasValue()) { + new (&m_value) ValueT(*reinterpret_cast(&other.m_value)); + } +} + +template +void Optional::set(const ValueT& other) { + if (hasValue()) { + *reinterpret_cast(&m_value) = other; + } else { + m_hasValue = true; + new (&m_value) ValueT(other); + } +} + +template +void Optional::reset() { + if (hasValue()) { + m_hasValue = false; + reinterpret_cast(&m_value)->~ValueT(); + } +} + +template +bool Optional::hasValue() const { + return m_hasValue; +} + +template +ValueT Optional::value() const { + if (hasValue()) { + return *reinterpret_cast(&m_value); + } + logger::acsdkError(logger::LogEntry("Optional", "valueFailed").d("reason", "optionalHasNoValue")); + return ValueT(); +} + +template +ValueT Optional::valueOr(const ValueT& other) const { + if (hasValue()) { + return *reinterpret_cast(&m_value); + } + return other; +} + +template +Optional::~Optional() { + if (hasValue()) { + reinterpret_cast(&m_value)->~ValueT(); + } +} + +template +Optional& Optional::operator=(const Optional& rhs) { + if (hasValue()) { + if (rhs.hasValue()) { + *reinterpret_cast(&m_value) = rhs.value(); + } else { + m_hasValue = false; + reinterpret_cast(&m_value)->~ValueT(); + } + } else if (rhs.hasValue()) { + m_hasValue = true; + new (&m_value) ValueT(rhs.value()); + } + return *this; +} + +template +bool Optional::operator==(const Optional& rhs) const { + if (this->hasValue()) { + return rhs.hasValue() && (this->value() == rhs.value()); + } + return !rhs.hasValue(); +} + +template +bool Optional::operator!=(const Optional& rhs) const { + return !(*this == rhs); +} + +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_OPTIONAL_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/IterativePlaylistParserInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/IterativePlaylistParserInterface.h new file mode 100644 index 0000000000..5fae38a6f1 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/IterativePlaylistParserInterface.h @@ -0,0 +1,68 @@ +/* + * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLAYLISTPARSER_ITERATIVEPLAYLISTPARSERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLAYLISTPARSER_ITERATIVEPLAYLISTPARSERINTERFACE_H_ + +#include +#include +#include + +#include "PlaylistParserObserverInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace playlistParser { + +/** + * An interface that can be implemented to parse playlists in a depth first search manner. + */ +class IterativePlaylistParserInterface { +public: + /** + * Initialize the playlist parsing. Once succesfully initialized, the method @c next can be used to retrieve + * playlist entries. + * + * @param url The root url that can either point to one media file or a playlist to be parsed. + * @return @c true if it succeeds; false otherwise. + */ + virtual bool initializeParsing(std::string url) = 0; + + /** + * Get the next element in the playlist. + * + * @return The parsing result. The @c url field will be valid if @c parseResult is different than ERROR. + */ + virtual PlaylistEntry next() = 0; + + /** + * Abort the current playlist parsing by causing ongoing and future calls to @c next to fail. Calls to + * @c initializeParsing will reset @c abort. + */ + virtual void abort() = 0; + + /** + * Destructor. + */ + virtual ~IterativePlaylistParserInterface() = default; +}; + +} // namespace playlistParser +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLAYLISTPARSER_ITERATIVEPLAYLISTPARSERINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistEntry.h b/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistEntry.h new file mode 100644 index 0000000000..3840e34638 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistEntry.h @@ -0,0 +1,236 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLAYLISTPARSER_PLAYLISTENTRY_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLAYLISTPARSER_PLAYLISTENTRY_H_ + +#include +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace playlistParser { + +/// Alias for holding start and end offsets of byte range of an URL to download. +typedef std::tuple ByteRange; + +/** + * An enum class used to specify the result of a parsing operation. + */ +enum class PlaylistParseResult { + + /// The playlist has been fully parsed successfully. This indicates that parsing of the playlist has completed. + FINISHED, + + /** + * The playlist parsing has encountered an error and will abort parsing. In this case, the url in the callback will + * not be valid. + */ + ERROR, + + /// The playlist parsing is still ongoing. + STILL_ONGOING +}; + +/** + * Struct to hold encryption info: method, key URL and initialization vector. + */ +struct EncryptionInfo { + /** + * Encryption method. + */ + enum class Method { + /// No encryption. + NONE, + + /// AES-128 encryption method. + AES_128, + + /// SAMPLE-AES encryption method. + SAMPLE_AES + }; + + /** + * Default constructor. + */ + EncryptionInfo(); + + /** + * Constructor. + * + * @param method The encryption method of media. + * @param url The URL for the encryption key to download. + * @param initVector The intilization vector used for encryption. + */ + EncryptionInfo(Method method, std::string url, std::string initVector = std::string()); + + /** + * Helper method to check if EncryptionInfo is valid. + * + * @return @c true if encryption info is valid or @c false otherwise. + */ + bool isValid() const; + + /// Encryption method. + Method method; + + /// The URL of the encryption key. + std::string keyURL; + + /// The initilization vector used for encryption. + std::string initVector; +}; + +/** + * Struct to hold information about an entry parsed from playlist. + */ +struct PlaylistEntry { + /** + * Type of playlist entry. + */ + enum class Type { + /// Playlist Entry is about media. + MEDIA_INFO, + + /// Playlist Entry is about media initialization. + MEDIA_INIT_INFO, + + /// Playlist Entry is audio content, not a playlist. + AUDIO_CONTENT + }; + + /** + * Helper method to create ERROR PlaylistEntry + * + * @param url playlist URL + * @return PlaylistEntry with url, INVALID_DURATION and ERROR parseResult. + */ + static PlaylistEntry createErrorEntry(const std::string& url); + + /** + * Helper method to create MEDIA_INIT_INFO PlaylistEntry. + * + * @param url playlist URL + * @param byteRange The byte range of the MEDIA_INIT_INFO to download. + * @return PlaylistEntry with url, INVALID_DURATION and STILL_ONGOING parseResult. + */ + static PlaylistEntry createMediaInitInfo(std::string url, ByteRange byteRange = std::make_tuple(0, 0)); + + /** + * Constructor. + * + * @param _url The URL of the playlist entry media. + * @param _duration The duration of media in milliseconds. + * @param _parseResult The @c PlaylistParseResult. + * @param _type The Type of the entry. + * @param _byteRange The byte range of the url to download. Default is (0, 0). + * @param _encryptionInfo The encryption info of the media. Default value is NONE. + * @param _contentFetcher Content fetcher related to the entry. + */ + PlaylistEntry( + std::string _url, + std::chrono::milliseconds _duration, + avsCommon::utils::playlistParser::PlaylistParseResult _parseResult, + Type _type = Type::MEDIA_INFO, + ByteRange _byteRange = std::make_tuple(0, 0), + EncryptionInfo _encryptionInfo = EncryptionInfo(), + std::shared_ptr _contentFetcher = nullptr); + + /** + * Returns true if byterange is valid. + */ + bool hasValidByteRange() const; + + /// Type of playlist entry. + Type type; + + /// The URL for the entry. + std::string url; + + /// The duration of the content if its known; INVALID_DURATION otherwise. + std::chrono::milliseconds duration; + + /// The latest parsing result. + PlaylistParseResult parseResult; + + /// ByteRange to download. + ByteRange byteRange; + + /// EncryptionInfo of the media. + EncryptionInfo encryptionInfo; + + /// The content fetcher associated with this playlist item. If a content fetcher is set, then it should be + /// considered to be safe to use it. Otherwise, a new content fetcher should be created. + std::shared_ptr contentFetcher; +}; + +inline PlaylistEntry PlaylistEntry::createErrorEntry(const std::string& url) { + return PlaylistEntry( + url, std::chrono::milliseconds(-1), PlaylistParseResult::ERROR, Type::MEDIA_INFO, std::make_tuple(0, 0)); +} + +inline PlaylistEntry PlaylistEntry::createMediaInitInfo(std::string url, ByteRange byteRange) { + auto duration = std::chrono::milliseconds(-1); + return PlaylistEntry(url, duration, PlaylistParseResult::STILL_ONGOING, Type::MEDIA_INIT_INFO, byteRange); +} + +inline EncryptionInfo::EncryptionInfo() : method(Method::NONE) { +} + +inline EncryptionInfo::EncryptionInfo(Method method, std::string url, std::string initVector) : + method(method), + keyURL(url), + initVector(initVector) { +} + +inline bool EncryptionInfo::isValid() const { + return (Method::NONE == method) || (Method::NONE != method && !keyURL.empty() && !initVector.empty()); +} + +inline bool PlaylistEntry::hasValidByteRange() const { + long start, end; + std::tie(start, end) = byteRange; + return start >= 0 && end > 0; +} + +inline PlaylistEntry::PlaylistEntry( + std::string _url, + std::chrono::milliseconds _duration, + avsCommon::utils::playlistParser::PlaylistParseResult _parseResult, + Type _type, + ByteRange _byteRange, + EncryptionInfo _encryptionInfo, + std::shared_ptr _contentFetcher) : + type(_type), + url(_url), + duration(_duration), + parseResult(_parseResult), + byteRange(_byteRange), + encryptionInfo(_encryptionInfo), + contentFetcher(_contentFetcher) { +} + +} // namespace playlistParser +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLAYLISTPARSER_PLAYLISTENTRY_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistParserInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistParserInterface.h index 6fc6be90f2..5c350474e6 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistParserInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistParserInterface.h @@ -36,7 +36,7 @@ class PlaylistParserInterface { enum class PlaylistType { M3U, - M3U8, + EXT_M3U, PLS }; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistParserObserverInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistParserObserverInterface.h index be057bde7e..3049ab9652 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistParserObserverInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/PlaylistParser/PlaylistParserObserverInterface.h @@ -21,37 +21,18 @@ #include #include +#include "PlaylistEntry.h" + namespace alexaClientSDK { namespace avsCommon { namespace utils { namespace playlistParser { -/** - * An enum class used to specify the result of a parsing operation. - */ -enum class PlaylistParseResult { - - /// The playlist has been fully parsed successfully. This indicates that parsing of the playlist has completed. - SUCCESS, - - /** - * The playlist parsing has encountered an error and will abort parsing. In this case, the url in the callback will - * not be valid. - */ - ERROR, - - /// The playlist parsing is still ongoing. - STILL_ONGOING -}; - /** * An observer of the playlist parser. */ class PlaylistParserObserverInterface { public: - /// An invalid duration. - static constexpr std::chrono::milliseconds INVALID_DURATION = std::chrono::milliseconds(-1); - /** * Destructor. */ @@ -61,18 +42,12 @@ class PlaylistParserObserverInterface { * Notification that an entry has been parsed. * * @param requestId The id of the callback to connect this callback to an original request. - * @param url An entry that has been extracted. - * @param parseResult The result of parsing the playlist. - * @param duration A duration in milliseconds of the playlist entry, if it was able to be deduced based on - * available playlist metadata. + * @param playlistEntry The parsing result. The @c url field will be valid if @c parseResult is different than + * ERROR. * * @note This function is always called from a single thread in PlayListParser. */ - virtual void onPlaylistEntryParsed( - int requestId, - std::string url, - PlaylistParseResult parseResult, - std::chrono::milliseconds duration = INVALID_DURATION) = 0; + virtual void onPlaylistEntryParsed(int requestId, PlaylistEntry playlistEntry) = 0; }; /** @@ -84,8 +59,8 @@ class PlaylistParserObserverInterface { */ inline std::ostream& operator<<(std::ostream& stream, const PlaylistParseResult& result) { switch (result) { - case PlaylistParseResult::SUCCESS: - stream << "SUCCESS"; + case PlaylistParseResult::FINISHED: + stream << "FINISHED"; break; case PlaylistParseResult::ERROR: stream << "ERROR"; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/PromiseFuturePair.h b/AVSCommon/Utils/include/AVSCommon/Utils/PromiseFuturePair.h new file mode 100644 index 0000000000..ccd2797951 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/PromiseFuturePair.h @@ -0,0 +1,112 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PROMISEFUTUREPAIR_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PROMISEFUTUREPAIR_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { + +/** + * Template to pair a promise and it's future that can store a value. + */ +template +class PromiseFuturePair { +public: + /** + * Constructor + */ + PromiseFuturePair() : m_future{m_promise.get_future()} { + } + + /** + * Set the value in promise. + * @param val The value to be set. + */ + void setValue(Type val) { + m_promise.set_value(val); + } + + /** + * Wait for promise to be set. + * @param timeout Timeout for waiting for promise to be set. + * @return True if promise has been set before timeout, otherwise false. + */ + bool waitFor(std::chrono::milliseconds timeout) { + auto future = m_future; + return future.wait_for(timeout) == std::future_status::ready; + } + + /** + * Retrieved the promised value. + */ + Type getValue() { + auto future = m_future; + return future.get(); + } + +private: + /// The promise that will be set later asynchronously with a value. + std::promise m_promise; + + /// The future object based from the @c m_promise. + std::shared_future m_future; +}; + +/** + * Template to pair a promise and it's future with Void values. + */ +template <> +class PromiseFuturePair { +public: + /** + * Constructor + */ + PromiseFuturePair() : m_future{m_promise.get_future()} { + } + + /** + * Set the value in promise. + */ + void setValue() { + m_promise.set_value(); + } + + /** + * Wait for promise to be set. + * @param timeout Timeout for waiting for promise to be set. + * @return True if promise has been set before timeout, otherwise false. + */ + bool waitFor(std::chrono::milliseconds timeout) { + auto future = m_future; + return future.wait_for(timeout) == std::future_status::ready; + } + +private: + /// The promise that will be set later asynchronously. + std::promise m_promise; + + /// The future object based from the @c m_promise. + std::shared_future m_future; +}; + +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PROMISEFUTUREPAIR_H_ \ No newline at end of file diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h b/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h index 0840b16617..4265b136ae 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h @@ -30,15 +30,15 @@ namespace utils { namespace sdkVersion{ inline static std::string getCurrentVersion(){ - return "0.0.0"; + return "1.13.0"; } inline static int getMajorVersion(){ - return 0; + return 1; } inline static int getMinorVersion(){ - return 0; + return 13; } inline static int getPatchVersion(){ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Writer.h b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Writer.h index ce4c8760b0..e8820f6b2a 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Writer.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Writer.h @@ -284,11 +284,11 @@ ssize_t SharedDataStream::Writer::write(const void* buf, size_t nWords, std:: // continuously, so a missed notification is not significant. // Note: As a further optimization, the lock could be omitted if no blocking readers are in use (ACSDK-251). std::unique_lock dataAvailableLock(header->dataAvailableMutex, std::defer_lock); - if (Policy::NONBLOCKABLE == m_policy) { + if (Policy::NONBLOCKABLE != m_policy) { dataAvailableLock.lock(); } header->writeStartCursor = header->writeEndCursor.load(); - if (Policy::NONBLOCKABLE == m_policy) { + if (Policy::NONBLOCKABLE != m_policy) { dataAvailableLock.unlock(); } diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Stream/Streambuf.h b/AVSCommon/Utils/include/AVSCommon/Utils/Stream/Streambuf.h index 0bdcd4c749..388a2ce44e 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Stream/Streambuf.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Stream/Streambuf.h @@ -44,20 +44,17 @@ class Streambuf : public std::streambuf { std::streampos seekpos(std::streampos sp, std::ios_base::openmode which = std::ios_base::in) override; private: + /// @name @c std::streambuf method overrides + /// @{ int_type underflow() override; - int_type uflow() override; int_type pbackfail(int_type ch) override; std::streamsize showmanyc() override; + /// @} - /** - * This function makes sure that the requested operation is valid and does the update. - * - * @return the position in the stream - */ - std::streampos UpdateAndValidate(); - + /// The start of the buffer to stream. char* const m_begin; - char* m_current; + + /// The end (one byte past the last byte) of the buffer to stream. char* const m_end; }; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/String/StringUtils.h b/AVSCommon/Utils/include/AVSCommon/Utils/String/StringUtils.h index 721befb1a4..b380a5e920 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/String/StringUtils.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/String/StringUtils.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -44,6 +44,26 @@ bool stringToInt(const std::string& str, int* result); */ bool stringToInt(const char* str, int* result); +/** + * A utility function to convert a string to an integer 64 bits. + * If the string is successfully parsed, then the out parameter will be updated. + * + * @param str The string input. + * @param[out] result The resulting integer, if successfully parsed from the string. + * @return @c true If the string was parsed as an integer, otherwise @c false. + */ +bool stringToInt64(const std::string& str, int64_t* result); + +/** + * A utility function to convert a c-string to an integer 64 bits. + * If the string is successfully parsed, then the out parameter will be updated. + * + * @param str The C-string input. + * @param[out] result The resulting integer, if successfully parsed from the string. + * @return @c true If the string was parsed as an integer, otherwise @c false. + */ +bool stringToInt64(const char* str, int64_t* result); + /** * A utility function to convert a vector of bytes to a printable string. For example, the vector {1, 2, 3} will return * the string "0x01 0x02 0x03" @@ -61,6 +81,24 @@ std::string byteVectorToString(const std::vector& byteVector); */ std::string stringToLowerCase(const std::string& input); +/** + * A utility function to convert a string into upper case. + * + * @param input The input string to be converted. + * @return The converted string in upper case. + */ +std::string stringToUpperCase(const std::string& input); + +/** + * Replaces all occurrences of a substring with another string. + * + * @param str The reference string. + * @param from The string to find. + * @param to The replacement string. + * @return A new string with the replaced substrings. + */ +std::string replaceAllSubstring(const std::string& str, const std::string& from, const std::string& to); + } // namespace string } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/Executor.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/Executor.h index ea796a694a..8bc81b435e 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/Executor.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/Executor.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -16,11 +16,17 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_EXECUTOR_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_EXECUTOR_H_ +#include +#include +#include +#include +#include #include +#include +#include #include #include "AVSCommon/Utils/Threading/TaskThread.h" -#include "AVSCommon/Utils/Threading/TaskQueue.h" namespace alexaClientSDK { namespace avsCommon { @@ -34,8 +40,11 @@ class Executor { public: /** * Constructs an Executor. + * + * @param delayExit The period of time that this executor will keep its thread running while waiting + * for a new job. We use 1s by default. */ - Executor(); + Executor(const std::chrono::milliseconds& delayExit = std::chrono::milliseconds(1000)); /** * Destructs an Executor. @@ -66,7 +75,7 @@ class Executor { auto submitToFront(Task task, Args&&... args) -> std::future; /** - * Wait for any previously submitted tasks to complete. + * Waits for any previously submitted tasks to complete. */ void waitForSubmittedTasks(); @@ -77,21 +86,162 @@ class Executor { bool isShutdown(); private: - /// The queue of tasks to execute. - std::shared_ptr m_taskQueue; + /// The queue type to use for holding tasks. + using Queue = std::deque>; + + /** + * Executes the next job in the queue. + * + * @return @c true if there's a next job; @c false if the job queue is empty. + */ + bool runNext(); + + /** + * Checks if the job queue is empty and that no job is added in the grace period determined by @c m_timeout. + * + * @return @c true if there's at least one job left in the queue; @c false if the job queue is empty. + */ + bool hasNext(); + + /** + * Returns and removes the task at the front of the queue. If there are no tasks, this call will return an empty + * function. + * + * @returns A function that represents a new task. The function will be empty if the queue has no job. + */ + std::function pop(); + + /** + * Pushes a task on the the queue. If the queue is shutdown, the task will be dropped, and an invalid + * future will be returned. + * + * @param front If @c true, push to the front of the queue, else push to the back. + * @param task A task to push to the front or back of the queue. + * @param args The arguments to call the task with. + * @returns A @c std::future to access the return value of the task. If the queue is shutdown, the task will be + * dropped, and an invalid future will be returned. + */ + template + auto pushTo(bool front, Task task, Args&&... args) -> std::future; + + /// The queue of tasks + Queue m_queue; + + /// Flag to indicate if the taskThread already have an executing job. + bool m_threadRunning; + + /// Period that this queue will wait for a new job until it releases the task thread. + std::chrono::milliseconds m_timeout; + + /// A mutex to protect access to the tasks in m_queue. + std::mutex m_queueMutex; + + /// A flag for whether or not the queue is expecting more tasks. + std::atomic_bool m_shutdown; + + /// The condition variable used to detect new job or timeout. + std::condition_variable m_delayedCondition; /// The thread to execute tasks on. The thread must be declared last to be destructed first. - std::unique_ptr m_taskThread; + TaskThread m_taskThread; }; template auto Executor::submit(Task task, Args&&... args) -> std::future { - return m_taskQueue->push(task, std::forward(args)...); + bool front = false; + return pushTo(front, std::forward(task), std::forward(args)...); } template auto Executor::submitToFront(Task task, Args&&... args) -> std::future { - return m_taskQueue->pushToFront(task, std::forward(args)...); + bool front = true; + return pushTo(front, std::forward(task), std::forward(args)...); +} + +/** + * Utility function which waits for a @c std::future to be fulfilled and forward the result to a @c std::promise. + * + * @param promise The @c std::promise to fulfill when @c future is fulfilled. + * @param future The @c std::future on which to wait for a result to forward to @c promise. + */ +template +inline static void forwardPromise(std::shared_ptr> promise, std::future* future) { + promise->set_value(future->get()); +} + +/** + * Specialization of @c forwardPromise() for @c void types. + * + * @param promise The @c std::promise to fulfill when @c future is fulfilled. + * @param future The @c std::future on which to wait before fulfilling @c promise. + */ +template <> +inline void forwardPromise(std::shared_ptr> promise, std::future* future) { + future->get(); + promise->set_value(); +} + +template +auto Executor::pushTo(bool front, Task task, Args&&... args) -> std::future { + // Remove arguments from the tasks type by binding the arguments to the task. + auto boundTask = std::bind(std::forward(task), std::forward(args)...); + + /* + * Create a std::packaged_task with the correct return type. The decltype only returns the return value of the + * boundTask. The following parentheses make it a function call with the boundTask return type. The package task + * will then return a future of the correct type. + * + * Note: A std::packaged_task fulfills its future *during* the call to operator(). If the user of a + * std::packaged_task hands it off to another thread to execute, and then waits on the future, they will be able to + * retrieve the return value from the task and know that the task has executed, but they do not know exactly when + * the task object has been deleted. This distinction can be significant if the packaged task is holding onto + * resources that need to be freed (through a std::shared_ptr for example). If the user needs to wait for those + * resources to be freed they have no way of knowing how long to wait. The translated_task lambda below is a + * workaround for this limitation. It executes the packaged task, then disposes of it before passing the task's + * return value back to the future that the user is waiting on. + */ + using PackagedTaskType = std::packaged_task; + auto packaged_task = std::make_shared(boundTask); + + // Create a promise/future that we will fulfill when we have cleaned up the task. + auto cleanupPromise = std::make_shared>(); + auto cleanupFuture = cleanupPromise->get_future(); + + // Remove the return type from the task by wrapping it in a lambda with no return value. + auto translated_task = [packaged_task, cleanupPromise]() mutable { + // Execute the task. + packaged_task->operator()(); + // Note the future for the task's result. + auto taskFuture = packaged_task->get_future(); + // Clean up the task. + packaged_task.reset(); + // Forward the task's result to our cleanup promise/future. + forwardPromise(cleanupPromise, &taskFuture); + }; + + // Release our local reference to packaged task so that the only remaining reference is inside the lambda. + packaged_task.reset(); + + { + bool restart = false; + std::lock_guard queueLock{m_queueMutex}; + if (!m_shutdown) { + restart = !m_threadRunning; + m_queue.emplace(front ? m_queue.begin() : m_queue.end(), std::move(translated_task)); + } else { + using FutureType = decltype(task(args...)); + return std::future(); + } + + if (restart) { + // Restart task thread. + m_taskThread.start(std::bind(&Executor::runNext, this)); + m_threadRunning = true; + } + } + + m_delayedCondition.notify_one(); + return cleanupFuture; } } // namespace threading diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskQueue.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskQueue.h deleted file mode 100644 index 85bcb82252..0000000000 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskQueue.h +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_TASKQUEUE_H_ -#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_TASKQUEUE_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace alexaClientSDK { -namespace avsCommon { -namespace utils { -namespace threading { - -/** - * A TaskQueue contains a queue of tasks to run - */ -class TaskQueue { -public: - /** - * Constructs an empty TaskQueue. - */ - TaskQueue(); - - /** - * Pushes a task on the back of the queue. If the queue is shutdown, the task will be dropped, and an invalid - * future will be returned. - * - * @param task A task to push to the back of the queue. - * @param args The arguments to call the task with. - * @returns A @c std::future to access the return value of the task. If the queue is shutdown, the task will be - * dropped, and an invalid future will be returned. - */ - template - auto push(Task task, Args&&... args) -> std::future; - - /** - * Pushes a task on the front of the queue. If the queue is shutdown, the task will be dropped, and an invalid - * future will be returned. - * - * @param task A task to push to the back of the queue. - * @param args The arguments to call the task with. - * @returns A @c std::future to access the return value of the task. If the queue is shutdown, the task will be - * dropped, and an invalid future will be returned. - */ - template - auto pushToFront(Task task, Args&&... args) -> std::future; - - /** - * Returns and removes the task at the front of the queue. If there are no tasks, this call will block until there - * is one. A @c nullptr will be returned if there are no more tasks expected. - * - * @returns A task which the caller assumes ownership of, or @c nullptr if the TaskQueue expects no more tasks. - */ - std::unique_ptr> pop(); - - /** - * Clears the queue of outstanding tasks and refuses any additional tasks to be pushed onto the queue. - * - * Must be called by task enqueuers when no more tasks will be enqueued. - */ - void shutdown(); - - /** - * Returns whether or not the queue is shutdown. - * - * @returns Whether or not the queue is shutdown. - */ - bool isShutdown(); - -private: - /// The queue type to use for holding tasks. - using Queue = std::deque>>; - - /** - * Pushes a task on the the queue. If the queue is shutdown, the task will be dropped, and an invalid - * future will be returned. - * - * @param front If @c true, push to the front of the queue, else push to the back. - * @param task A task to push to the front or back of the queue. - * @param args The arguments to call the task with. - * @returns A @c std::future to access the return value of the task. If the queue is shutdown, the task will be - * dropped, and an invalid future will be returned. - */ - template - auto pushTo(bool front, Task task, Args&&... args) -> std::future; - - /// The queue of tasks - Queue m_queue; - - /// A condition variable to wait for new tasks to be placed on the queue. - std::condition_variable m_queueChanged; - - /// A mutex to protect access to the tasks in m_queue. - std::mutex m_queueMutex; - - /// A flag for whether or not the queue is expecting more tasks. - std::atomic_bool m_shutdown; -}; - -template -auto TaskQueue::push(Task task, Args&&... args) -> std::future { - bool front = true; - return pushTo(!front, std::forward(task), std::forward(args)...); -} - -template -auto TaskQueue::pushToFront(Task task, Args&&... args) -> std::future { - bool front = true; - return pushTo(front, std::forward(task), std::forward(args)...); -} - -/** - * Utility function which waits for a @c std::future to be fulfilled and forward the result to a @c std::promise. - * - * @param promise The @c std::promise to fulfill when @c future is fulfilled. - * @param future The @c std::future on which to wait for a result to forward to @c promise. - */ -template -inline static void forwardPromise(std::shared_ptr> promise, std::future* future) { - promise->set_value(future->get()); -} - -/** - * Specialization of @c forwardPromise() for @c void types. - * - * @param promise The @c std::promise to fulfill when @c future is fulfilled. - * @param future The @c std::future on which to wait before fulfilling @c promise. - */ -template <> -inline void forwardPromise(std::shared_ptr> promise, std::future* future) { - future->get(); - promise->set_value(); -} - -template -auto TaskQueue::pushTo(bool front, Task task, Args&&... args) -> std::future { - // Remove arguments from the tasks type by binding the arguments to the task. - auto boundTask = std::bind(std::forward(task), std::forward(args)...); - - /* - * Create a std::packaged_task with the correct return type. The decltype only returns the return value of the - * boundTask. The following parentheses make it a function call with the boundTask return type. The package task - * will then return a future of the correct type. - * - * Note: A std::packaged_task fulfills its future *during* the call to operator(). If the user of a - * std::packaged_task hands it off to another thread to execute, and then waits on the future, they will be able to - * retrieve the return value from the task and know that the task has executed, but they do not know exactly when - * the task object has been deleted. This distinction can be significant if the packaged task is holding onto - * resources that need to be freed (through a std::shared_ptr for example). If the user needs to wait for those - * resources to be freed they have no way of knowing how long to wait. The translated_task lambda below is a - * workaround for this limitation. It executes the packaged task, then disposes of it before passing the task's - * return value back to the future that the user is waiting on. - */ - using PackagedTaskType = std::packaged_task; - auto packaged_task = std::make_shared(boundTask); - - // Create a promise/future that we will fulfill when we have cleaned up the task. - auto cleanupPromise = std::make_shared>(); - auto cleanupFuture = cleanupPromise->get_future(); - - // Remove the return type from the task by wrapping it in a lambda with no return value. - auto translated_task = [packaged_task, cleanupPromise]() mutable { - // Execute the task. - packaged_task->operator()(); - // Note the future for the task's result. - auto taskFuture = packaged_task->get_future(); - // Clean up the task. - packaged_task.reset(); - // Forward the task's result to our cleanup promise/future. - forwardPromise(cleanupPromise, &taskFuture); - }; - - // Release our local reference to packaged task so that the only remaining reference is inside the lambda. - packaged_task.reset(); - - { - std::lock_guard queueLock{m_queueMutex}; - if (!m_shutdown) { - m_queue.emplace(front ? m_queue.begin() : m_queue.end(), new std::function(translated_task)); - } else { - using FutureType = decltype(task(args...)); - return std::future(); - } - } - - m_queueChanged.notify_all(); - return cleanupFuture; -} - -} // namespace threading -} // namespace utils -} // namespace avsCommon -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_TASKQUEUE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h index f496f9b04f..bef911582d 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -17,18 +17,19 @@ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_TASKTHREAD_H_ #include -#include +#include +#include #include -#include "AVSCommon/Utils/Threading/TaskQueue.h" - namespace alexaClientSDK { namespace avsCommon { namespace utils { namespace threading { /** - * A TaskThread is a thread which reads from a TaskQueue and executes those tasks. + * A TaskThread executes in sequence until no more tasks exists. + * + * @note It's the caller responsibility to restart the @c TaskThread if jobRunner returns false. */ class TaskThread { public: @@ -37,7 +38,7 @@ class TaskThread { * * @params taskQueue A TaskQueue to take tasks from to execute. */ - TaskThread(std::shared_ptr taskQueue); + TaskThread(); /** * Destructs the TaskThread. @@ -45,31 +46,38 @@ class TaskThread { ~TaskThread(); /** - * Starts executing tasks from the queue on the thread. - */ - void start(); - - /** - * Returns whether or not the TaskThread has been shutdown. + * Start executing tasks from the given job runner. The task thread will keep running until @c jobRunner + * returns @c false or @c start gets called again. * - * @returns whether or not the TaskThread has been shutdown. + * @param jobRunner Function that should execute jobs. The function should return @c true if there's more tasks + * to be executed. + * @return @c true if it succeeds to start the new jobRunner thread; @c false if it fails. */ - bool isShutdown(); + bool start(std::function jobRunner); private: /** - * Loops over the TaskQueue executing all tasks until either the TaskQueue is lost, or the thread is shutdown. + * Run the @c jobRunner until it returns @c false or @c m_stop is set to true. + * + * @param jobRunner Function that should execute the next job. The function should return @c true if a new job + * still exists. */ - void processTasksLoop(); - - /// A weak pointer to the TaskQueue, if the task queue is no longer accessible, there is no reason to execute tasks. - std::weak_ptr m_taskQueue; - - /// A flag to message the task thread to stop executing. - std::atomic_bool m_shutdown; + void run(std::function jobRunner); /// The thread to run tasks on. std::thread m_thread; + + /// Old thread that will be terminated after start. + std::thread m_oldThread; + + /// Flag used by the new thread to ensure that the old thread will exit once the current job ends. + std::atomic_bool m_stop; + + /// Flag used to indicate that there is a new job starting. + std::atomic_bool m_alreadyStarting; + + /// The task thread moniker. + std::string m_moniker; }; } // namespace threading diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Stopwatch.h b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Stopwatch.h new file mode 100644 index 0000000000..32680c62d6 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Stopwatch.h @@ -0,0 +1,116 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_TIMING_STOPWATCH_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_TIMING_STOPWATCH_H_ + +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace timing { + +/** + * Class to provide simple stopwatch functionality. + */ +class Stopwatch { +public: + /** + * Constructor. Stopwatch instances are created ready for a call to @c start. + */ + Stopwatch(); + + /** + * Start marking time. Only valid if called when the stopwatch is new or reset(). + * + * @return Whether the operation succeeded. + */ + bool start(); + + /** + * Pause marking time. Only valid if called after @c start() or @c resume(). + * + * @return Whether the operation succeeded. + */ + bool pause(); + + /** + * Resume marking time. Only valid if called after @c pause(). + * + * @return Whether the operation succeeded. + */ + bool resume(); + + /** + * Stop marking time. Valid after all other calls. + */ + void stop(); + + /** + * Reset elapsed time, prepare for @c start(). Valid after all other calls. + */ + void reset(); + + /** + * Get the total time elapsed in the @c start() or @c resume() state. + * + * @return The total time elapsed in the @c start() or @c resume() state. + */ + std::chrono::milliseconds getElapsed(); + +private: + /** + * Emum specifying the current state of a @c Stopwatch. + */ + enum class State { + /// Initial / reset state. Elapsed time reset to zero. Ready to start. + RESET, + /// The @c started()ed or @c resume()ed state. Time is being marked. + RUNNING, + /// The @c pause()d state. Time is not being marked. + PAUSED, + /// The @c stop()ed state. Time is not being marked. Elapsed reflects total time spent + /// @c start()ed and @c resume()d + STOPPED + }; + + /// Serializes access to all members. + std::mutex m_mutex; + + /// The current state of the Stopwatch. + State m_state; + + /// The time @c start() was called while @c RESET. + std::chrono::steady_clock::time_point m_startTime; + + /// The last time @c pause() was called while @c RUNNING. + std::chrono::steady_clock::time_point m_pauseTime; + + /// The last time @c stop() was called while @c RUNNING or @c PAUSED. + std::chrono::steady_clock::time_point m_stopTime; + + /// The total time spent @c PAUSED. + std::chrono::milliseconds m_totalTimePaused; +}; + +} // namespace timing +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_TIMING_STOPWATCH_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimeUtils.h b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimeUtils.h index 3f2af49e6f..7c87ba53bc 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimeUtils.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimeUtils.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -16,8 +16,10 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_TIMING_TIMEUTILS_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_TIMING_TIMEUTILS_H_ +#include #include #include +#include #include "AVSCommon/Utils/RetryTimer.h" #include "AVSCommon/Utils/Timing/SafeCTimeAccess.h" @@ -86,31 +88,37 @@ class TimeUtils { bool getCurrentUnixTime(int64_t* currentTime); /** - * Convert timeval struct to a ISO 8601 RFC 3339 date-time string. This follows these specifications: + * Convert timepoint to a ISO 8601 RFC 3339 date-time string. This follows these specifications: * - https://tools.ietf.org/html/rfc3339 * * The end result will look like "1970-01-01T00:00:00.000Z" * - * @param t The time from the Unix epoch to convert. + * @param tp The timepoint from which to convert. * @param[out] iso8601TimeString The resulting time string. * @return True, if successful, false otherwise. */ - bool convertTimeToUtcIso8601Rfc3339(const struct timeval& t, std::string* iso8601TimeString); + bool convertTimeToUtcIso8601Rfc3339( + const std::chrono::system_clock::time_point& tp, + std::string* iso8601TimeString); private: /** * Calculate localtime offset in std::time_t. * - * In order to calculate the timezone offset, we call gmtime and localtime giving the same arbitrary time point. - * Then, we convert them back to time_t and calculate the conversion difference. The arbitrary time point is 24 - * hours past epoch, so we don't have to deal with negative time_t values. + * In order to calculate the timezone offset, we call gmtime and localtime giving the same reference time point. + * Then, we convert them back to time_t and calculate the conversion difference. + * + * Note: The reference time point cannot be a fixed value as time differences for a timezone can change. + * For example, Europe/London was observing UTC + 1:00 year long during 1970 but later changed to UTC + 1:00 only + * during summer time. * * This function uses non-threadsafe time functions. Thus, it is important to use the SafeCTimeAccess class. * + * @param referenceTime - The input reference time used to calculate the offset. * @param[out] ret Required pointer to object where the result will be saved. * @return Whether it succeeded to calculate the localtime offset. */ - bool localtimeOffset(std::time_t* ret); + bool localtimeOffset(std::time_t referenceTime, std::time_t* ret); /// Object used to safely access the system ctime functions. std::shared_ptr m_safeCTimeAccess; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/functional/hash.h b/AVSCommon/Utils/include/AVSCommon/Utils/functional/hash.h index 5aaade7e12..a497cb54d6 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/functional/hash.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/functional/hash.h @@ -40,6 +40,21 @@ void hashCombine(size_t& seed, Type const& value) { seed = hasher(value) ^ ((seed << 1) | ((seed >> bitsMinus1) & 1)); } +/** + * Functor to support std::hash implementations for enum classes. Example: + * @code + * enum class MyEnum { ONE, TWO, THREE }; + * std::unordered_map myMap; + * return myMap[TWO]; + * @endcode + */ +struct EnumClassHash { + template + std::size_t operator()(T t) const { + return static_cast(t); + } +}; + } // namespace functional } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/Bluetooth/SDPRecords.cpp b/AVSCommon/Utils/src/Bluetooth/SDPRecords.cpp new file mode 100644 index 0000000000..97c750e77e --- /dev/null +++ b/AVSCommon/Utils/src/Bluetooth/SDPRecords.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSinkInterface.h" +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/A2DPSourceInterface.h" +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPControllerInterface.h" +#include "AVSCommon/SDKInterfaces/Bluetooth/Services/AVRCPTargetInterface.h" + +#include "AVSCommon/Utils/Bluetooth/SDPRecords.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +using namespace avsCommon::sdkInterfaces::bluetooth::services; + +SDPRecord::SDPRecord(const std::string& name, const std::string& uuid, const std::string& version) : + m_name{name}, + m_uuid{uuid}, + m_version{version} { +} + +std::string SDPRecord::getName() const { + return m_name; +} + +std::string SDPRecord::getUuid() const { + return m_uuid; +} + +std::string SDPRecord::getVersion() const { + return m_version; +} + +A2DPSourceRecord::A2DPSourceRecord(const std::string& version) : + SDPRecord{A2DPSourceInterface::NAME, A2DPSourceInterface::UUID, version} { +} + +A2DPSinkRecord::A2DPSinkRecord(const std::string& version) : + SDPRecord{A2DPSinkInterface::NAME, A2DPSinkInterface::UUID, version} { +} + +AVRCPTargetRecord::AVRCPTargetRecord(const std::string& version) : + SDPRecord{AVRCPTargetInterface::NAME, AVRCPTargetInterface::UUID, version} { +} + +AVRCPControllerRecord::AVRCPControllerRecord(const std::string& version) : + SDPRecord{AVRCPControllerInterface::NAME, AVRCPControllerInterface::UUID, version} { +} + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/BluetoothEventBus.cpp b/AVSCommon/Utils/src/BluetoothEventBus.cpp new file mode 100644 index 0000000000..5d498c9be4 --- /dev/null +++ b/AVSCommon/Utils/src/BluetoothEventBus.cpp @@ -0,0 +1,136 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "AVSCommon/Utils/Logger/Logger.h" + +#include "AVSCommon/Utils/Bluetooth/BluetoothEventBus.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/// String to identify log entries originating from this file. +static const std::string TAG{"BluetoothEventBus"}; + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +BluetoothEventBus::BluetoothEventBus() { +} + +void BluetoothEventBus::sendEvent(const BluetoothEvent& event) { + ListenerList listenerList; + + { + std::lock_guard lock(m_mutex); + auto mapIterator = m_listenerMap.find(event.getType()); + if (mapIterator == m_listenerMap.end()) { + // No listeners registered + return; + } + + listenerList = mapIterator->second; + } + + for (auto listenerWeakPtr : listenerList) { + std::shared_ptr listener = listenerWeakPtr.lock(); + if (listener != nullptr) { + listener->onEventFired(event); + } + } +} + +void BluetoothEventBus::addListener( + const std::vector& eventTypes, + std::shared_ptr listener) { + if (listener == nullptr) { + ACSDK_ERROR(LX("addListenerFailed").d("reason", "Listener cannot be null")); + return; + } + + std::lock_guard lock(m_mutex); + + for (BluetoothEventType eventType : eventTypes) { + ListenerList& listenerList = m_listenerMap[eventType]; + + auto iter = listenerList.begin(); + while (iter != listenerList.end()) { + auto listenerWeakPtr = *iter; + auto listenerPtr = listenerWeakPtr.lock(); + if (listenerPtr == nullptr) { + iter = listenerList.erase(iter); + } else { + if (listenerPtr == listener) { + ACSDK_ERROR(LX("addListenerFailed").d("reason", "The same listener already exists")); + break; + } + ++iter; + } + } + + if (iter == listenerList.end()) { + listenerList.push_back(listener); + } + } +} + +void BluetoothEventBus::removeListener( + const std::vector& eventTypes, + std::shared_ptr listener) { + if (listener == nullptr) { + ACSDK_ERROR(LX("removeListenerFailed").d("reason", "Listener cannot be null")); + return; + } + + std::lock_guard lock(m_mutex); + + for (BluetoothEventType eventType : eventTypes) { + auto mapIterator = m_listenerMap.find(eventType); + if (mapIterator == m_listenerMap.end()) { + ACSDK_ERROR(LX("removeListenerFailed").d("reason", "Listener not subscribed")); + continue; + } + + ListenerList& listenerList = mapIterator->second; + + auto iter = listenerList.begin(); + while (iter != listenerList.end()) { + auto listenerWeakPtr = *iter; + auto listenerPtr = listenerWeakPtr.lock(); + if (listenerPtr == nullptr) { + iter = listenerList.erase(iter); + } else if (listenerPtr == listener) { + listenerList.erase(iter); + break; + } else { + ++iter; + } + } + + if (listenerList.empty()) { + m_listenerMap.erase(mapIterator); + } + } // Iterate through event types +} + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/Configuration/ConfigurationNode.cpp b/AVSCommon/Utils/src/Configuration/ConfigurationNode.cpp index 48e5036f16..ae92779e1b 100644 --- a/AVSCommon/Utils/src/Configuration/ConfigurationNode.cpp +++ b/AVSCommon/Utils/src/Configuration/ConfigurationNode.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -90,13 +90,14 @@ static void mergeDocument(const std::string& path, Value& out, Value& in, Docume } } -bool ConfigurationNode::initialize(const std::vector& jsonStreams) { +bool ConfigurationNode::initialize(const std::vector>& jsonStreams) { std::lock_guard lock(m_mutex); if (m_root) { ACSDK_ERROR(LX("initializeFailed").d("reason", "alreadyInitialized")); return false; } m_document.SetObject(); + for (auto jsonStream : jsonStreams) { if (!jsonStream) { m_document.SetObject(); @@ -111,10 +112,13 @@ bool ConfigurationNode::initialize(const std::vector& jsonStreams .d("offset", overlay.GetErrorOffset()) .d("message", GetParseError_En(overlay.GetParseError()))); m_document.SetObject(); + return false; } + mergeDocument("root", m_document, overlay, m_document.GetAllocator()); } + m_root = ConfigurationNode(&m_document); ACSDK_DEBUG0(LX("initializeSuccess").sensitive("configuration", valueToString(m_document))); return true; @@ -172,6 +176,61 @@ ConfigurationNode::operator bool() const { ConfigurationNode::ConfigurationNode(const rapidjson::Value* object) : m_object{object} { } +std::string ConfigurationNode::serialize() const { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + if (!m_object->Accept(writer)) { + ACSDK_ERROR(LX("serializeFailed").d("reason", "writerRefusedObject")); + return ""; + } + + const char* bufferData = buffer.GetString(); + if (!bufferData) { + ACSDK_ERROR(LX("serializeFailed").d("reason", "nullptrBufferString")); + return ""; + } + + return std::string(bufferData); +} + +ConfigurationNode ConfigurationNode::getArray(const std::string& key) const { + if (!*this) { + ACSDK_ERROR(LX("getArrayFailed").d("reason", "emptyConfigurationNode")); + return ConfigurationNode(); + } + auto it = m_object->FindMember(key.c_str()); + if (m_object->MemberEnd() == it) { + return ConfigurationNode(); + } + if (!it->value.IsArray()) { + ACSDK_ERROR(LX("getArrayFailed").d("reason", "notAnArray")); + return ConfigurationNode(); + } + return ConfigurationNode(&it->value); +} + +std::size_t ConfigurationNode::getArraySize() const { + if (!*this) { + ACSDK_ERROR(LX("getArraySizeFailed").d("reason", "emptyConfigurationNode")); + return 0; + } + if (!m_object->IsArray()) { + ACSDK_ERROR(LX("getArraySizeFailed").d("reason", "notAnArray")); + return 0; + } + return m_object->Size(); +} + +ConfigurationNode ConfigurationNode::operator[](const std::size_t index) const { + auto size = getArraySize(); + if (index >= size) { + ACSDK_ERROR(LX("operator[]Failed").d("reason", "indexOutOfRange").d("size", size).d("index", index)); + return ConfigurationNode(); + } + const rapidjson::Value& objectRef = *m_object; + return ConfigurationNode(&objectRef[index]); +} + } // namespace configuration } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/DeviceInfo.cpp b/AVSCommon/Utils/src/DeviceInfo.cpp new file mode 100644 index 0000000000..202ac030f7 --- /dev/null +++ b/AVSCommon/Utils/src/DeviceInfo.cpp @@ -0,0 +1,147 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "AVSCommon/Utils/DeviceInfo.h" + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { + +/// String to identify log entries originating from this file. +static const std::string TAG("DeviceInfo"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// Name of @c ConfigurationNode for DeviceInfo +const std::string CONFIG_KEY_DEVICE_INFO = "deviceInfo"; +/// Name of clientId value in DeviceInfo's @c ConfigurationNode. +const std::string CONFIG_KEY_CLIENT_ID = "clientId"; +/// Name of productId value in DeviceInfo's @c ConfigurationNode. +const std::string CONFIG_KEY_PRODUCT_ID = "productId"; +/// Name of deviceSerialNumber value in DeviceInfo's @c ConfigurationNode. +const std::string CONFIG_KEY_DSN = "deviceSerialNumber"; + +std::unique_ptr DeviceInfo::create( + const avsCommon::utils::configuration::ConfigurationNode& configurationRoot) { + std::string clientId; + std::string productId; + std::string deviceSerialNumber; + const std::string errorEvent = "createFailed"; + const std::string errorReasonKey = "reason"; + const std::string errorValueKey = "key"; + + auto deviceInfoConfiguration = configurationRoot[CONFIG_KEY_DEVICE_INFO]; + if (!deviceInfoConfiguration) { + ACSDK_ERROR(LX(errorEvent) + .d(errorReasonKey, "missingDeviceInfoConfiguration") + .d(errorValueKey, CONFIG_KEY_DEVICE_INFO)); + return nullptr; + } + + if (!deviceInfoConfiguration.getString(CONFIG_KEY_CLIENT_ID, &clientId)) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "missingClientId").d(errorValueKey, CONFIG_KEY_CLIENT_ID)); + return nullptr; + } + + if (!deviceInfoConfiguration.getString(CONFIG_KEY_PRODUCT_ID, &productId)) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "missingProductId").d(errorValueKey, CONFIG_KEY_PRODUCT_ID)); + return nullptr; + } + + if (!deviceInfoConfiguration.getString(CONFIG_KEY_DSN, &deviceSerialNumber)) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "missingDeviceSerialNumber").d(errorValueKey, CONFIG_KEY_DSN)); + return nullptr; + } + + return create(clientId, productId, deviceSerialNumber); +} + +std::unique_ptr DeviceInfo::create( + const std::string& clientId, + const std::string& productId, + const std::string& deviceSerialNumber) { + const std::string errorEvent = "createFailed"; + const std::string errorReasonKey = "reason"; + const std::string errorValueKey = "key"; + + if (clientId.empty()) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "missingClientId").d(errorValueKey, CONFIG_KEY_CLIENT_ID)); + return nullptr; + } + + if (productId.empty()) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "missingProductId").d(errorValueKey, CONFIG_KEY_PRODUCT_ID)); + return nullptr; + } + + if (deviceSerialNumber.empty()) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "missingDeviceSerialNumber").d(errorValueKey, CONFIG_KEY_DSN)); + return nullptr; + } + + std::unique_ptr instance(new DeviceInfo(clientId, productId, deviceSerialNumber)); + + return instance; +} + +std::string DeviceInfo::getClientId() const { + return m_clientId; +} + +std::string DeviceInfo::getProductId() const { + return m_productId; +} + +std::string DeviceInfo::getDeviceSerialNumber() const { + return m_deviceSerialNumber; +} + +bool DeviceInfo::operator==(const DeviceInfo& rhs) { + if (getClientId() != rhs.getClientId()) { + return false; + } + if (getProductId() != rhs.getProductId()) { + return false; + } + if (getDeviceSerialNumber() != rhs.getDeviceSerialNumber()) { + return false; + } + + return true; +} + +bool DeviceInfo::operator!=(const DeviceInfo& rhs) { + return !(*this == rhs); +} + +DeviceInfo::DeviceInfo( + const std::string& clientId, + const std::string& productId, + const std::string& deviceSerialNumber) : + m_clientId{clientId}, + m_productId{productId}, + m_deviceSerialNumber{deviceSerialNumber} { +} + +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/Executor.cpp b/AVSCommon/Utils/src/Executor.cpp index 2d93f83dad..c917669d8d 100644 --- a/AVSCommon/Utils/src/Executor.cpp +++ b/AVSCommon/Utils/src/Executor.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -21,31 +21,66 @@ namespace avsCommon { namespace utils { namespace threading { -Executor::Executor() : - m_taskQueue{std::make_shared()}, - m_taskThread{memory::make_unique(m_taskQueue)} { - m_taskThread->start(); -} - Executor::~Executor() { shutdown(); } +Executor::Executor(const std::chrono::milliseconds& delayExit) : + m_threadRunning{false}, + m_timeout{delayExit}, + m_shutdown{false} { +} + void Executor::waitForSubmittedTasks() { - std::promise flushedPromise; - auto flushedFuture = flushedPromise.get_future(); - auto task = [&flushedPromise]() { flushedPromise.set_value(); }; - submit(task); - flushedFuture.get(); + std::unique_lock lock{m_queueMutex}; + if (m_threadRunning) { + // wait for thread to exit. + std::promise flushedPromise; + auto flushedFuture = flushedPromise.get_future(); + m_queue.emplace_back([&flushedPromise]() { flushedPromise.set_value(); }); + + lock.unlock(); + m_delayedCondition.notify_one(); + flushedFuture.wait(); + } +} + +std::function Executor::pop() { + std::lock_guard lock{m_queueMutex}; + if (!m_queue.empty()) { + auto task = std::move(m_queue.front()); + m_queue.pop_front(); + return task; + } + return std::function(); +} + +bool Executor::hasNext() { + std::unique_lock lock{m_queueMutex}; + m_delayedCondition.wait_for(lock, m_timeout, [this] { return !m_queue.empty() || m_shutdown; }); + m_threadRunning = !m_queue.empty(); + return m_threadRunning; +} + +bool Executor::runNext() { + auto task = pop(); + if (task) { + task(); + } + + return hasNext(); } void Executor::shutdown() { - m_taskQueue->shutdown(); - m_taskThread.reset(); + std::unique_lock lock{m_queueMutex}; + m_queue.clear(); + m_shutdown = true; + lock.unlock(); + waitForSubmittedTasks(); } bool Executor::isShutdown() { - return m_taskQueue->isShutdown(); + return m_shutdown; } } // namespace threading diff --git a/AVSCommon/Utils/src/FormattedAudioStreamAdapter.cpp b/AVSCommon/Utils/src/FormattedAudioStreamAdapter.cpp new file mode 100644 index 0000000000..6660905061 --- /dev/null +++ b/AVSCommon/Utils/src/FormattedAudioStreamAdapter.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "AVSCommon/Utils/Bluetooth/FormattedAudioStreamAdapter.h" +#include "AVSCommon/Utils/Logger/Logger.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace bluetooth { + +/// String to identify log entries originating from this file. +static const std::string TAG("FormattedAudioStreamAdapter"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +FormattedAudioStreamAdapter::FormattedAudioStreamAdapter(const AudioFormat& audioFormat) { + // Some compilers fail to initialize AudioFormat structure with a curly brackets notation. + m_audioFormat = audioFormat; +} + +AudioFormat FormattedAudioStreamAdapter::getAudioFormat() const { + return m_audioFormat; +} + +void FormattedAudioStreamAdapter::setListener(std::shared_ptr listener) { + std::lock_guard guard(m_readerFunctionMutex); + m_listener = listener; +} + +size_t FormattedAudioStreamAdapter::send(const unsigned char* buffer, size_t size) { + if (!buffer) { + ACSDK_ERROR(LX("sendFailed").d("reason", "buffer is null")); + return 0; + } + + if (0 == size) { + ACSDK_ERROR(LX("sendFailed").d("reason", "size is 0")); + return 0; + } + + std::shared_ptr listener; + + { + std::lock_guard guard(m_readerFunctionMutex); + listener = m_listener.lock(); + } + + if (listener) { + listener->onFormattedAudioStreamAdapterData(m_audioFormat, buffer, size); + return size; + } else { + return 0; + } +} + +} // namespace bluetooth +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/HTTP2/HTTP2GetMimeHeadersResult.cpp b/AVSCommon/Utils/src/HTTP2/HTTP2GetMimeHeadersResult.cpp new file mode 100644 index 0000000000..695e954cfd --- /dev/null +++ b/AVSCommon/Utils/src/HTTP2/HTTP2GetMimeHeadersResult.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "AVSCommon/Utils/HTTP2/HTTP2GetMimeHeadersResult.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +const HTTP2GetMimeHeadersResult HTTP2GetMimeHeadersResult::PAUSE{HTTP2SendStatus::PAUSE, {}}; +const HTTP2GetMimeHeadersResult HTTP2GetMimeHeadersResult::COMPLETE{HTTP2SendStatus::COMPLETE, {}}; +const HTTP2GetMimeHeadersResult HTTP2GetMimeHeadersResult::ABORT{HTTP2SendStatus::ABORT, {}}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp b/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp new file mode 100644 index 0000000000..2674dbb375 --- /dev/null +++ b/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp @@ -0,0 +1,321 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2MimeRequestEncoder.h" +#include "AVSCommon/Utils/HTTP2/HTTP2MimeRequestSourceInterface.h" + +/// String to identify log entries originating from this file. +static const std::string TAG("HTTP2MimeRequestEncoder"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +static const std::string BOUNDARY_HEADER_PREFIX = "Content-Type: multipart/form-data; boundary="; + +static const std::string CRLF = "\r\n"; + +static const std::string TWO_DASHES = "--"; + +std::ostream& operator<<(std::ostream& stream, HTTP2MimeRequestEncoder::State state) { + switch (state) { + case HTTP2MimeRequestEncoder::State::NEW: + return stream << "NEW"; + case HTTP2MimeRequestEncoder::State::GETTING_1ST_PART_HEADERS: + return stream << "GETTING_1ST_PART_HEADERS"; + case HTTP2MimeRequestEncoder::State::SENDING_1ST_BOUNDARY: + return stream << "SENDING_1ST_BOUNDARY"; + case HTTP2MimeRequestEncoder::State::SENDING_PART_HEADERS: + return stream << "SENDING_PART_HEADERS"; + case HTTP2MimeRequestEncoder::State::SENDING_PART_DATA: + return stream << "SENDING_PART_DATA"; + case HTTP2MimeRequestEncoder::State::SENDING_END_BOUNDARY: + return stream << "SENDING_END_BOUNDARY"; + case HTTP2MimeRequestEncoder::State::GETTING_NTH_PART_HEADERS: + return stream << "GETTING_NTH_PART_HEADERS"; + case HTTP2MimeRequestEncoder::State::SENDING_CRLF_AFTER_BOUNDARY: + return stream << "SENDING_CRLF_AFTER_BOUNDARY"; + case HTTP2MimeRequestEncoder::State::SENDING_TERMINATING_DASHES: + return stream << "SENDING_TERMINATING_DASHES"; + case HTTP2MimeRequestEncoder::State::DONE: + return stream << "DONE"; + case HTTP2MimeRequestEncoder::State::ABORT: + return stream << "ABORT"; + } + return stream << "UNKNOWN(" << static_cast(state) << ")"; +} + +HTTP2MimeRequestEncoder::HTTP2MimeRequestEncoder( + const std::string& boundary, + std::shared_ptr source) : + m_state{State::NEW}, + m_rawBoundary{boundary}, + m_prefixedBoundary{CRLF + TWO_DASHES + boundary}, + m_source{source}, + m_getMimeHeaderLinesResult{HTTP2GetMimeHeadersResult::ABORT}, + m_stringIndex{0} { + ACSDK_DEBUG5(LX(__func__).d("boundary", boundary).d("source", source.get())); +} + +HTTP2SendDataResult HTTP2MimeRequestEncoder::onSendData(char* bytes, size_t size) { + ACSDK_DEBUG5(LX(__func__).d("size", size).d("state", m_state)); + + if (!m_source) { + return HTTP2SendDataResult::COMPLETE; + } + + m_bytesCopied = 0; + + while (true) { + switch (m_state) { + case State::NEW: { + setState(State::GETTING_1ST_PART_HEADERS); + } break; + + case State::GETTING_1ST_PART_HEADERS: + m_getMimeHeaderLinesResult = m_source->getMimePartHeaderLines(); + switch (m_getMimeHeaderLinesResult.status) { + case HTTP2SendStatus::CONTINUE: + m_stringIndex = 0; + setState(State::SENDING_1ST_BOUNDARY); + break; + + case HTTP2SendStatus::PAUSE: + if (m_bytesCopied != 0) { + return continueResult(); + } + return HTTP2SendDataResult::PAUSE; + + case HTTP2SendStatus::COMPLETE: + setState(State::DONE); + return continueResult(); + + case HTTP2SendStatus::ABORT: { + setState(State::ABORT); + return HTTP2SendDataResult::ABORT; + } + } + break; + + case State::SENDING_1ST_BOUNDARY: + if (!sendStringAndCRLF(bytes, size, m_prefixedBoundary)) { + return continueResult(); + } + m_headerLine = m_getMimeHeaderLinesResult.headers.begin(); + m_stringIndex = 0; + setState(State::SENDING_PART_HEADERS); + break; + + case State::SENDING_PART_HEADERS: + while (m_headerLine != m_getMimeHeaderLinesResult.headers.end()) { + if (sendStringAndCRLF(bytes, size, *m_headerLine)) { + m_headerLine++; + m_stringIndex = 0; + } else { + return continueResult(); + } + } + if (sendString(bytes, size, CRLF)) { + setState(State::SENDING_PART_DATA); + } else { + return continueResult(); + } + break; + + case State::SENDING_PART_DATA: { + auto sendPartResult = m_source->onSendMimePartData(bytes + m_bytesCopied, size - m_bytesCopied); + switch (sendPartResult.status) { + case HTTP2SendStatus::CONTINUE: + m_bytesCopied += sendPartResult.size; + if (m_bytesCopied == size) { + return continueResult(); + } else if (m_bytesCopied > size) { + setState(State::ABORT); + return HTTP2SendDataResult::ABORT; + } + // Continues back around to case State::SENDING_PART_DATA. + break; + + case HTTP2SendStatus::PAUSE: + if (m_bytesCopied != 0) { + return continueResult(); + } + return HTTP2SendDataResult::PAUSE; + + case HTTP2SendStatus::COMPLETE: + m_stringIndex = 0; + setState(State::SENDING_END_BOUNDARY); + break; + + case HTTP2SendStatus::ABORT: + setState(State::ABORT); + return HTTP2SendDataResult::ABORT; + } + } break; + + case State::SENDING_END_BOUNDARY: + if (!sendString(bytes, size, m_prefixedBoundary)) { + return continueResult(); + } + setState(State::GETTING_NTH_PART_HEADERS); + break; + + case State::GETTING_NTH_PART_HEADERS: + m_getMimeHeaderLinesResult = m_source->getMimePartHeaderLines(); + switch (m_getMimeHeaderLinesResult.status) { + case HTTP2SendStatus::CONTINUE: + m_stringIndex = 0; + setState(State::SENDING_CRLF_AFTER_BOUNDARY); + break; + + case HTTP2SendStatus::PAUSE: + if (m_bytesCopied != 0) { + return continueResult(); + } + return HTTP2SendDataResult::PAUSE; + + case HTTP2SendStatus::COMPLETE: + m_stringIndex = 0; + setState(State::SENDING_TERMINATING_DASHES); + break; + + case HTTP2SendStatus::ABORT: + setState(State::ABORT); + return HTTP2SendDataResult::ABORT; + } + break; + + case State::SENDING_CRLF_AFTER_BOUNDARY: + if (!sendString(bytes, size, CRLF)) { + return continueResult(); + } + m_headerLine = m_getMimeHeaderLinesResult.headers.begin(); + m_stringIndex = 0; + setState(State::SENDING_PART_HEADERS); + break; + + case State::SENDING_TERMINATING_DASHES: + if (sendStringAndCRLF(bytes, size, TWO_DASHES)) { + setState(State::DONE); + } + return continueResult(); + break; + + case State::DONE: + return HTTP2SendDataResult::COMPLETE; + break; + + case State::ABORT: + return HTTP2SendDataResult::ABORT; + } + } +} + +std::vector HTTP2MimeRequestEncoder::getRequestHeaderLines() { + ACSDK_DEBUG5(LX(__func__)); + if (m_source) { + auto lines = m_source->getRequestHeaderLines(); + lines.push_back(BOUNDARY_HEADER_PREFIX + m_rawBoundary); + return lines; + } + return {}; +} + +void HTTP2MimeRequestEncoder::setState(State newState) { + if (newState == m_state) { + ACSDK_DEBUG9(LX("nonStateChangeInSetState").d("state", m_state).d("newState", newState)); + return; + } + + static const std::set> transitions = { + {State::NEW, State::GETTING_1ST_PART_HEADERS}, + {State::GETTING_1ST_PART_HEADERS, State::SENDING_1ST_BOUNDARY}, + {State::GETTING_1ST_PART_HEADERS, State::DONE}, + {State::GETTING_1ST_PART_HEADERS, State::ABORT}, + {State::SENDING_1ST_BOUNDARY, State::SENDING_PART_HEADERS}, + {State::SENDING_PART_HEADERS, State::SENDING_PART_DATA}, + {State::SENDING_PART_DATA, State::SENDING_END_BOUNDARY}, + {State::SENDING_PART_DATA, State::DONE}, + {State::SENDING_PART_DATA, State::ABORT}, + {State::SENDING_END_BOUNDARY, State::GETTING_NTH_PART_HEADERS}, + {State::GETTING_NTH_PART_HEADERS, State::SENDING_CRLF_AFTER_BOUNDARY}, + {State::GETTING_NTH_PART_HEADERS, State::SENDING_TERMINATING_DASHES}, + {State::GETTING_NTH_PART_HEADERS, State::ABORT}, + {State::SENDING_CRLF_AFTER_BOUNDARY, State::SENDING_PART_HEADERS}, + {State::SENDING_TERMINATING_DASHES, State::DONE}, + }; + + if (transitions.find({m_state, newState}) != transitions.end()) { + ACSDK_DEBUG9(LX("setState").d("state", m_state).d("newState", newState)); + m_state = newState; + } else { + ACSDK_DEBUG9(LX("setStateFailed").d("reason", "noAllowed").d("state", m_state).d("newState", newState)); + m_state = State::DONE; + } +} + +bool HTTP2MimeRequestEncoder::sendString(char* bytes, size_t size, const std::string& text) { + auto bufferRemaining = size - m_bytesCopied; + auto textRemaining = text.size() - m_stringIndex; + auto result = bufferRemaining > textRemaining; + auto sizeToCopy = result ? textRemaining : bufferRemaining; + std::memcpy(bytes + m_bytesCopied, text.c_str() + m_stringIndex, sizeToCopy); + m_bytesCopied += sizeToCopy; + m_stringIndex += sizeToCopy; + return result; +} + +bool HTTP2MimeRequestEncoder::sendStringAndCRLF(char* bytes, size_t size, const std::string& text) { + if (m_stringIndex < text.size()) { + if (!sendString(bytes, size, text)) { + return false; + } + } + + auto crlfBytesCopied = m_stringIndex - text.size(); + auto bufferRemaining = size - m_bytesCopied; + auto crlfRemaining = CRLF.size() - crlfBytesCopied; + auto result = bufferRemaining > crlfRemaining; + auto sizeToCopy = result ? crlfRemaining : bufferRemaining; + std::memcpy(bytes + m_bytesCopied, CRLF.c_str() + crlfBytesCopied, sizeToCopy); + m_bytesCopied += sizeToCopy; + m_stringIndex += sizeToCopy; + return result; +} + +HTTP2SendDataResult HTTP2MimeRequestEncoder::continueResult() { + return HTTP2SendDataResult(m_bytesCopied); +} + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp b/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp new file mode 100644 index 0000000000..7cdfc1bef7 --- /dev/null +++ b/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp @@ -0,0 +1,256 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include "AVSCommon/Utils/HTTP/HttpResponseCode.h" +#include "AVSCommon/Utils/HTTP2/HTTP2MimeResponseDecoder.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +using namespace avsCommon::utils::http; + +/// String to identify log entries originating from this file. +static const std::string TAG("HTTP2MimeResponseDecoder"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// ASCII value of CR +static const char CARRIAGE_RETURN_ASCII = 13; +/// ASCII value of LF +static const char LINE_FEED_ASCII = 10; +/// CRLF sequence +static const char CRLF_SEQUENCE[] = {CARRIAGE_RETURN_ASCII, LINE_FEED_ASCII}; +/// Size of CLRF in chars +static const int LEADING_CRLF_CHAR_SIZE = sizeof(CRLF_SEQUENCE) / sizeof(*CRLF_SEQUENCE); + +/// MIME boundary string prefix in HTTP header. +static const std::string BOUNDARY_PREFIX = "boundary="; +/// Size in chars of the MIME boundary string prefix +static const int BOUNDARY_PREFIX_SIZE = BOUNDARY_PREFIX.size(); +/// MIME HTTP header value delimiter +static const std::string BOUNDARY_DELIMITER = ";"; + +HTTP2MimeResponseDecoder::HTTP2MimeResponseDecoder(std::shared_ptr sink) : + m_sink{sink}, + m_responseCode{0}, + m_lastStatus{HTTP2ReceiveDataStatus::SUCCESS}, + m_index{0}, + m_leadingCRLFCharsLeftToRemove{LEADING_CRLF_CHAR_SIZE}, + m_boundaryFound{false}, + m_lastSuccessIndex{0} { + m_multipartReader.onPartBegin = HTTP2MimeResponseDecoder::partBeginCallback; + m_multipartReader.onPartData = HTTP2MimeResponseDecoder::partDataCallback; + m_multipartReader.onPartEnd = HTTP2MimeResponseDecoder::partEndCallback; + m_multipartReader.userData = this; + ACSDK_DEBUG5(LX(__func__)); +} + +bool HTTP2MimeResponseDecoder::onReceiveResponseCode(long responseCode) { + ACSDK_DEBUG5(LX(__func__).d("responseCode", responseCode)); + m_responseCode = responseCode; + + if (!m_sink) { + ACSDK_WARN(LX("onReceiveResponseCodeIgnored").d("reason", "nullSink")); + return false; + } + + return m_sink->onReceiveResponseCode(m_responseCode); +} + +bool HTTP2MimeResponseDecoder::onReceiveHeaderLine(const std::string& line) { + ACSDK_DEBUG5(LX(__func__).d("line", line)); + + if (!m_sink) { + ACSDK_WARN(LX("onReceiveHeaderLineIgnored").d("reason", "nullSink")); + return false; + } + + if (!m_boundaryFound) { + if (line.find(BOUNDARY_PREFIX) != std::string::npos) { + std::string boundary{line.substr(line.find(BOUNDARY_PREFIX))}; + boundary = boundary.substr(BOUNDARY_PREFIX_SIZE, boundary.find(BOUNDARY_DELIMITER) - BOUNDARY_PREFIX_SIZE); + m_multipartReader.setBoundary(boundary); + m_boundaryFound = true; + } + } + + return m_sink->onReceiveHeaderLine(line); +} + +void HTTP2MimeResponseDecoder::partBeginCallback(const MultipartHeaders& headers, void* userData) { + HTTP2MimeResponseDecoder* decoder = static_cast(userData); + if (!decoder) { + ACSDK_ERROR(LX("partBeginCallbackFailed").d("reason", "nullDecoder")); + return; + } + switch (decoder->m_lastStatus) { + case HTTP2ReceiveDataStatus::SUCCESS: + // Pass notification and headers through to our sink. + if (!decoder->m_sink->onBeginMimePart(headers)) { + // Sink doesn't want the next part? ABORT! + decoder->m_lastStatus = HTTP2ReceiveDataStatus::ABORT; + } + case HTTP2ReceiveDataStatus::PAUSE: + // PAUSE state will switch to SUCCESS when partDataCallback() consumes the previously paused + // part chunk. So, ignore any previous parsed part-begin notifications or notifications + // that arrive after our sink switches to the PAUSED state. + break; + case HTTP2ReceiveDataStatus::ABORT: + // If in the ABORT state, ignore all notifications. + break; + } +} + +void HTTP2MimeResponseDecoder::partDataCallback(const char* buffer, size_t size, void* userData) { + HTTP2MimeResponseDecoder* decoder = static_cast(userData); + if (!decoder) { + ACSDK_ERROR(LX("partDataCallbackFailed").d("reason", "nullDecoder")); + return; + } + if (!buffer) { + ACSDK_ERROR(LX("partDataCallbackFailed").d("reason", "nullBuffer")); + return; + } + // Increment the counter for partDataCallbacks. + decoder->m_index++; + // Check if we can resume callbacks to sink again. + if (decoder->m_lastStatus == HTTP2ReceiveDataStatus::PAUSE && + decoder->m_index == (decoder->m_lastSuccessIndex + 1)) { + decoder->m_lastStatus = HTTP2ReceiveDataStatus::SUCCESS; + } + // Continue only if successful so far. + if (decoder->m_lastStatus == HTTP2ReceiveDataStatus::SUCCESS) { + decoder->m_lastStatus = decoder->m_sink->onReceiveMimeData(buffer, size); + // Update checkpoint. + if (decoder->m_lastStatus == HTTP2ReceiveDataStatus::SUCCESS) { + decoder->m_lastSuccessIndex = decoder->m_index; + } + } +} + +void HTTP2MimeResponseDecoder::partEndCallback(void* userData) { + HTTP2MimeResponseDecoder* decoder = static_cast(userData); + if (!decoder) { + ACSDK_ERROR(LX("partEndCallbackFailed").d("reason", "nullDecoder")); + return; + } + if ((decoder->m_lastStatus == HTTP2ReceiveDataStatus::SUCCESS) && !decoder->m_sink->onEndMimePart()) { + decoder->m_lastStatus = HTTP2ReceiveDataStatus::ABORT; + } +} + +HTTP2ReceiveDataStatus HTTP2MimeResponseDecoder::onReceiveData(const char* bytes, size_t size) { + ACSDK_DEBUG5(LX(__func__).d("size", size)); + if (!bytes) { + ACSDK_ERROR(LX("onReceivedDataFailed").d("reason", "nullBytes")); + return HTTP2ReceiveDataStatus::ABORT; + } + if (m_lastStatus != HTTP2ReceiveDataStatus::ABORT) { + // If a non successful response code was received, don't parse the data. Instead forward it to our sink. + if (m_responseCode != HTTPResponseCode::SUCCESS_OK) { + if (m_sink) { + return m_sink->onReceiveNonMimeData(bytes, size); + } else { + return HTTP2ReceiveDataStatus::ABORT; + } + } + + auto readerCheckpoint = m_multipartReader; + auto oldLeadingCRLFCharsLeftToRemove = m_leadingCRLFCharsLeftToRemove; + /** + * If no boundary found... + */ + if (!m_boundaryFound) { + return HTTP2ReceiveDataStatus::ABORT; + } + + /** + * Our parser expects no leading CRLF in the data stream. Additionally downchannel streams + * include this CRLF but event streams do not. So ensure that a leading CRLF in the data stream is removed, + * if it exists. + */ + if (m_leadingCRLFCharsLeftToRemove > 0) { + size_t posInCRLF = LEADING_CRLF_CHAR_SIZE - m_leadingCRLFCharsLeftToRemove; + while (m_leadingCRLFCharsLeftToRemove > 0 && size > 0) { + if (*bytes == CRLF_SEQUENCE[posInCRLF]) { + bytes++; + size--; + m_leadingCRLFCharsLeftToRemove--; + posInCRLF++; + } else { + if (1 == m_leadingCRLFCharsLeftToRemove) { + // Having only CR instead of CRLF indicates a broken stream, so we may safely report error here. + return HTTP2ReceiveDataStatus::ABORT; + } + m_leadingCRLFCharsLeftToRemove = 0; + break; + } + } + + if (0 == size) { + return HTTP2ReceiveDataStatus::SUCCESS; + } + } + + m_index = 0; + m_multipartReader.feed(bytes, size); + + if (m_multipartReader.hasError()) { + ACSDK_ERROR(LX("onReceiveDataFailed") + .d("reason", "mimeParseError") + .d("error", m_multipartReader.getErrorMessage())); + m_lastStatus = HTTP2ReceiveDataStatus::ABORT; + } + + switch (m_lastStatus) { + case HTTP2ReceiveDataStatus::SUCCESS: + m_lastSuccessIndex = 0; + break; + case HTTP2ReceiveDataStatus::PAUSE: + m_multipartReader = readerCheckpoint; + m_leadingCRLFCharsLeftToRemove = oldLeadingCRLFCharsLeftToRemove; + break; + case HTTP2ReceiveDataStatus::ABORT: + break; + } + } + + return m_lastStatus; +} + +void HTTP2MimeResponseDecoder::onResponseFinished(HTTP2ResponseFinishedStatus status) { + ACSDK_DEBUG5(LX(__func__).d("status", status)); + + if (!m_sink) { + ACSDK_WARN(LX("onResponseFinishedIgnored").d("reason", "nullSink")); + return; + } + + m_sink->onResponseFinished(status); +} + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/HTTP2/HTTP2SendDataResult.cpp b/AVSCommon/Utils/src/HTTP2/HTTP2SendDataResult.cpp new file mode 100644 index 0000000000..db6ead4434 --- /dev/null +++ b/AVSCommon/Utils/src/HTTP2/HTTP2SendDataResult.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "AVSCommon/Utils/HTTP2/HTTP2SendDataResult.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +const HTTP2SendDataResult HTTP2SendDataResult::PAUSE{HTTP2SendStatus::PAUSE, 0}; +const HTTP2SendDataResult HTTP2SendDataResult::COMPLETE{HTTP2SendStatus::COMPLETE, 0}; +const HTTP2SendDataResult HTTP2SendDataResult::ABORT{HTTP2SendStatus::ABORT, 0}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/JSON/JSONGenerator.cpp b/AVSCommon/Utils/src/JSON/JSONGenerator.cpp new file mode 100644 index 0000000000..6d3d360737 --- /dev/null +++ b/AVSCommon/Utils/src/JSON/JSONGenerator.cpp @@ -0,0 +1,123 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#include +#include + +#include + +#include "AVSCommon/Utils/JSON/JSONGenerator.h" +#include "AVSCommon/Utils/JSON/JSONUtils.h" + +/// String to identify log entries originating from this file. +static const std::string TAG("JSONGenerator"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace json { + +JsonGenerator::JsonGenerator() : m_buffer{}, m_writer{m_buffer} { + m_writer.StartObject(); +} + +bool JsonGenerator::startObject(const std::string& key) { + return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.StartObject(); +} + +bool JsonGenerator::finishObject() { + return checkWriter() && m_writer.EndObject(); +} + +bool JsonGenerator::addMember(const std::string& key, const std::string& value) { + return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.String(value); +} + +bool JsonGenerator::addMember(const std::string& key, uint64_t value) { + return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.Uint64(value); +} + +bool JsonGenerator::addMember(const std::string& key, unsigned int value) { + return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.Uint(value); +} + +bool JsonGenerator::addMember(const std::string& key, int64_t value) { + return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.Int64(value); +} + +bool JsonGenerator::addMember(const std::string& key, int value) { + return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.Int(value); +} + +bool JsonGenerator::addMember(const std::string& key, bool value) { + return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.Bool(value); +} + +bool JsonGenerator::addMember(const std::string& key, const char* value) { + return value && checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.String(value); +} + +bool JsonGenerator::addRawJsonMember(const std::string& key, const std::string& json, bool validate) { + // Validate the provided json. + if (validate) { + rapidjson::Document document; + if (!jsonUtils::parseJSON(json, &document)) { + ACSDK_ERROR(LX("addRawJsonMemberFailed").d("reason", "invalidJson").sensitive("rawJson", json)); + return false; + } + } + return checkWriter() && m_writer.Key(key.c_str(), key.length()) && + m_writer.RawValue(json.c_str(), json.length(), rapidjson::kStringType); +} + +bool JsonGenerator::finalize() { + while (!m_writer.IsComplete()) { + if (!m_writer.EndObject()) { + ACSDK_ERROR(LX("finishFailed").d("reason", "failToEndObject")); + return false; + } + } + return true; +} + +std::string JsonGenerator::toString(bool finalizeJson) { + if (finalizeJson) { + finalize(); + } + return m_buffer.GetString(); +} + +bool JsonGenerator::checkWriter() { + if (m_writer.IsComplete()) { + ACSDK_ERROR(LX("addMemberFailed").d("reason", "finalizedGenerator")); + return false; + } + return true; +} + +bool JsonGenerator::isFinalized() { + return !checkWriter(); +} + +} // namespace json +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/JSONUtils.cpp b/AVSCommon/Utils/src/JSON/JSONUtils.cpp similarity index 84% rename from AVSCommon/Utils/src/JSONUtils.cpp rename to AVSCommon/Utils/src/JSON/JSONUtils.cpp index 9ed612f4a6..4a9d7bc418 100644 --- a/AVSCommon/Utils/src/JSONUtils.cpp +++ b/AVSCommon/Utils/src/JSON/JSONUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -132,7 +132,7 @@ bool findNode( auto iterator = jsonNode.FindMember(key); if (iterator == jsonNode.MemberEnd()) { - ACSDK_ERROR(LX("findNodeFailed").d("reason", "missingDirectChild").d("child", key)); + ACSDK_DEBUG5(LX("findNode").d("reason", "missingDirectChild").d("child", key)); return false; } @@ -169,6 +169,25 @@ bool convertToValue(const rapidjson::Value& documentNode, std::string* value) { return true; } +bool convertToValue(const rapidjson::Value& documentNode, uint64_t* value) { + if (!value) { + ACSDK_ERROR(LX("convertToUnsignedInt64ValueFailed").d("reason", "nullValue")); + return false; + } + + if (!documentNode.IsUint64()) { + ACSDK_ERROR(LX("convertToUnsignedInt64ValueFailed") + .d("reason", "invalidValue") + .d("expectedValue", "Uint64") + .d("type", documentNode.GetType())); + return false; + } + + *value = documentNode.GetUint64(); + + return true; +} + bool convertToValue(const rapidjson::Value& documentNode, int64_t* value) { if (!value) { ACSDK_ERROR(LX("convertToInt64ValueFailed").d("reason", "nullValue")); @@ -176,7 +195,11 @@ bool convertToValue(const rapidjson::Value& documentNode, int64_t* value) { } if (!documentNode.IsInt64()) { - ACSDK_ERROR(LX("convertToInt64ValueFailed").d("reason", "invalidValue").d("expectedValue", "Int64")); + ACSDK_ERROR(LX("convertToInt64ValueFailed") + .d("reason", "invalidValue") + .d("expectedValue", "Int64") + .d("type", documentNode.GetType())); + return false; } @@ -192,7 +215,10 @@ bool convertToValue(const rapidjson::Value& documentNode, bool* value) { } if (!documentNode.IsBool()) { - ACSDK_ERROR(LX("convertToBoolValueFailed").d("reason", "invalidValue").d("expectedValue", "Bool")); + ACSDK_ERROR(LX("convertToBoolValueFailed") + .d("reason", "invalidValue") + .d("expectedValue", "Bool") + .d("type", documentNode.GetType())); return false; } diff --git a/AVSCommon/Utils/src/LibcurlUtils/CallbackData.cpp b/AVSCommon/Utils/src/LibcurlUtils/CallbackData.cpp new file mode 100644 index 0000000000..9dc67ff5f1 --- /dev/null +++ b/AVSCommon/Utils/src/LibcurlUtils/CallbackData.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +using namespace alexaClientSDK::avsCommon::utils; + +/// String to identify log entries originating from this file. +static const std::string TAG("CallbackData"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +CallbackData::CallbackData(const char* data) { + appendData(data); +} + +size_t CallbackData::appendData(const char* data, size_t dataSize) { + if (!data) { + ACSDK_ERROR(LX("appendDataFailed").d("reason", "nullData")); + return 0; + } + m_memory.insert(m_memory.end(), data, data + dataSize); + return dataSize; +} + +size_t CallbackData::appendData(const char* data) { + if (!data) { + ACSDK_ERROR(LX("appendDataFailed").d("reason", "nullData")); + return 0; + } + return appendData(data, strlen(data)); +} + +void CallbackData::clearData() { + m_memory.clear(); +} + +size_t CallbackData::getData(char* dataContainer, size_t dataSize) { + if (!dataContainer) { + ACSDK_ERROR(LX("getDataFailed").d("reason", "nullDataContainer")); + return 0; + } + if ((m_memory.empty()) || (dataSize == 0)) { + ACSDK_INFO(LX("getDataFailed").d("reason", "noDataToGet")); + return 0; + } + + size_t getSize = std::min(dataSize, m_memory.size()); + std::memcpy(dataContainer, m_memory.data(), getSize); + + return getSize; +} + +size_t CallbackData::getSize() { + return m_memory.size(); +} + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp b/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp index 57c4448cf7..c3bda8f87c 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -15,17 +15,24 @@ #include +#include +#include "AVSCommon/Utils/Configuration/ConfigurationNode.h" #include -#include #include #include +#ifdef ACSDK_EMIT_CURL_LOGS + +#include + +#endif + namespace alexaClientSDK { namespace avsCommon { namespace utils { namespace libcurlUtils { -using namespace alexaClientSDK::avsCommon::utils; +using namespace avsCommon::utils::http; /// String to identify log entries originating from this file. static const std::string TAG("CurlEasyHandleWrapper"); @@ -38,18 +45,98 @@ static const std::string TAG("CurlEasyHandleWrapper"); #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /// MIME Content-Type for JSON data -static std::string JSON_MIME_TYPE = "text/json"; +static std::string JSON_MIME_TYPE = "application/json"; /// MIME Content-Type for octet stream data static std::string OCTET_MIME_TYPE = "application/octet-stream"; +/// Key for looking up the @c LibCurlUtil @c ConfigurationNode. +static const std::string LIBCURLUTILS_CONFIG_KEY = "libcurlUtils"; +/// Key for looking up a configuration value for @c CURLOPT_INTERFACE +static const std::string INTERFACE_CONFIG_KEY = "CURLOPT_INTERFACE"; +/// Counter used to geneate unique IDs. +std::atomic CurlEasyHandleWrapper::m_idGenerator{1}; + +#ifdef ACSDK_EMIT_CURL_LOGS +/// Key under 'acl' configuration node for path/prefix of per-stream log file names. +static const std::string STREAM_LOG_PREFIX_KEY("streamLogPrefix"); +/// Prefix for per-stream log file names. +static const std::string STREAM_LOG_NAME_PREFIX("stream-"); +/// Suffix for per-stream log file names. +static const std::string STREAM_LOG_NAME_SUFFIX(".log"); +/// Suffix for per-stream dump of incoming data. +static const std::string STREAM_IN_DUMP_SUFFIX("-in.bin"); +/// Suffix for per-stream dump of outgoing data. +static const std::string STREAM_OUT_DUMP_SUFFIX("-out.bin"); + +/** + * Macro to simplify building a switch that translates from enum values to strings. + * + * @param name The name of the enum value to translate. + */ +#define ACSDK_TYPE_CASE(name) \ + case name: \ + return #name; + +/** + * Return a string identifying a @c curl_infotype value. + * + * @param type The value to identify + * @return A string identifying the specified @c curl_infotype value. + */ +static const char* curlInfoTypeToString(curl_infotype type) { + switch (type) { + ACSDK_TYPE_CASE(CURLINFO_TEXT) + ACSDK_TYPE_CASE(CURLINFO_HEADER_OUT) + ACSDK_TYPE_CASE(CURLINFO_DATA_OUT) + ACSDK_TYPE_CASE(CURLINFO_SSL_DATA_OUT) + ACSDK_TYPE_CASE(CURLINFO_HEADER_IN) + ACSDK_TYPE_CASE(CURLINFO_DATA_IN) + ACSDK_TYPE_CASE(CURLINFO_SSL_DATA_IN) + ACSDK_TYPE_CASE(CURLINFO_END) + } + return ">>> unknown curl_infotype value <<<"; +} -CurlEasyHandleWrapper::CurlEasyHandleWrapper() : +#undef ACSDK_TYPE_CASE + +/** + * Return a prefix suitable for the data associated with a @c curl_infotype value. + * + * @param type The type of data to prefix. + * @return The prefix to use for the specified typw of data. + */ +static const char* curlInfoTypeToPrefix(curl_infotype type) { + switch (type) { + case CURLINFO_TEXT: + return "* "; + case CURLINFO_HEADER_OUT: + case CURLINFO_DATA_OUT: + case CURLINFO_SSL_DATA_OUT: + return "> "; + case CURLINFO_HEADER_IN: + case CURLINFO_DATA_IN: + case CURLINFO_SSL_DATA_IN: + return "< "; + case CURLINFO_END: + return ""; + } + return ">>> unknown curl_infotype value <<<"; +} +#endif // ACSDK_EMIT_CURL_LOGS + +CurlEasyHandleWrapper::CurlEasyHandleWrapper(std::string id) : m_handle{curl_easy_init()}, m_requestHeaders{nullptr}, m_postHeaders{nullptr}, - m_post{nullptr} { + m_post{nullptr}, + m_lastPost{nullptr} { if (m_handle == nullptr) { ACSDK_ERROR(LX("CurlEasyHandleWrapperFailed").d("reason", "curl_easy_init failed")); } else { + if (id.empty()) { + m_id = std::to_string(m_idGenerator++); + } else { + m_id = std::move(id); + } setDefaultOptions(); } }; @@ -61,7 +148,12 @@ CurlEasyHandleWrapper::~CurlEasyHandleWrapper() { } }; -bool CurlEasyHandleWrapper::reset() { +bool CurlEasyHandleWrapper::reset(std::string id) { + if (id.empty()) { + m_id = std::to_string(m_idGenerator++); + } else { + m_id = std::move(id); + } cleanupResources(); long responseCode = 0; CURLcode ret = curl_easy_getinfo(m_handle, CURLINFO_RESPONSE_CODE, &responseCode); @@ -106,6 +198,10 @@ bool CurlEasyHandleWrapper::isValid() { return m_handle != nullptr; } +std::string CurlEasyHandleWrapper::getId() const { + return m_id; +} + CURL* CurlEasyHandleWrapper::getCurlHandle() { return m_handle; } @@ -143,64 +239,17 @@ bool CurlEasyHandleWrapper::setTransferType(TransferType type) { case TransferType::kPOST: ret = setopt(CURLOPT_HTTPPOST, m_post); break; + case TransferType::kPUT: + ret = setopt(CURLOPT_UPLOAD, 1L); + break; } return ret; } -bool CurlEasyHandleWrapper::setPostContent(const std::string& fieldName, const std::string& payload) { - curl_httppost* last = nullptr; - CURLFORMcode ret = curl_formadd( - &m_post, - &last, - CURLFORM_COPYNAME, - fieldName.c_str(), - CURLFORM_COPYCONTENTS, - payload.c_str(), - CURLFORM_CONTENTTYPE, - JSON_MIME_TYPE.c_str(), - CURLFORM_CONTENTHEADER, - m_postHeaders, - CURLFORM_END); - if (ret) { - ACSDK_ERROR(LX("setPostContentFailed") - .d("reason", "curlFailure") - .d("method", "curl_formadd") - .d("fieldName", fieldName) - .sensitive("content", payload) - .d("curlFormCode", ret)); - - return false; - } - return true; -} - bool CurlEasyHandleWrapper::setTransferTimeout(const long timeoutSeconds) { return setopt(CURLOPT_TIMEOUT, timeoutSeconds); } -bool CurlEasyHandleWrapper::setPostStream(const std::string& fieldName, void* userData) { - curl_httppost* last = m_post; - CURLFORMcode ret = curl_formadd( - &m_post, - &last, - CURLFORM_COPYNAME, - fieldName.c_str(), - CURLFORM_STREAM, - userData, - CURLFORM_CONTENTTYPE, - OCTET_MIME_TYPE.c_str(), - CURLFORM_END); - if (ret) { - ACSDK_ERROR(LX("setPostStreamFailed") - .d("reason", "curlFailure") - .d("method", "curl_formadd") - .d("fieldName", fieldName) - .d("curlFormCode", ret)); - return false; - } - return true; -} - bool CurlEasyHandleWrapper::setPostData(const std::string& data) { return setopt(CURLOPT_POSTFIELDS, data.c_str()); } @@ -210,15 +259,25 @@ bool CurlEasyHandleWrapper::setConnectionTimeout(const std::chrono::seconds time } bool CurlEasyHandleWrapper::setWriteCallback(CurlCallback callback, void* userData) { - return setopt(CURLOPT_WRITEFUNCTION, callback) && (userData == nullptr || setopt(CURLOPT_WRITEDATA, userData)); + return setopt(CURLOPT_WRITEFUNCTION, callback) && (!userData || setopt(CURLOPT_WRITEDATA, userData)); } bool CurlEasyHandleWrapper::setHeaderCallback(CurlCallback callback, void* userData) { - return setopt(CURLOPT_HEADERFUNCTION, callback) && (userData == nullptr || setopt(CURLOPT_HEADERDATA, userData)); + return setopt(CURLOPT_HEADERFUNCTION, callback) && (!userData || setopt(CURLOPT_HEADERDATA, userData)); } bool CurlEasyHandleWrapper::setReadCallback(CurlCallback callback, void* userData) { - return setopt(CURLOPT_READFUNCTION, callback) && (userData == nullptr || setopt(CURLOPT_READDATA, userData)); + return setopt(CURLOPT_READFUNCTION, callback) && (!userData || setopt(CURLOPT_READDATA, userData)); +} + +std::string CurlEasyHandleWrapper::urlEncode(const std::string& in) const { + std::string result; + auto temp = curl_easy_escape(m_handle, in.c_str(), 0); + if (temp) { + result = temp; + curl_free(temp); + } + return result; } void CurlEasyHandleWrapper::cleanupResources() { @@ -235,23 +294,191 @@ void CurlEasyHandleWrapper::cleanupResources() { if (m_post) { curl_formfree(m_post); m_post = nullptr; + m_lastPost = nullptr; } } bool CurlEasyHandleWrapper::setDefaultOptions() { - if (prepareForTLS(m_handle)) { +#ifdef ACSDK_EMIT_CURL_LOGS + if (!(setopt(CURLOPT_DEBUGDATA, this) && setopt(CURLOPT_DEBUGFUNCTION, debugFunction) && + setopt(CURLOPT_VERBOSE, 1L))) { + return false; + } + initStreamLog(); +#endif + + // Using a do-while(false) loop here so we can break from the loop and do some cleanup in case of failures. + do { + if (!prepareForTLS(m_handle)) { + ACSDK_ERROR(LX("setDefaultOptions").d("reason", "prepareForTLS failed")); + break; + } + /* * The documentation from libcurl recommends setting CURLOPT_NOSIGNAL to 1 for multi-threaded applications. * https://curl.haxx.se/libcurl/c/threadsafe.html */ - return setopt(CURLOPT_NOSIGNAL, 1); - } - ACSDK_ERROR(LX("setDefaultOptions").d("reason", "prepareForTLS failed")); + if (!setopt(CURLOPT_NOSIGNAL, 1)) { + break; + } + + auto config = configuration::ConfigurationNode::getRoot()[LIBCURLUTILS_CONFIG_KEY]; + std::string interfaceName; + if (config.getString(INTERFACE_CONFIG_KEY, &interfaceName) && + !setopt(CURLOPT_INTERFACE, interfaceName.c_str())) { + break; + } + + // Everything is good, return true here. + return true; + } while (false); + + // Cleanup after breaking do-while(false) loop. curl_easy_cleanup(m_handle); m_handle = nullptr; return false; } +long CurlEasyHandleWrapper::getHTTPResponseCode() { + if (isValid()) { + long http_code = 0; + auto status = curl_easy_getinfo(m_handle, CURLINFO_RESPONSE_CODE, &http_code); + if (status != CURLE_OK) { + ACSDK_ERROR(LX("getHTTPResponseCodeFailed").d("reason", "curlEasyGetinfoFailed")); + http_code = 0; + } + return http_code; + } + + ACSDK_ERROR(LX("getHTTPResponseCodeFailed").d("reason", "notValid")); + return 0; +} + +CURLcode CurlEasyHandleWrapper::perform() { + if (isValid()) { + return curl_easy_perform(m_handle); + } + + ACSDK_ERROR(LX("performFailed").d("reason", "notValid")); + return CURLcode::CURLE_FAILED_INIT; +} + +CURLcode CurlEasyHandleWrapper::pause(int mask) { + if (isValid()) { + return curl_easy_pause(m_handle, mask); + } + + ACSDK_ERROR(LX("pauseFailed").d("reason", "notValid")); + return CURLcode::CURLE_FAILED_INIT; +} + +#ifdef ACSDK_EMIT_CURL_LOGS +void CurlEasyHandleWrapper::initStreamLog() { + std::string streamLogPrefix; + configuration::ConfigurationNode::getRoot()[LIBCURLUTILS_CONFIG_KEY].getString( + STREAM_LOG_PREFIX_KEY, &streamLogPrefix); + if (streamLogPrefix.empty()) { + return; + } + + if (m_streamLog) { + m_streamLog->close(); + m_streamLog.reset(); + } + + if (m_streamInDump) { + m_streamInDump->close(); + m_streamInDump.reset(); + } + + if (m_streamOutDump) { + m_streamOutDump->close(); + m_streamOutDump.reset(); + } + + // Include a 'session id' (just a time stamp) in the log file name to avoid overwriting previous sessions. + static std::string sessionId; + if (sessionId.empty()) { + sessionId = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + ACSDK_INFO(LX("initStreamLog").d("sessionId", sessionId)); + } + + auto basePath = streamLogPrefix + STREAM_LOG_NAME_PREFIX + sessionId + "-" + m_id; + + auto streamLogPath = basePath + STREAM_LOG_NAME_SUFFIX; + m_streamLog.reset(new std::ofstream(streamLogPath)); + if (!m_streamLog->good()) { + m_streamLog.reset(); + ACSDK_ERROR(LX("initStreamLogFailed").d("reason", "fileOpenFailed").d("streamLogPath", streamLogPath)); + } + + auto streamInDumpPath = basePath + STREAM_IN_DUMP_SUFFIX; + m_streamInDump.reset(new std::ofstream(streamInDumpPath, std::ios_base::out | std::ios_base::binary)); + if (!m_streamInDump->good()) { + m_streamInDump.reset(); + ACSDK_ERROR(LX("initStreamLogFailed").d("reason", "fileOpenFailed").d("streamInDumpPath", streamInDumpPath)); + } + + auto streamOutDumpPath = basePath + STREAM_OUT_DUMP_SUFFIX; + m_streamOutDump.reset(new std::ofstream(streamOutDumpPath, std::ios_base::out | std::ios_base::binary)); + if (!m_streamOutDump->good()) { + m_streamOutDump.reset(); + ACSDK_ERROR(LX("initStreamLogFailed").d("reason", "fileOpenFailed").d("streamOutDumpPath", streamOutDumpPath)); + } +} + +int CurlEasyHandleWrapper::debugFunction(CURL* handle, curl_infotype type, char* data, size_t size, void* user) { + CurlEasyHandleWrapper* stream = static_cast(user); + if (!stream) { + return 0; + } + if (stream->m_streamLog) { + auto logFormatter = stream->m_logFormatter; + (*stream->m_streamLog) << logFormatter.format( + logger::Level::INFO, + std::chrono::system_clock::now(), + logger::ThreadMoniker::getThisThreadMoniker().c_str(), + curlInfoTypeToString(type)) + << std::endl; + if (CURLINFO_TEXT == type) { + (*stream->m_streamLog) << curlInfoTypeToPrefix(type) << data; + } else { + logger::dumpBytesToStream( + *stream->m_streamLog, curlInfoTypeToPrefix(type), 0x20, reinterpret_cast(data), size); + } + stream->m_streamLog->flush(); + } + switch (type) { + case CURLINFO_TEXT: { + std::string text(data, size); + auto index = text.rfind("\n"); + if (index != std::string::npos) { + text.resize(index); + } + ACSDK_DEBUG9(LX("libcurl").d("id", stream->m_id).sensitive("text", text)); + } break; + case CURLINFO_HEADER_IN: + case CURLINFO_DATA_IN: + if (stream->m_streamInDump) { + stream->m_streamInDump->write(data, size); + stream->m_streamInDump->flush(); + } + break; + case CURLINFO_HEADER_OUT: + case CURLINFO_DATA_OUT: + if (stream->m_streamOutDump) { + stream->m_streamOutDump->write(data, size); + stream->m_streamOutDump->flush(); + } + break; + default: + break; + } + + return 0; +} +#endif // ACSDK_EMIT_CURL_LOGS + } // namespace libcurlUtils } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/LibcurlUtils/CurlMultiHandleWrapper.cpp b/AVSCommon/Utils/src/LibcurlUtils/CurlMultiHandleWrapper.cpp index bdf158d2b9..0c491fea43 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/CurlMultiHandleWrapper.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/CurlMultiHandleWrapper.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -21,8 +21,6 @@ namespace avsCommon { namespace utils { namespace libcurlUtils { -using namespace alexaClientSDK::avsCommon::utils; - /// String to identify log entries originating from this file. static const std::string TAG("CurlMultiHandleWrapper"); diff --git a/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp b/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp index 46104598eb..78a20963b1 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ #include #include +#include #include namespace alexaClientSDK { @@ -22,8 +23,19 @@ namespace avsCommon { namespace utils { namespace libcurlUtils { +/// String to identify log entries originating from this file. +static const std::string TAG("HTTPContentFetcherFactory"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + std::unique_ptr HTTPContentFetcherFactory::create( const std::string& url) { + ACSDK_DEBUG9(LX(__func__).sensitive("URL", url).m("Creating a new http content fetcher")); return avsCommon::utils::memory::make_unique(url); } diff --git a/AVSCommon/Utils/src/LibcurlUtils/HTTPResponse.cpp b/AVSCommon/Utils/src/LibcurlUtils/HTTPResponse.cpp new file mode 100644 index 0000000000..04212108cb --- /dev/null +++ b/AVSCommon/Utils/src/LibcurlUtils/HTTPResponse.cpp @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +std::string HTTPResponse::serialize() { + if (0 == code) { + return ""; + } + + std::string serializedValue = "Code: " + std::to_string(code); + + if (!body.empty()) { + serializedValue = serializedValue + ", Body: " + body; + } + + return serializedValue; +} + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp b/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp index 58dc60e2d5..c39ac371e7 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,8 +13,10 @@ * permissions and limitations under the License. */ +#include + +#include #include -#include #include #include @@ -43,22 +45,44 @@ std::unique_ptr HttpPost::create() { return nullptr; } -bool HttpPost::addHTTPHeader(const std::string& header) { - return m_curl.addHTTPHeader(header); -} - long HttpPost::doPost( const std::string& url, const std::string& data, std::chrono::seconds timeout, std::string& body) { + auto response = doPost(url, {}, data, timeout); + body = response.body; + return response.code; +} + +HTTPResponse HttpPost::doPost( + const std::string& url, + const std::vector headerLines, + const std::vector>& data, + std::chrono::seconds timeout) { + auto encodedData = buildPostData(data); + return doPost(url, headerLines, encodedData, timeout); +} + +HTTPResponse HttpPost::doPost( + const std::string& url, + const std::vector headerLines, + const std::string& data, + std::chrono::seconds timeout) { std::lock_guard lock(m_mutex); - body.clear(); + HTTPResponse response; + + if (!m_curl.reset() || !m_curl.setTransferTimeout(static_cast(timeout.count())) || !m_curl.setURL(url) || + !m_curl.setPostData(data) || !m_curl.setWriteCallback(staticWriteCallbackLocked, &response.body)) { + return HTTPResponse(); + } - if (!m_curl.setTransferTimeout(static_cast(timeout.count())) || !m_curl.setURL(url) || - !m_curl.setPostData(data) || !m_curl.setWriteCallback(staticWriteCallbackLocked, &body)) { - return HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED; + for (auto line : headerLines) { + if (!m_curl.addHTTPHeader(line)) { + ACSDK_ERROR(LX("doPostFailed").d("reason", "unableToAddHttpHeader")); + return HTTPResponse(); + } } auto curlHandle = m_curl.getCurlHandle(); @@ -69,24 +93,34 @@ long HttpPost::doPost( .d("reason", "curl_easy_performFailed") .d("result", result) .d("error", curl_easy_strerror(result))); - body.clear(); - return HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED; + return HTTPResponse(); } - long responseCode = 0; - result = curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &responseCode); + result = curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &response.code); if (result != CURLE_OK) { ACSDK_ERROR(LX("doPostFailed") .d("reason", "curl_easy_getinfoFailed") .d("property", "CURLINFO_RESPONSE_CODE") .d("result", result) .d("error", curl_easy_strerror(result))); - body.clear(); - return HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED; + return HTTPResponse(); } else { - ACSDK_DEBUG(LX("doPostSucceeded").d("code", responseCode)); - return responseCode; + ACSDK_DEBUG5(LX("doPostSucceeded").d("code", response.code)); + return response; + } +} + +std::string HttpPost::buildPostData(const std::vector>& data) const { + std::stringstream dataStream; + + for (auto ix = data.begin(); ix != data.end(); ix++) { + if (ix != data.begin()) { + dataStream << '&'; + } + dataStream << m_curl.urlEncode(ix->first) << '=' << m_curl.urlEncode(ix->second); } + + return dataStream.str(); } size_t HttpPost::staticWriteCallbackLocked(char* ptr, size_t size, size_t nmemb, void* userdata) { diff --git a/AVSCommon/Utils/src/LibcurlUtils/HttpPut.cpp b/AVSCommon/Utils/src/LibcurlUtils/HttpPut.cpp new file mode 100644 index 0000000000..394ac8aa2a --- /dev/null +++ b/AVSCommon/Utils/src/LibcurlUtils/HttpPut.cpp @@ -0,0 +1,156 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include +#include +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +using namespace alexaClientSDK::avsCommon::utils; + +/// String to identify log entries originating from this file. +static const std::string TAG("HttpPut"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/** + * Read callback function used for CURLOPT_READFUNCTION option in libcurl + */ +static size_t readCallback(char* dataBuffer, size_t blockSize, size_t numBlocks, void* dataStream); + +/** + * Write callback function used for CURLOPT_WRITEFUNCTION option in libcurl + */ +static size_t writeCallback(char* dataBuffer, size_t blockSize, size_t numBlocks, void* dataStream); + +std::unique_ptr HttpPut::create() { + std::unique_ptr httpPut(new HttpPut()); + if (httpPut->m_curl.isValid()) { + return httpPut; + } + return nullptr; +} + +HTTPResponse HttpPut::doPut(const std::string& url, const std::vector& headers, const std::string& data) { + std::lock_guard lock(m_mutex); + const std::string errorEvent = "doPutFailed"; + const std::string errorReasonKey = "reason"; + HTTPResponse httpResponse; + + if (!m_curl.reset()) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "unableToResetCurlHandle")); + return httpResponse; + } + + if (!m_curl.setURL(url)) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "unableToSetUrl")); + return httpResponse; + } + + if (!m_curl.setTransferType(CurlEasyHandleWrapper::TransferType::kPUT)) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "unableToSetHttpRequestType")); + return httpResponse; + } + + if (!m_curl.setopt(CURLOPT_INFILESIZE_LARGE, static_cast(data.length()))) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "unableToSetDataLength")); + return httpResponse; + } + + for (auto header : headers) { + if (!m_curl.addHTTPHeader(header)) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "unableToAddHttpHeader")); + return httpResponse; + } + } + + CallbackData requestData(data.c_str()); + if (!m_curl.setReadCallback(readCallback, &requestData)) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "unableToSetReadCallback")); + return httpResponse; + } + + CallbackData responseData; + if (!m_curl.setWriteCallback(writeCallback, &responseData)) { + ACSDK_ERROR(LX(errorEvent).d(errorReasonKey, "unableToSetWriteCallback")); + return httpResponse; + } + + CURLcode curlResult = m_curl.perform(); + if (curlResult != CURLE_OK) { + ACSDK_ERROR( + LX(errorEvent).d(errorReasonKey, "curlFailedWithCode: " + std::string(curl_easy_strerror(curlResult)))); + return httpResponse; + } + + size_t responseSize = responseData.getSize(); + if (responseSize > 0) { + std::vector responseBody(responseSize + 1, 0); + responseData.getData(responseBody.data(), responseSize); + httpResponse.body = std::string(responseBody.data()); + } else { + httpResponse.body = ""; + } + httpResponse.code = m_curl.getHTTPResponseCode(); + + return httpResponse; +} + +size_t readCallback(char* dataBuffer, size_t blockSize, size_t numBlocks, void* dataStream) { + if (!dataStream) { + ACSDK_ERROR(LX("readCallbackFailed").d("reason", "nullDataStream")); + return 0; + } + + CallbackData* callbackData = reinterpret_cast(dataStream); + if (callbackData->getSize() == 0) { + return 0; + } + size_t readSize = callbackData->getData(dataBuffer, blockSize * numBlocks); + callbackData->clearData(); + return readSize; +} + +size_t writeCallback(char* dataBuffer, size_t blockSize, size_t numBlocks, void* dataStream) { + if (!dataStream) { + ACSDK_ERROR(LX("writeCallbackFailed").d("reason", "nullDataStream")); + return 0; + } + + size_t realSize = blockSize * numBlocks; + CallbackData* callbackData = reinterpret_cast(dataStream); + + return callbackData->appendData(dataBuffer, realSize); +} + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp index 2e3c40bdcb..d80ba4939e 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -14,8 +14,12 @@ */ #include +#include +#include +#include #include +#include "AVSCommon/Utils/LibcurlUtils/CurlMultiHandleWrapper.h" #include #include #include @@ -25,6 +29,10 @@ namespace avsCommon { namespace utils { namespace libcurlUtils { +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils::http; +using namespace avsCommon::utils::libcurlUtils; + /// String to identify log entries originating from this file. static const std::string TAG("LibCurlHttpContentFetcher"); @@ -33,6 +41,14 @@ static const std::string TAG("LibCurlHttpContentFetcher"); * may also increase latency. */ static const std::chrono::milliseconds TIMEOUT_FOR_BLOCKING_WRITE = std::chrono::milliseconds(100); +/// Timeout for polling loops that check activities running on separate threads. +static const std::chrono::milliseconds WAIT_FOR_ACTIVITY_TIMEOUT{100}; +/// Timeout for curl connection. +static const auto TIMEOUT_CONNECTION = std::chrono::seconds(30); +/// Timeout to wait for a call to get body after getContent was called +static const std::chrono::minutes MAX_GET_BODY_WAIT{1}; +/// Timeout to wait for get header to complete +static const std::chrono::minutes MAX_GET_HEADER_WAIT{5}; /** * Create a LogEntry using this file's TAG and the specified event string. @@ -46,16 +62,21 @@ size_t LibCurlHttpContentFetcher::headerCallback(char* data, size_t size, size_t ACSDK_ERROR(LX("headerCallback").d("reason", "nullUserDataPointer")); return 0; } + auto fetcher = static_cast(userData); + + ACSDK_DEBUG9(LX(__func__).sensitive("url", fetcher->m_url).m("CALLED")); + + fetcher->stateTransition(State::FETCHING_HEADER, true); + std::string line(static_cast(data), size * nmemb); std::transform(line.begin(), line.end(), line.begin(), ::tolower); if (line.find("http") == 0) { // To find lines like: "HTTP/1.1 200 OK" std::istringstream iss(line); std::string httpVersion; - long statusCode; + int statusCode = 0; iss >> httpVersion >> statusCode; - LibCurlHttpContentFetcher* thisObject = static_cast(userData); - thisObject->m_lastStatusCode = statusCode; + fetcher->m_header.responseCode = intToHTTPResponseCode(statusCode); } else if (line.find("content-type") == 0) { // To find lines like: "Content-Type: audio/x-mpegurl; charset=utf-8" std::istringstream iss(line); @@ -68,8 +89,21 @@ size_t LibCurlHttpContentFetcher::headerCallback(char* data, size_t size, size_t // Remove characters after the separator ; contentType.erase(separator); } - LibCurlHttpContentFetcher* thisObject = static_cast(userData); - thisObject->m_lastContentType = contentType; + fetcher->m_header.contentType = contentType; + } else if (line.find("content-length") == 0) { + // To find lines like: "Content-Length: 12345" + std::istringstream iss(line); + std::string contentLengthBeginning; + iss >> contentLengthBeginning >> fetcher->m_header.contentLength; + ACSDK_DEBUG9(LX(__func__).d("type", "content-length").d("length", fetcher->m_header.contentLength)); + } else if (line.find("content-range") == 0) { + // To find lines like: "Content-Range: bytes 1000-3979/3980" + std::istringstream iss(line); + std::string contentRangeBeginning; + std::string contentUnit; + std::string range; + iss >> contentRangeBeginning >> contentUnit >> range; + ACSDK_DEBUG9(LX(__func__).d("type", "content-range").d("unit", contentUnit).d("range", range)); } return size * nmemb; } @@ -79,25 +113,50 @@ size_t LibCurlHttpContentFetcher::bodyCallback(char* data, size_t size, size_t n ACSDK_ERROR(LX("bodyCallback").d("reason", "nullUserDataPointer")); return 0; } - LibCurlHttpContentFetcher* thisObject = static_cast(userData); - if (thisObject->m_done) { + auto fetcher = static_cast(userData); + if (fetcher->m_done) { // In order to properly quit when downloading live content, which block forever when performing a GET request return 0; } - if (!thisObject->m_bodyCallbackBegan) { - thisObject->m_bodyCallbackBegan = true; - thisObject->m_statusCodePromise.set_value(thisObject->m_lastStatusCode); - thisObject->m_contentTypePromise.set_value(thisObject->m_lastContentType); + + if (State::FETCHING_HEADER == fetcher->getState()) { + ACSDK_DEBUG9(LX(__func__).sensitive("url", fetcher->m_url).m("End of header found.")); + fetcher->stateTransition(State::HEADER_DONE, true); + } + + // Waits until the content fetcher is shutting down or the @c getBody method gets called. + auto startTime = std::chrono::steady_clock::now(); + auto elapsedTime = std::chrono::steady_clock::now() - startTime; + while ((MAX_GET_BODY_WAIT > elapsedTime) && !fetcher->m_isShutdown && fetcher->waitingForBodyRequest()) { + std::this_thread::sleep_for(WAIT_FOR_ACTIVITY_TIMEOUT); + elapsedTime = std::chrono::steady_clock::now() - startTime; + } + if (MAX_GET_BODY_WAIT <= elapsedTime) { + ACSDK_ERROR(LX(__func__).d("reason", "getBodyCallWaitTimeout")); + fetcher->stateTransition(State::ERROR, false); + return 0; + } + if (fetcher->m_isShutdown) { + return 0; } - auto streamWriter = thisObject->m_streamWriter; + + fetcher->stateTransition(State::FETCHING_BODY, true); + + if (!fetcher->m_streamWriter) { + ACSDK_DEBUG9(LX(__func__).m("No writer received. Creating a new one.")); + // Using the url as the identifier for the attachment + auto stream = std::make_shared(fetcher->m_url); + fetcher->m_streamWriter = stream->createWriter(sds::WriterPolicy::BLOCKING); + } + + auto streamWriter = fetcher->m_streamWriter; size_t totalBytesWritten = 0; if (streamWriter) { size_t targetNumBytes = size * nmemb; - while (totalBytesWritten < targetNumBytes && !thisObject->m_done) { - avsCommon::avs::attachment::AttachmentWriter::WriteStatus writeStatus = - avsCommon::avs::attachment::AttachmentWriter::WriteStatus::OK; + while ((totalBytesWritten < targetNumBytes) && !fetcher->m_done) { + auto writeStatus = avsCommon::avs::attachment::AttachmentWriter::WriteStatus::OK; size_t numBytesWritten = streamWriter->write( data, @@ -120,10 +179,21 @@ size_t LibCurlHttpContentFetcher::bodyCallback(char* data, size_t size, size_t n ACSDK_ERROR(LX(__func__).d("unexpected return code", "OK_BUFFER_FULL")); return 0; } - ACSDK_ERROR(LX(__func__).m("unexpected writeStatus")); + ACSDK_ERROR(LX("UnexpectedWriteStatus").d("writeStatus", static_cast(writeStatus))); return 0; } } + + fetcher->m_totalContentReceivedLength += totalBytesWritten; + fetcher->m_currentContentReceivedLength += totalBytesWritten; + + ACSDK_DEBUG9(LX(__func__) + .d("totalContentReceived", fetcher->m_totalContentReceivedLength) + .d("contentLength", fetcher->m_header.contentLength) + .d("currentContentReceived", fetcher->m_currentContentReceivedLength) + .d("remaining", fetcher->m_header.contentLength - fetcher->m_currentContentReceivedLength) + .d("totalBytesWritten", totalBytesWritten)); + return totalBytesWritten; } @@ -132,41 +202,126 @@ size_t LibCurlHttpContentFetcher::noopCallback(char* data, size_t size, size_t n } LibCurlHttpContentFetcher::LibCurlHttpContentFetcher(const std::string& url) : + m_state{HTTPContentFetcherInterface::State::INITIALIZED}, m_url{url}, - m_bodyCallbackBegan{false}, - m_lastStatusCode{0}, - m_done{false} { + m_currentContentReceivedLength{0}, + m_totalContentReceivedLength{0}, + m_done{false}, + m_isShutdown{false} { m_hasObjectBeenUsed.clear(); + m_headerFuture = m_headerPromise.get_future(); +} + +HTTPContentFetcherInterface::State LibCurlHttpContentFetcher::getState() { + std::lock_guard lock(m_stateMutex); + return m_state; +} + +std::string LibCurlHttpContentFetcher::getUrl() const { + return m_url; +} + +HTTPContentFetcherInterface::Header LibCurlHttpContentFetcher::getHeader(std::atomic* shouldShutdown) { + // Creates a copy of the future for the current thread + auto localFuture = m_headerFuture; + auto startTime = std::chrono::steady_clock::now(); + auto elapsedTime = std::chrono::steady_clock::now() - startTime; + while ((MAX_GET_HEADER_WAIT > elapsedTime) && !m_isShutdown && (!shouldShutdown || !(*shouldShutdown))) { + if (State::ERROR == getState()) { + ACSDK_ERROR(LX(__func__).sensitive("URL", m_url).d("reason", "Invalid state").d("state", "ERROR")); + m_header.successful = false; + return m_header; + } + std::future_status status = localFuture.wait_for(std::chrono::milliseconds(WAIT_FOR_ACTIVITY_TIMEOUT)); + if (std::future_status::ready == status) { + break; + } + elapsedTime = std::chrono::steady_clock::now() - startTime; + } + if (MAX_GET_HEADER_WAIT <= elapsedTime) { + ACSDK_ERROR(LX("getHeaderFailed").d("reason", "waitTimeout")); + m_header.successful = false; + } + return m_header; +} + +bool LibCurlHttpContentFetcher::getBody(std::shared_ptr writer) { + std::lock_guard lock(m_getBodyMutex); + if (State::ERROR == getState()) { + return false; + } + if (!waitingForBodyRequest()) { + ACSDK_ERROR(LX(__func__).d("reason", "functionAlreadyCalled")); + return false; + } + m_streamWriter = writer; + stateTransition(State::FETCHING_BODY, true); + return true; +} + +void LibCurlHttpContentFetcher::shutdown() { + ACSDK_DEBUG9(LX(__func__).m("Shutting down")); + m_isShutdown = true; + stateTransition(State::BODY_DONE, true); } std::unique_ptr LibCurlHttpContentFetcher::getContent( FetchOptions fetchOption, - std::shared_ptr writer) { + std::unique_ptr attachmentWriter, + const std::vector& customHeaders) { if (m_hasObjectBeenUsed.test_and_set()) { + ACSDK_ERROR(LX("getContentFailed").d("reason", "Object has already been used")); + stateTransition(State::ERROR, false); return nullptr; } if (!m_curlWrapper.setURL(m_url)) { ACSDK_ERROR(LX("getContentFailed").d("reason", "failedToSetUrl")); + stateTransition(State::ERROR, false); return nullptr; } auto curlReturnValue = curl_easy_setopt(m_curlWrapper.getCurlHandle(), CURLOPT_FOLLOWLOCATION, 1L); if (curlReturnValue != CURLE_OK) { - ACSDK_ERROR(LX("getContentFailed").d("reason", "enableFollowRedirectsFailed")); + ACSDK_ERROR(LX("getContentFailed").d("reason", "enableFollowRedirectsFailed").d("error", curlReturnValue)); + stateTransition(State::ERROR, false); return nullptr; } curlReturnValue = curl_easy_setopt(m_curlWrapper.getCurlHandle(), CURLOPT_AUTOREFERER, 1L); if (curlReturnValue != CURLE_OK) { - ACSDK_ERROR(LX("getContentFailed").d("reason", "enableAutoReferralSettingToRedirectsFailed")); + ACSDK_ERROR(LX("getContentFailed") + .d("reason", "enableAutoReferralSettingToRedirectsFailed") + .d("error", curlReturnValue)); + stateTransition(State::ERROR, false); return nullptr; } // This enables the libcurl cookie engine, allowing it to send cookies curlReturnValue = curl_easy_setopt(m_curlWrapper.getCurlHandle(), CURLOPT_COOKIEFILE, ""); if (curlReturnValue != CURLE_OK) { - ACSDK_ERROR(LX("getContentFailed").d("reason", "enableLibCurlCookieEngineFailed")); + ACSDK_ERROR(LX("getContentFailed").d("reason", "enableLibCurlCookieEngineFailed").d("error", curlReturnValue)); + stateTransition(State::ERROR, false); + return nullptr; + } + if (!m_curlWrapper.setConnectionTimeout(TIMEOUT_CONNECTION)) { + ACSDK_ERROR(LX("getContentFailed").d("reason", "setConnectionTimeoutFailed")); + stateTransition(State::ERROR, false); + return nullptr; + } + + curl_slist* headerList = getCustomHeaderList(customHeaders); + if (headerList && curl_easy_setopt(m_curlWrapper.getCurlHandle(), CURLOPT_HTTPHEADER, headerList) != CURLE_OK) { + ACSDK_ERROR(LX("getContentFailed").d("reason", "setCustomHeadersFailed")); + stateTransition(State::ERROR, false); + return nullptr; + } + + curlReturnValue = curl_easy_setopt( + m_curlWrapper.getCurlHandle(), + CURLOPT_USERAGENT, + sdkInterfaces::HTTPContentFetcherInterface::getUserAgent().c_str()); + if (curlReturnValue != CURLE_OK) { + ACSDK_ERROR(LX("getContentFailed").d("reason", "setCurlOptRangeFailed").d("error", curlReturnValue)); + stateTransition(State::ERROR, false); return nullptr; } - auto httpStatusCodeFuture = m_statusCodePromise.get_future(); - auto contentTypeFuture = m_contentTypePromise.get_future(); std::shared_ptr stream = nullptr; @@ -185,91 +340,348 @@ std::unique_ptr LibCurlHttpContentFetcher::getCon ACSDK_ERROR(LX("getContentFailed").d("reason", "failedToSetCurlCallback")); return nullptr; } - m_thread = std::thread([this]() { - long finalResponseCode = 0; - char* contentType = nullptr; - auto curlReturnValue = curl_easy_perform(m_curlWrapper.getCurlHandle()); - if (curlReturnValue != CURLE_OK && curlReturnValue != CURLE_WRITE_ERROR) { - ACSDK_ERROR(LX("curlEasyPerformFailed").d("error", curl_easy_strerror(curlReturnValue))); - } - curlReturnValue = - curl_easy_getinfo(m_curlWrapper.getCurlHandle(), CURLINFO_RESPONSE_CODE, &finalResponseCode); - if (curlReturnValue != CURLE_OK) { - ACSDK_ERROR(LX("curlEasyGetInfoFailed").d("error", curl_easy_strerror(curlReturnValue))); + stateTransition(State::FETCHING_HEADER, true); + m_thread = std::thread([this, headerList]() { + auto curlMultiHandle = CurlMultiHandleWrapper::create(); + if (!curlMultiHandle) { + ACSDK_ERROR(LX("getContentFailed").d("reason", "curlMultiHandleWrapperCreateFailed")); + // Set the promises because of errors. + m_header.responseCode = HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED; + m_header.contentType = ""; + return; } - ACSDK_DEBUG9(LX("getContent").d("responseCode", finalResponseCode).sensitive("url", m_url)); - m_statusCodePromise.set_value(finalResponseCode); - curlReturnValue = curl_easy_getinfo(m_curlWrapper.getCurlHandle(), CURLINFO_CONTENT_TYPE, &contentType); - if (curlReturnValue == CURLE_OK && contentType) { - ACSDK_DEBUG9(LX("getContent").d("contentType", contentType).sensitive("url", m_url)); - m_contentTypePromise.set_value(std::string(contentType)); - } else { - ACSDK_ERROR(LX("curlEasyGetInfoFailed").d("error", curl_easy_strerror(curlReturnValue))); - ACSDK_ERROR(LX("getContent").d("contentType", "failedToGetContentType").sensitive("url", m_url)); - m_contentTypePromise.set_value(""); + curlMultiHandle->addHandle(m_curlWrapper.getCurlHandle()); + + int numTransfersLeft = 1; + HTTPResponseCode finalResponseCode = HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED; + char* contentType = nullptr; + + while (numTransfersLeft && !m_isShutdown) { + auto result = curlMultiHandle->perform(&numTransfersLeft); + if (CURLM_CALL_MULTI_PERFORM == result) { + continue; + } else if (CURLM_OK != result) { + ACSDK_ERROR(LX("getContentFailed").d("reason", "performFailed")); + break; + } + + int finalResponseCodeId = 0; + auto curlReturnValue = + curl_easy_getinfo(m_curlWrapper.getCurlHandle(), CURLINFO_RESPONSE_CODE, &finalResponseCodeId); + finalResponseCode = intToHTTPResponseCode(finalResponseCode); + if (curlReturnValue != CURLE_OK) { + ACSDK_ERROR(LX("curlEasyGetInfoFailed").d("error", curl_easy_strerror(curlReturnValue))); + break; + } + if (HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED != finalResponseCode && + !isRedirect(finalResponseCode)) { + ACSDK_DEBUG9(LX("getContent").d("responseCode", finalResponseCode).sensitive("url", m_url)); + curlReturnValue = + curl_easy_getinfo(m_curlWrapper.getCurlHandle(), CURLINFO_CONTENT_TYPE, &contentType); + if ((curlReturnValue == CURLE_OK) && contentType) { + ACSDK_DEBUG9(LX("getContent").d("contentType", contentType).sensitive("url", m_url)); + } else { + ACSDK_ERROR(LX("curlEasyGetInfoFailed").d("error", curl_easy_strerror(curlReturnValue))); + ACSDK_ERROR( + LX("getContent").d("contentType", "failedToGetContentType").sensitive("url", m_url)); + } + break; + } + + int numTransfersUpdated = 0; + result = curlMultiHandle->wait(WAIT_FOR_ACTIVITY_TIMEOUT, &numTransfersUpdated); + if (result != CURLM_OK) { + ACSDK_ERROR(LX("getContentFailed") + .d("reason", "multiWaitFailed") + .d("error", curl_multi_strerror(result))); + break; + } } + + // Free custom headers. + curl_slist_free_all(headerList); + + // Abort any curl operation by removing the curl handle. + curlMultiHandle->removeHandle(m_curlWrapper.getCurlHandle()); }); break; case FetchOptions::ENTIRE_BODY: - if (!writer) { - // Using the url as the identifier for the attachment - stream = std::make_shared(m_url); - writer = stream->createWriter(sds::WriterPolicy::BLOCKING); - writerWasCreatedLocally = true; - } - - m_streamWriter = writer; - if (!m_streamWriter) { - ACSDK_ERROR(LX("getContentFailed").d("reason", "failedToCreateWriter")); - return nullptr; - } + stateTransition(State::FETCHING_HEADER, true); if (!m_curlWrapper.setWriteCallback(bodyCallback, this)) { ACSDK_ERROR(LX("getContentFailed").d("reason", "failedToSetCurlBodyCallback")); + stateTransition(State::ERROR, false); return nullptr; } if (!m_curlWrapper.setHeaderCallback(headerCallback, this)) { ACSDK_ERROR(LX("getContentFailed").d("reason", "failedToSetCurlHeaderCallback")); + stateTransition(State::ERROR, false); return nullptr; } - m_thread = std::thread([this, writerWasCreatedLocally]() { - auto curlReturnValue = curl_easy_perform(m_curlWrapper.getCurlHandle()); - if (curlReturnValue != CURLE_OK) { - ACSDK_ERROR(LX("curlEasyPerformFailed").d("error", curl_easy_strerror(curlReturnValue))); + + m_thread = std::thread([this, writerWasCreatedLocally, headerList]() { + ACSDK_DEBUG9(LX("transferThread").sensitive("URL", m_url).m("start")); + auto curlMultiHandle = avsCommon::utils::libcurlUtils::CurlMultiHandleWrapper::create(); + if (!curlMultiHandle) { + ACSDK_ERROR(LX("getContentFailed").d("reason", "curlMultiHandleWrapperCreateFailed")); + // Set the promises because of errors. + m_header.responseCode = HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED; + m_header.contentType = ""; + stateTransition(State::ERROR, false); + return; } - if (!m_bodyCallbackBegan) { - m_statusCodePromise.set_value(m_lastStatusCode); - m_contentTypePromise.set_value(m_lastContentType); + curlMultiHandle->addHandle(m_curlWrapper.getCurlHandle()); + + int numTransfersLeft = 1; + while (numTransfersLeft && !m_isShutdown) { + ACSDK_DEBUG9(LX("transferThread").sensitive("URL", m_url).d("numTransfersLeft", numTransfersLeft)); + auto result = curlMultiHandle->perform(&numTransfersLeft); + if (CURLM_CALL_MULTI_PERFORM == result) { + continue; + } else if (CURLM_OK != result) { + ACSDK_ERROR(LX("getContentFailed").sensitive("URL", m_url).d("reason", "performFailed")); + stateTransition(State::ERROR, false); + return; + } + + int numTransfersUpdated = 0; + result = curlMultiHandle->wait(WAIT_FOR_ACTIVITY_TIMEOUT, &numTransfersUpdated); + if (result != CURLM_OK) { + ACSDK_ERROR(LX("getContentFailed") + .d("reason", "multiWaitFailed") + .d("error", curl_multi_strerror(result))); + stateTransition(State::ERROR, false); + return; + } + + auto bytesRemaining = m_header.contentLength - m_currentContentReceivedLength; + if (bytesRemaining < 0) { + bytesRemaining = 0; + } + + // There's still byte remaining to download, let's try to get the rest of the data by using range. + if ((numTransfersLeft == 0) && bytesRemaining > 0) { + // Remove the current curlHandle from the multiHandle first. + curlMultiHandle->removeHandle(m_curlWrapper.getCurlHandle()); + + // Reset the current content counters. + m_header.contentLength = 0; + m_currentContentReceivedLength = 0; + + // Set the range to start with total content that's been received so far to the end. + std::ostringstream ss; + ss << (m_totalContentReceivedLength) << "-"; + auto curlReturnValue = + curl_easy_setopt(m_curlWrapper.getCurlHandle(), CURLOPT_RANGE, ss.str().c_str()); + if (curlReturnValue != CURLE_OK) { + ACSDK_ERROR( + LX("getContentFailed").d("reason", "setUserAgentFailed").d("error", curlReturnValue)); + stateTransition(State::ERROR, false); + return; + } + + // Add the curlHandle back to the mutliHandle. + curlMultiHandle->addHandle(m_curlWrapper.getCurlHandle()); + + // Set this to 1 so that we will try to perform() again. + numTransfersLeft = 1; + + ACSDK_DEBUG9(LX(__func__) + .d("bytesRemaining", bytesRemaining) + .d("totalContentReceived", m_totalContentReceivedLength) + .d("restartingWithRange", ss.str())); + } } + /* * If the writer was created locally, its job is done and can be safely closed. */ if (writerWasCreatedLocally) { + ACSDK_DEBUG9(LX(__func__).m("Closing the writer")); m_streamWriter->close(); } /* - * Note: If the writer was not created locally, its owner must ensure that it closes when necessary. - * In the case of a livestream, if the writer is not closed the LibCurlHttpContentFetcher - * will continue to download data indefinitely. + * Note: If the writer was not created locally, its owner must ensure that it closes when + * necessary. In the case of a livestream, if the writer is not closed the + * LibCurlHttpContentFetcher will continue to download data indefinitely. */ - m_done = true; + + // Free custom headers. + curl_slist_free_all(headerList); + + // Abort any curl operation by removing the curl handle. + curlMultiHandle->removeHandle(m_curlWrapper.getCurlHandle()); + + if (State::INITIALIZED == getState() || State::ERROR == getState()) { + ACSDK_DEBUG9(LX("transferThread").sensitive("URL", m_url).m("end with error")); + stateTransition(State::ERROR, false); + } else { + ACSDK_DEBUG9(LX("transferThread").sensitive("URL", m_url).m("end")); + stateTransition(State::BODY_DONE, true); + } }); break; default: + stateTransition(State::ERROR, false); return nullptr; } - return avsCommon::utils::memory::make_unique( - avsCommon::utils::HTTPContent{std::move(httpStatusCodeFuture), std::move(contentTypeFuture), stream}); + return nullptr; +} + +curl_slist* LibCurlHttpContentFetcher::getCustomHeaderList(std::vector customHeaders) { + struct curl_slist* headers = nullptr; + for (const auto& header : customHeaders) { + ACSDK_DEBUG9(LX("getCustomHeaderList").d("header", header.c_str())); + headers = curl_slist_append(headers, header.c_str()); + } + return headers; } LibCurlHttpContentFetcher::~LibCurlHttpContentFetcher() { + ACSDK_DEBUG9(LX("~LibCurlHttpContentFetcher").sensitive("URL", m_url)); if (m_thread.joinable()) { + m_done = true; + m_isShutdown = true; + stateTransition(State::BODY_DONE, true); m_thread.join(); } } +void LibCurlHttpContentFetcher::reportInvalidStateTransitionAttempt(State currentState, State newState) { + ACSDK_ERROR(LX(__func__) + .d("currentState", currentState) + .d("newState", newState) + .m("An attempt was made to perform an invalid state transition.")); +} + +void LibCurlHttpContentFetcher::stateTransition(State newState, bool value) { + std::lock_guard lock(m_stateMutex); + switch (m_state) { + case State::INITIALIZED: + switch (newState) { + case State::INITIALIZED: + return; + case State::FETCHING_HEADER: + break; + case State::HEADER_DONE: + m_header.successful = value; + m_headerPromise.set_value(value); + break; + case State::FETCHING_BODY: + m_header.successful = value; + m_headerPromise.set_value(value); + break; + case State::BODY_DONE: + m_header.successful = value; + m_headerPromise.set_value(value); + break; + case State::ERROR: + m_header.successful = false; + m_headerPromise.set_value(false); + break; + } + break; + case State::FETCHING_HEADER: + switch (newState) { + case State::INITIALIZED: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::FETCHING_HEADER: + return; + case State::HEADER_DONE: + m_header.successful = value; + m_headerPromise.set_value(value); + break; + case State::FETCHING_BODY: + m_header.successful = value; + m_headerPromise.set_value(value); + break; + case State::BODY_DONE: + m_header.successful = value; + m_headerPromise.set_value(value); + break; + case State::ERROR: + m_header.successful = false; + m_headerPromise.set_value(false); + break; + } + break; + case State::HEADER_DONE: + switch (newState) { + case State::INITIALIZED: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::FETCHING_HEADER: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::HEADER_DONE: + return; + case State::FETCHING_BODY: + break; + case State::BODY_DONE: + break; + case State::ERROR: + break; + } + break; + case State::FETCHING_BODY: + switch (newState) { + case State::INITIALIZED: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::FETCHING_HEADER: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::HEADER_DONE: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::FETCHING_BODY: + return; + case State::BODY_DONE: + break; + case State::ERROR: + break; + } + break; + case State::BODY_DONE: + switch (newState) { + case State::INITIALIZED: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::FETCHING_HEADER: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::HEADER_DONE: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::FETCHING_BODY: + reportInvalidStateTransitionAttempt(m_state, newState); + return; + case State::BODY_DONE: + return; + case State::ERROR: + break; + } + break; + case State::ERROR: + return; + } + if (State::ERROR == newState) { + ACSDK_ERROR(LX(__func__).sensitive("URL", m_url).d("oldState", m_state).m("State transition to ERROR")); + } else { + ACSDK_DEBUG9( + LX(__func__).sensitive("URL", m_url).d("oldState", m_state).d("newState", newState).m("State transition")); + } + m_state = newState; +} + +bool LibCurlHttpContentFetcher::waitingForBodyRequest() { + State state = getState(); + return (State::INITIALIZED == state) || (State::FETCHING_HEADER == state) || (State::HEADER_DONE == state); +} + } // namespace libcurlUtils } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp new file mode 100644 index 0000000000..42428132ff --- /dev/null +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp @@ -0,0 +1,388 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include +#include + +#include "AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h" +#include "AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Request.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +using namespace avsCommon::utils::http2; +using namespace avsCommon::utils::libcurlUtils; + +/// String to identify log entries originating from this file. +static const std::string TAG("LibcurlHTTP2Connection"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// Timeout for curl_multi_wait +const static std::chrono::milliseconds WAIT_FOR_ACTIVITY_TIMEOUT(100); +/// Timeout for curl_multi_wait while all non-intermittent HTTP/2 streams are paused. +const static std::chrono::milliseconds WAIT_FOR_ACTIVITY_WHILE_STREAMS_PAUSED_TIMEOUT(10); + +#ifdef ACSDK_OPENSSL_MIN_VER_REQUIRED +/** + * This function checks the minimum version of OpenSSL required and prints a warning if the version is too old or + * the version string parsing failed. + */ +static void verifyOpenSslVersion(curl_version_info_data* data) { + // There are three numbers in OpenSSL version: major.minor.patch + static const int NUM_OF_VERSION_NUMBER = 3; + + unsigned int versionUsed[NUM_OF_VERSION_NUMBER]; + unsigned int minVersionRequired[NUM_OF_VERSION_NUMBER]; + + if (!data) { + ACSDK_ERROR(LX("verifyOpenSslVersionFailed").d("reason", "nullData")); + return; + } else if (!data->ssl_version) { + ACSDK_ERROR(LX("verifyOpenSslVersionFailed").d("reason", "nullSslVersion")); + return; + } + + // parse ssl_version + int matchedVersionUsed = + sscanf(data->ssl_version, "OpenSSL/%u.%u.%u", &versionUsed[0], &versionUsed[1], &versionUsed[2]); + + // parse minimum OpenSSL version required + int matchedVersionRequired = sscanf( + ACSDK_STRINGIFY(ACSDK_OPENSSL_MIN_VER_REQUIRED), + "%u.%u.%u", + &minVersionRequired[0], + &minVersionRequired[1], + &minVersionRequired[2]); + + if (matchedVersionUsed == matchedVersionRequired && matchedVersionUsed == NUM_OF_VERSION_NUMBER) { + bool versionRequirementFailed = false; + + for (int i = 0; i < NUM_OF_VERSION_NUMBER; i++) { + if (versionUsed[i] < minVersionRequired[i]) { + versionRequirementFailed = true; + break; + } else if (versionUsed[i] > minVersionRequired[i]) { + break; + } + } + if (versionRequirementFailed) { + ACSDK_WARN(LX("OpenSSL minimum version requirement failed!") + .d("version", data->ssl_version) + .d("required", ACSDK_STRINGIFY(ACSDK_OPENSSL_MIN_VER_REQUIRED))); + } + } else { + ACSDK_WARN(LX("Unable to parse OpenSSL version!") + .d("version", data->ssl_version) + .d("required", ACSDK_STRINGIFY(ACSDK_OPENSSL_MIN_VER_REQUIRED))); + } +} +#endif + +/** + * This function logs a warning if the version of curl is not recent enough for use with the ACL. + * @return true if check passes, false if it fails + */ +static bool performCurlChecks() { + curl_version_info_data* data = curl_version_info(CURLVERSION_NOW); + if (data && !(data->features & CURL_VERSION_HTTP2)) { + ACSDK_CRITICAL(LX("libcurl not built with HTTP/2 support!")); + return false; + } +#ifdef ACSDK_OPENSSL_MIN_VER_REQUIRED + verifyOpenSslVersion(data); +#endif + return true; +} + +LibcurlHTTP2Connection::LibcurlHTTP2Connection() : m_isStopping{false} { + m_networkThread = std::thread(&LibcurlHTTP2Connection::networkLoop, this); +} + +bool LibcurlHTTP2Connection::createMultiHandle() { + m_multi = CurlMultiHandleWrapper::create(); + if (!m_multi) { + ACSDK_ERROR(LX("initFailed").d("reason", "curlMultiHandleWrapperCreateFailed")); + return false; + } + if (curl_multi_setopt(m_multi->getCurlHandle(), CURLMOPT_PIPELINING, 2L) != CURLM_OK) { + m_multi.reset(); + ACSDK_ERROR(LX("initFailed").d("reason", "enableHTTP2PipeliningFailed")); + return false; + } + + return true; +} + +std::shared_ptr LibcurlHTTP2Connection::create() { + if (!performCurlChecks()) { + return nullptr; + } + return std::shared_ptr(new LibcurlHTTP2Connection()); +} + +LibcurlHTTP2Connection::~LibcurlHTTP2Connection() { + ACSDK_DEBUG5(LX(__func__)); + disconnect(); +} + +bool LibcurlHTTP2Connection::isStopping() { + std::lock_guard lock(m_mutex); + return m_isStopping; +} + +void LibcurlHTTP2Connection::setIsStopping() { + std::lock_guard lock(m_mutex); + m_isStopping = true; + m_cv.notify_one(); +} + +std::shared_ptr LibcurlHTTP2Connection::dequeueRequest() { + std::lock_guard lock(m_mutex); + if (m_isStopping || m_requestQueue.empty()) { + return nullptr; + } + auto result = m_requestQueue.front(); + m_requestQueue.pop_front(); + return result; +} + +void LibcurlHTTP2Connection::processNextRequest() { + auto stream = dequeueRequest(); + if (!stream) { + return; + } + stream->setTimeOfLastTransfer(); + auto result = m_multi->addHandle(stream->getCurlHandle()); + if (CURLM_OK == result) { + auto handle = stream->getCurlHandle(); + ACSDK_DEBUG9(LX("insertActiveStream").d("handle", handle).d("streamId", stream->getId())); + m_activeStreams[handle] = stream; + } else { + ACSDK_ERROR(LX("processNextRequest").d("reason", "addHandleFailed").d("error", curl_multi_strerror(result))); + stream->reportCompletion(HTTP2ResponseFinishedStatus::INTERNAL_ERROR); + } +} + +void LibcurlHTTP2Connection::networkLoop() { + ACSDK_DEBUG5(LX(__func__)); + + while (!isStopping()) { + if (!createMultiHandle()) { + setIsStopping(); + break; + } + { + // wait until we have at least one request + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [this] { return m_isStopping || !m_requestQueue.empty(); }); + if (m_isStopping) { + break; + } + } + + processNextRequest(); + + int numTransfersLeft = 1; // just dequeued the first request. + // Call perform repeatedly to transfer data on active streams. + while (numTransfersLeft > 0 && !isStopping()) { + auto result = m_multi->perform(&numTransfersLeft); + if (result == CURLM_CALL_MULTI_PERFORM) { + continue; + } + if (result != CURLM_OK) { + ACSDK_ERROR(LX("networkLoopStopping").d("reason", "performFailed")); + setIsStopping(); + break; + } + + cleanupFinishedStreams(); + cleanupCancelledAndStalledStreams(); + + if (isStopping()) { + break; + } + + processNextRequest(); + + auto before = std::chrono::time_point::max(); + auto multiWaitTimeout = WAIT_FOR_ACTIVITY_TIMEOUT; + bool paused = areStreamsPaused(); + if (paused) { + multiWaitTimeout = WAIT_FOR_ACTIVITY_WHILE_STREAMS_PAUSED_TIMEOUT; + before = std::chrono::steady_clock::now(); + } + + int numTransfersUpdated = 0; + result = m_multi->wait(multiWaitTimeout, &numTransfersUpdated); + if (result != CURLM_OK) { + ACSDK_ERROR( + LX("networkLoopStopping").d("reason", "multiWaitFailed").d("error", curl_multi_strerror(result))); + setIsStopping(); + break; + } + + // @note curl_multi_wait will return immediately even if all streams are paused, because HTTP/2 streams + // are full-duplex - so activity may have occurred on the other side. Therefore, if our intent is to pause + // transfers to give the readers / writers time to catch up, we must perform a local sleep of our own. + if (paused) { + auto after = std::chrono::steady_clock::now(); + auto elapsed = after - before; + auto remaining = multiWaitTimeout - elapsed; + + // sanity check that remainingMs is valid before performing a sleep. + if (remaining.count() > 0 && remaining <= WAIT_FOR_ACTIVITY_WHILE_STREAMS_PAUSED_TIMEOUT) { + std::this_thread::sleep_for(remaining); + } + } + unPauseActiveStreams(); + } + cancelAllStreams(); + m_multi.reset(); + } + + ACSDK_DEBUG5(LX("networkLoopExiting")); +} + +std::shared_ptr LibcurlHTTP2Connection::createAndSendRequest(const HTTP2RequestConfig& config) { + auto req = std::make_shared(config, config.getId()); + addStream(req); + return req; +} + +void LibcurlHTTP2Connection::disconnect() { + ACSDK_DEBUG5(LX(__func__)); + setIsStopping(); + if (m_networkThread.joinable()) { + m_networkThread.join(); + } +} + +bool LibcurlHTTP2Connection::addStream(std::shared_ptr stream) { + if (!stream) { + ACSDK_ERROR(LX("addStream").d("failed", "null stream")); + return false; + } + std::lock_guard lock(m_mutex); + if (m_isStopping) { + ACSDK_ERROR(LX("addStream").d("failed", "network loop stopping")); + return false; + } + m_requestQueue.push_back(std::move(stream)); + m_cv.notify_one(); + return true; +} + +void LibcurlHTTP2Connection::cleanupFinishedStreams() { + CURLMsg* message = nullptr; + do { + int messagesLeft = 0; + message = m_multi->infoRead(&messagesLeft); + if (message && message->msg == CURLMSG_DONE) { + auto it = m_activeStreams.find(message->easy_handle); + if (it != m_activeStreams.end()) { + it->second->reportResponseCode(); + + if (CURLE_OPERATION_TIMEDOUT == message->data.result) { + it->second->reportCompletion(HTTP2ResponseFinishedStatus::TIMEOUT); + } else { + it->second->reportCompletion(HTTP2ResponseFinishedStatus::COMPLETE); + } + ACSDK_DEBUG(LX("streamFinished") + .d("streamId", it->second->getId()) + .d("result", curl_easy_strerror(message->data.result)) + .d("CURLcode", message->data.result)); + releaseStream(*(it->second)); + } else { + ACSDK_ERROR( + LX("cleanupFinishedStreamError").d("reason", "streamNotFound").d("handle", message->easy_handle)); + } + } + } while (message); +} + +void LibcurlHTTP2Connection::cleanupCancelledAndStalledStreams() { + auto it = m_activeStreams.begin(); + while (it != m_activeStreams.end()) { + auto stream = (it++)->second; + if (stream->isCancelled()) { + cancelStream(*stream); + } else if (stream->hasProgressTimedOut()) { + ACSDK_WARN(LX("streamProgressTimedOut").d("streamId", stream->getId())); + stream->reportCompletion(HTTP2ResponseFinishedStatus::TIMEOUT); + releaseStream(*stream); + } + } +} + +bool LibcurlHTTP2Connection::areStreamsPaused() { + size_t numberNonIntermittentStreams = 0; + size_t numberPausedStreams = 0; + for (const auto& entry : m_activeStreams) { + const auto& stream = entry.second; + if (!stream->isIntermittentTransferExpected()) { + numberNonIntermittentStreams++; + if (entry.second->isPaused()) { + numberPausedStreams++; + } + } + } + return numberPausedStreams > 0 && (numberPausedStreams == numberNonIntermittentStreams); +} + +void LibcurlHTTP2Connection::unPauseActiveStreams() { + for (auto& stream : m_activeStreams) { + stream.second->unPause(); + } +} + +bool LibcurlHTTP2Connection::cancelStream(LibcurlHTTP2Request& stream) { + ACSDK_INFO(LX("cancelStream").d("streamId", stream.getId())); + stream.reportCompletion(HTTP2ResponseFinishedStatus::CANCELLED); + return releaseStream(stream); +} + +void LibcurlHTTP2Connection::cancelAllStreams() { + auto it = m_activeStreams.begin(); + while (it != m_activeStreams.end()) { + cancelStream(*(it++)->second); + } +} + +bool LibcurlHTTP2Connection::releaseStream(LibcurlHTTP2Request& stream) { + auto handle = stream.getCurlHandle(); + ACSDK_DEBUG9(LX("releaseStream").d("streamId", stream.getId())); + auto result = m_multi->removeHandle(handle); + m_activeStreams.erase(handle); + if (result != CURLM_OK) { + ACSDK_ERROR(LX("releaseStreamFailed").d("reason", "removeHandleFailed").d("streamId", stream.getId())); + return false; + } + return true; +} + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2ConnectionFactory.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2ConnectionFactory.cpp new file mode 100644 index 0000000000..c3a41be463 --- /dev/null +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2ConnectionFactory.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include "AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h" +#include "AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2ConnectionFactory.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +/// String to identify log entries originating from this file. +static const std::string TAG("LibcurlHTTP2ConnectionFactory"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +std::shared_ptr LibcurlHTTP2ConnectionFactory:: + createHTTP2Connection() { + ACSDK_DEBUG5(LX(__func__)); + auto result = LibcurlHTTP2Connection::create(); + return result; +} + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp new file mode 100644 index 0000000000..402348abf6 --- /dev/null +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp @@ -0,0 +1,235 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include "AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Request.h" +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { + +using namespace avsCommon::utils::http2; +using namespace std::chrono; + +static constexpr long INVALID_RESPONSE_CODE = -1L; + +/// String to identify log entries originating from this file. +static const std::string TAG("LibcurlHTTP2Request"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +size_t LibcurlHTTP2Request::writeCallback(char* data, size_t size, size_t nmemb, void* userData) { + if (!userData) { + ACSDK_ERROR(LX(__func__).d("reason", "nullUserData")); + return CURLE_WRITE_ERROR; + } + + LibcurlHTTP2Request* stream = static_cast(userData); + ACSDK_DEBUG9(LX(__func__).d("id", stream->getId()).d("size", size).d("nmemb", nmemb).d("userData", userData)); + + stream->setTimeOfLastTransfer(); + stream->reportResponseCode(); + + auto length = size * nmemb; + if (length != 0 && stream->m_sink) { + auto result = stream->m_sink->onReceiveData(data, length); + switch (result) { + case HTTP2ReceiveDataStatus::SUCCESS: + return length; + case HTTP2ReceiveDataStatus::PAUSE: + stream->m_isPaused = true; + return CURL_WRITEFUNC_PAUSE; + case HTTP2ReceiveDataStatus ::ABORT: + return 0; + } + } + + return 0; +} + +size_t LibcurlHTTP2Request::headerCallback(char* data, size_t size, size_t nmemb, void* userData) { + if (!userData) { + ACSDK_ERROR(LX("headerCallbackFailed").d("reason", "nullUserData")); + return 0; + } + + LibcurlHTTP2Request* stream = static_cast(userData); + ACSDK_DEBUG9(LX(__func__).d("id", stream->getId()).d("size", size).d("nmemb", nmemb).d("userData", userData)); + + stream->setTimeOfLastTransfer(); + stream->reportResponseCode(); + + auto length = size * nmemb; + if (length != 0 && stream->m_sink) { + std::string line(data, length); + stream->m_sink->onReceiveHeaderLine(line); + } + + return length; +} + +size_t LibcurlHTTP2Request::readCallback(char* data, size_t size, size_t nmemb, void* userData) { + if (!userData) { + ACSDK_ERROR(LX("readCallback").d("reason", "nullUserData")); + return CURLE_READ_ERROR; + } + + LibcurlHTTP2Request* stream = static_cast(userData); + ACSDK_DEBUG9(LX(__func__).d("id", stream->getId()).d("size", size).d("nmemb", nmemb).d("userData", userData)); + + stream->setTimeOfLastTransfer(); + + auto length = size * nmemb; + if (length != 0 && stream->m_source) { + auto result = stream->m_source->onSendData(data, length); + switch (result.status) { + case HTTP2SendStatus::CONTINUE: + return result.size; + case HTTP2SendStatus::PAUSE: + stream->m_isPaused = true; + return CURL_READFUNC_PAUSE; + case HTTP2SendStatus::COMPLETE: + return 0; + case HTTP2SendStatus::ABORT: + return CURL_READFUNC_ABORT; + } + } + + return CURL_READFUNC_ABORT; +} + +long LibcurlHTTP2Request::getResponseCode() { + long responseCode = 0; + CURLcode ret = curl_easy_getinfo(m_stream.getCurlHandle(), CURLINFO_RESPONSE_CODE, &responseCode); + if (ret != CURLE_OK) { + ACSDK_ERROR(LX("getResponseCodeFailed") + .d("reason", "curlFailure") + .d("method", "curl_easy_getinfo") + .d("info", "CURLINFO_RESPONSE_CODE") + .d("error", curl_easy_strerror(ret))); + return INVALID_RESPONSE_CODE; + } + return responseCode; +} + +CURL* LibcurlHTTP2Request::getCurlHandle() { + return m_stream.getCurlHandle(); +} + +LibcurlHTTP2Request::LibcurlHTTP2Request( + const alexaClientSDK::avsCommon::utils::http2::HTTP2RequestConfig& config, + std::string id) : + m_responseCodeReported{false}, + m_activityTimeout{milliseconds::zero()}, + m_timeOfLastTransfer{steady_clock::now()}, + m_stream{std::move(id)}, + m_isIntermittentTransferExpected{config.isIntermittentTransferExpected()}, + m_isPaused{false}, + m_isCancelled{false} { + switch (config.getRequestType()) { + case HTTP2RequestType::GET: + m_stream.setTransferType(CurlEasyHandleWrapper::TransferType::kGET); + break; + case HTTP2RequestType::POST: + curl_easy_setopt(m_stream.getCurlHandle(), CURLOPT_POST, 1L); + m_stream.setReadCallback(LibcurlHTTP2Request::readCallback, this); + break; + } + m_stream.setURL(config.getUrl()); + m_stream.setWriteCallback(LibcurlHTTP2Request::writeCallback, this); + m_stream.setHeaderCallback(LibcurlHTTP2Request::headerCallback, this); + m_stream.setopt(CURLOPT_TCP_KEEPALIVE, 1); + m_stream.setopt(CURLOPT_STREAM_WEIGHT, config.getPriority()); + + if (config.getSource()) { + m_source = config.getSource(); + auto headers = m_source->getRequestHeaderLines(); + for (const auto& header : headers) { + m_stream.addHTTPHeader(header); + } + } + if (config.getSink()) { + m_sink = config.getSink(); + } + if (config.getConnectionTimeout() != std::chrono::milliseconds::zero()) { + m_stream.setopt(CURLOPT_CONNECTTIMEOUT_MS, config.getConnectionTimeout().count()); + } + if (config.getTransferTimeout() != std::chrono::milliseconds::zero()) { + m_stream.setopt(CURLOPT_TIMEOUT_MS, config.getTransferTimeout().count()); + } + if (config.getActivityTimeout() != std::chrono::milliseconds::zero()) { + m_activityTimeout = config.getActivityTimeout(); + } +}; + +bool LibcurlHTTP2Request::hasProgressTimedOut() const { + if (m_activityTimeout == milliseconds::zero()) { + return false; // no activity timeout checks + } + return duration_cast(steady_clock::now() - m_timeOfLastTransfer) > m_activityTimeout; +} +bool LibcurlHTTP2Request::isIntermittentTransferExpected() const { + return m_isIntermittentTransferExpected; +} + +void LibcurlHTTP2Request::unPause() { + m_isPaused = false; + m_stream.pause(CURLPAUSE_CONT); +} + +bool LibcurlHTTP2Request::isPaused() const { + return m_isPaused; +} + +bool LibcurlHTTP2Request::isCancelled() const { + return m_isCancelled; +} + +bool LibcurlHTTP2Request::cancel() { + m_isCancelled = true; + return true; +} + +std::string LibcurlHTTP2Request::getId() const { + return m_stream.getId(); +} + +void LibcurlHTTP2Request::reportCompletion(HTTP2ResponseFinishedStatus status) { + if (m_sink) { + m_sink->onResponseFinished(status); + } +} + +void LibcurlHTTP2Request::reportResponseCode() { + if (m_responseCodeReported || !m_sink) { + return; + } + if (long responseCode = getResponseCode()) { + m_sink->onReceiveResponseCode(responseCode); + m_responseCodeReported = true; + } +} + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/Logger/LogEntry.cpp b/AVSCommon/Utils/src/Logger/LogEntry.cpp index cfb2f3aa5c..d8843eeff9 100644 --- a/AVSCommon/Utils/src/Logger/LogEntry.cpp +++ b/AVSCommon/Utils/src/Logger/LogEntry.cpp @@ -13,9 +13,10 @@ * permissions and limitations under the License. */ +#include "AVSCommon/Utils/Logger/LogEntry.h" + #include #include -#include "AVSCommon/Utils/Logger/LogEntry.h" namespace alexaClientSDK { namespace avsCommon { @@ -53,31 +54,55 @@ static const std::string BOOL_TRUE = "true"; static const std::string BOOL_FALSE = "false"; LogEntry::LogEntry(const std::string& source, const char* event) : m_hasMetadata(false) { - m_stream << source << SECTION_SEPARATOR << event; + m_stream << source << SECTION_SEPARATOR; + if (event) { + m_stream << event; + } } LogEntry::LogEntry(const std::string& source, const std::string& event) : m_hasMetadata(false) { m_stream << source << SECTION_SEPARATOR << event; } +LogEntry& LogEntry::d(const std::string& key, const char* value) { + return d(key.c_str(), value); +} + +LogEntry& LogEntry::d(const char* key, char* value) { + return d(key, static_cast(value)); +} + LogEntry& LogEntry::d(const char* key, const char* value) { prefixKeyValuePair(); + if (!key) { + key = ""; + } m_stream << key << KEY_VALUE_SEPARATOR; appendEscapedString(value); return *this; } +LogEntry& LogEntry::d(const std::string& key, const std::string& value) { + return d(key.c_str(), value.c_str()); +} + LogEntry& LogEntry::d(const char* key, const std::string& value) { return d(key, value.c_str()); } +LogEntry& LogEntry::d(const std::string& key, bool value) { + return d(key.c_str(), value); +} + LogEntry& LogEntry::d(const char* key, bool value) { return d(key, value ? BOOL_TRUE : BOOL_FALSE); } LogEntry& LogEntry::m(const char* message) { prefixMessage(); - m_stream << message; + if (message) { + m_stream << message; + } return *this; } @@ -108,6 +133,9 @@ void LogEntry::prefixMessage() { } void LogEntry::appendEscapedString(const char* in) { + if (!in) { + return; + } auto pos = in; // A little insurance against an infinite loop. auto maxCount = strlen(in); diff --git a/AVSCommon/Utils/src/Logger/Logger.cpp b/AVSCommon/Utils/src/Logger/Logger.cpp index 5795cd5d43..0a527d2444 100644 --- a/AVSCommon/Utils/src/Logger/Logger.cpp +++ b/AVSCommon/Utils/src/Logger/Logger.cpp @@ -29,6 +29,8 @@ static const std::string CONFIG_KEY_LOGGER = "logger"; /// Configuration key for "logLevel" values under "logger" and other per-module objects. static const std::string CONFIG_KEY_LOG_LEVEL = "logLevel"; +static constexpr auto AT_EXIT_THREAD_ID = "0"; + Logger::Logger(Level level) : m_level{level} { } @@ -110,6 +112,12 @@ void Logger::notifyObserversOnLogLevelChanged() { } } +void Logger::logAtExit(Level level, const LogEntry& entry) { + if (shouldLog(level)) { + emit(level, std::chrono::system_clock::now(), AT_EXIT_THREAD_ID, entry.c_str()); + } +} + } // namespace logger } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/Logger/ThreadMoniker.cpp b/AVSCommon/Utils/src/Logger/ThreadMoniker.cpp index 0b0fcd173a..5fc50ba043 100644 --- a/AVSCommon/Utils/src/Logger/ThreadMoniker.cpp +++ b/AVSCommon/Utils/src/Logger/ThreadMoniker.cpp @@ -15,8 +15,11 @@ #include #include +#include #include #include +#include + #include "AVSCommon/Utils/Logger/ThreadMoniker.h" namespace alexaClientSDK { @@ -24,15 +27,39 @@ namespace avsCommon { namespace utils { namespace logger { -thread_local ThreadMoniker ThreadMoniker::m_threadMoniker; - /// Counter to generate (small) unique thread monikers. static std::atomic g_nextThreadMoniker(1); -ThreadMoniker::ThreadMoniker() { +ThreadMoniker::ThreadMoniker(const std::string& moniker) : m_moniker{moniker.empty() ? generateMoniker() : moniker} { +} + +std::string ThreadMoniker::generateMoniker() { std::ostringstream stream; stream << std::setw(3) << std::hex << std::right << g_nextThreadMoniker++; - m_moniker = stream.str(); + return stream.str(); +} + +const ThreadMoniker& ThreadMoniker::getMonikerObjectFromMap(const std::string& moniker) { + /// Map each thread to a moniker. + static std::unordered_map threadMonikers; + /// Map each moniker to a thread. + static std::unordered_map monikerThreads; + /// Lock used to synchronize access to the local maps. + static std::mutex monikerMutex; + + std::lock_guard lock{monikerMutex}; + auto id = std::this_thread::get_id(); + auto entry = threadMonikers.find(id); + if (entry == threadMonikers.end()) { + auto oldEntry = monikerThreads.find(moniker); + if (oldEntry != monikerThreads.end()) { + threadMonikers.erase(oldEntry->second); + } + auto& object = threadMonikers.emplace(std::make_pair(id, ThreadMoniker(moniker))).first->second; + monikerThreads[object.m_moniker] = id; + return object; + } + return entry->second; } } // namespace logger diff --git a/AVSCommon/Utils/src/MacAddressString.cpp b/AVSCommon/Utils/src/MacAddressString.cpp new file mode 100644 index 0000000000..c0c61ea61f --- /dev/null +++ b/AVSCommon/Utils/src/MacAddressString.cpp @@ -0,0 +1,70 @@ +/* + * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { + +std::unique_ptr MacAddressString::create(const std::string& macAddress) { + enum class State { FIRST_OCTET_BYTE, SECOND_OCTET_BYTE, DIVIDER }; + + State parseState = State::FIRST_OCTET_BYTE; + int numOctets = 0; + for (const auto& c : macAddress) { + switch (parseState) { + case State::FIRST_OCTET_BYTE: + if (!std::isxdigit(c)) { + return nullptr; + } + parseState = State::SECOND_OCTET_BYTE; + break; + case State::SECOND_OCTET_BYTE: + if (!std::isxdigit(c)) { + return nullptr; + } + parseState = State::DIVIDER; + ++numOctets; + break; + case State::DIVIDER: + if ((':' != c) && ('-' != c)) { + return nullptr; + } + parseState = State::FIRST_OCTET_BYTE; + break; + } + } + + if ((6 != numOctets) || (parseState != State::DIVIDER)) { + return nullptr; + } + + return std::unique_ptr(new MacAddressString(macAddress)); +} + +std::string MacAddressString::getString() const { + return m_macAddress; +} + +MacAddressString::MacAddressString(const std::string& macAddress) : m_macAddress(macAddress) { +} + +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp b/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp new file mode 100644 index 0000000000..1404bda621 --- /dev/null +++ b/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp @@ -0,0 +1,237 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "AVSCommon/Utils/Network/InternetConnectionMonitor.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace network { + +using namespace avsCommon::avs::attachment; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils::sds; +using namespace utils::timing; + +/// String to identify log entries originating from this file. +static const std::string TAG("InternetConnectionMonitor"); + +/** + * Create a @c LogEntry using this file's @c TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// The number of bytes read from the attachment with each read in the read loop. +static const size_t CHUNK_SIZE(1024); + +/// The amount of time to wait before retesting for internet connection availability. +static const std::chrono::minutes DEFAULT_TEST_PERIOD{5}; + +/// Timeout for polling loops that check shutdown. +static const std::chrono::milliseconds WAIT_FOR_ACTIVITY_TIMEOUT{100}; + +/// The URL to fetch content from. +static const std::string S3_TEST_URL = "http://spectrum.s3.amazonaws.com/kindle-wifi/wifistub.html"; + +/// The string that will serve as validation that the HTTP content was received correctly. +static const std::string VALIDATION_STRING = "81ce4465-7167-4dcb-835b-dcc9e44c112a"; + +/// Process attachment ID prefix +static const std::string PROCESS_ATTACHMENT_ID_PREFIX = "download:"; + +std::unique_ptr InternetConnectionMonitor::create( + std::shared_ptr contentFetcherFactory) { + if (!contentFetcherFactory) { + ACSDK_ERROR(LX("createFailed").d("reason", "contentFetcherFactory was nullptr")); + return nullptr; + } + + return std::unique_ptr(new InternetConnectionMonitor(contentFetcherFactory)); +} + +InternetConnectionMonitor::InternetConnectionMonitor( + std::shared_ptr contentFetcherFactory) : + m_connected{false}, + m_period{DEFAULT_TEST_PERIOD}, + m_isShuttingDown{false}, + m_contentFetcherFactory{contentFetcherFactory} { + startMonitoring(); +} + +void InternetConnectionMonitor::addInternetConnectionObserver( + std::shared_ptr observer) { + if (!observer) { + ACSDK_ERROR(LX("addObserverFailed").d("reason", "nullObserver")); + return; + } + + bool connectedCopy = false; + bool observerAddedSuccessfully = false; + + { + std::unique_lock lock{m_mutex}; + connectedCopy = m_connected; + observerAddedSuccessfully = m_observers.insert(observer).second; + } + + // Inform the new observer of the current connection status. + if (observerAddedSuccessfully) { + observer->onConnectionStatusChanged(connectedCopy); + } +} + +void InternetConnectionMonitor::removeInternetConnectionObserver( + std::shared_ptr observer) { + if (!observer) { + ACSDK_ERROR(LX("removeObserverFailed").d("reason", "nullObserver")); + return; + } + + std::lock_guard lock{m_mutex}; + m_observers.erase(observer); +} + +void InternetConnectionMonitor::startMonitoring() { + ACSDK_DEBUG5(LX(__func__)); + + // Start a timer with an initial delay of 0 to kick off the first test, then + // allow the subsequent tests to start after m_period. + m_connectionTestTimer.start( + std::chrono::seconds(0), + m_period, + Timer::PeriodType::RELATIVE, + Timer::FOREVER, + std::bind(&InternetConnectionMonitor::testConnection, this)); +} + +void InternetConnectionMonitor::stopMonitoring() { + ACSDK_DEBUG5(LX(__func__)); + m_isShuttingDown = true; + m_connectionTestTimer.stop(); +} + +void InternetConnectionMonitor::testConnection() { + ACSDK_DEBUG5(LX(__func__)); + + auto contentFetcher = m_contentFetcherFactory->create(S3_TEST_URL); + auto httpContent = contentFetcher->getContent(HTTPContentFetcherInterface::FetchOptions::ENTIRE_BODY); + + auto stream = std::make_shared(PROCESS_ATTACHMENT_ID_PREFIX + S3_TEST_URL); + std::shared_ptr streamWriter = stream->createWriter(WriterPolicy::BLOCKING); + + HTTPContentFetcherInterface::Header header = contentFetcher->getHeader(&m_isShuttingDown); + + if (!header.successful) { + ACSDK_ERROR(LX("testConnectionFailed").d("reason", "contentFetcherCouldNotDownloadHeader")); + updateConnectionStatus(false); + return; + } + + ACSDK_DEBUG9(LX(__func__).d("contentLength", header.contentLength)); + + contentFetcher->getBody(streamWriter); + + HTTPContentFetcherInterface::State contentFetcherState = contentFetcher->getState(); + while (!m_isShuttingDown && (HTTPContentFetcherInterface::State::BODY_DONE != contentFetcherState) && + (HTTPContentFetcherInterface::State::ERROR != contentFetcherState)) { + std::this_thread::sleep_for(WAIT_FOR_ACTIVITY_TIMEOUT); + contentFetcherState = contentFetcher->getState(); + } + if (m_isShuttingDown) { + return; + } + if (HTTPContentFetcherInterface::State::ERROR == contentFetcherState) { + ACSDK_ERROR(LX("testConnectionFailed").d("reason", "contentFetcherCouldNotDownloadBody")); + updateConnectionStatus(false); + return; + } + + std::unique_ptr reader = stream->createReader(ReaderPolicy::NONBLOCKING); + + if (!reader) { + ACSDK_ERROR(LX("testConnectionFailed").d("reason", "failedToCreateStreamReader")); + updateConnectionStatus(false); + return; + } + auto readStatus = avs::attachment::AttachmentReader::ReadStatus::OK; + std::string testContent; + std::vector buffer(CHUNK_SIZE, 0); + bool streamClosed = false; + ssize_t bytesReadSoFar = 0; + while (!m_isShuttingDown && !streamClosed && (bytesReadSoFar < header.contentLength)) { + size_t bytesRead = reader->read(buffer.data(), buffer.size(), &readStatus); + bytesReadSoFar += bytesRead; + switch (readStatus) { + case avs::attachment::AttachmentReader::ReadStatus::CLOSED: + streamClosed = true; + if (0 == bytesRead) { + break; + } + /* FALL THROUGH - to add any data received even if closed */ + case avs::attachment::AttachmentReader::ReadStatus::OK: + case avs::attachment::AttachmentReader::ReadStatus::OK_WOULDBLOCK: + case avs::attachment::AttachmentReader::ReadStatus::OK_TIMEDOUT: + testContent.append(buffer.data(), bytesRead); + break; + case avs::attachment::AttachmentReader::ReadStatus::OK_OVERRUN_RESET: + // Current AttachmentReader policy renders this outcome impossible. + ACSDK_ERROR(LX("testConnectionFailed").d("reason", "overrunReset")); + break; + case avs::attachment::AttachmentReader::ReadStatus::ERROR_OVERRUN: + case avs::attachment::AttachmentReader::ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE: + case avs::attachment::AttachmentReader::ReadStatus::ERROR_INTERNAL: + ACSDK_ERROR(LX("testConnectionFailed").d("reason", "readError")); + updateConnectionStatus(false); + return; + } + if (bytesReadSoFar >= header.contentLength) { + ACSDK_DEBUG9(LX(__func__).m("alreadyReadAllBytes")); + } + } + + ACSDK_DEBUG9(LX(__func__).m("Finished reading")); + + // Check that the HTTP content received is what we expected. + bool found = (testContent.find(VALIDATION_STRING) != std::string::npos); + updateConnectionStatus(found); +} + +void InternetConnectionMonitor::notifyObserversLocked() { + ACSDK_DEBUG5(LX(__func__)); + for (auto& observer : m_observers) { + observer->onConnectionStatusChanged(m_connected); + } +} + +void InternetConnectionMonitor::updateConnectionStatus(bool connected) { + ACSDK_DEBUG5(LX(__func__).d("connected", connected)); + + std::lock_guard lock{m_mutex}; + if (m_connected != connected) { + m_connected = connected; + notifyObserversLocked(); + } +} + +InternetConnectionMonitor::~InternetConnectionMonitor() { + stopMonitoring(); +} + +} // namespace network +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/RequiresShutdown.cpp b/AVSCommon/Utils/src/RequiresShutdown.cpp index 58784de458..ea6ce2ca2c 100644 --- a/AVSCommon/Utils/src/RequiresShutdown.cpp +++ b/AVSCommon/Utils/src/RequiresShutdown.cpp @@ -104,11 +104,11 @@ ShutdownMonitor::~ShutdownMonitor() { for (auto object : m_objects) { if (!object->isShutdown()) { - m_destructorLogger.log( + m_destructorLogger.logAtExit( alexaClientSDK::avsCommon::utils::logger::Level::WARN, LX("ShutdownMonitor").d("reason", "no shutdown() call").d("name: ", object->name())); } - m_destructorLogger.log( + m_destructorLogger.logAtExit( alexaClientSDK::avsCommon::utils::logger::Level::WARN, LX("ShutdownMonitor").d("reason", "never deleted").d("name", object->name())); } diff --git a/AVSCommon/Utils/src/Stopwatch.cpp b/AVSCommon/Utils/src/Stopwatch.cpp new file mode 100644 index 0000000000..ff84158017 --- /dev/null +++ b/AVSCommon/Utils/src/Stopwatch.cpp @@ -0,0 +1,124 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include "AVSCommon/Utils/Timing/Stopwatch.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace timing { + +/// String to identify log entries originating from this file. +static const std::string TAG("Stopwatch"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/** + * Helper method to get the elapsed time in milliseconds from two @c time_point values. + * + * @param later The end time for the calculation . + * @param earlier The start time for the calculation. + * @return The elapsed time in milliseconds from two @c time_point values. + */ +static std::chrono::milliseconds elapsed( + std::chrono::steady_clock::time_point later, + std::chrono::steady_clock::time_point earlier) { + // Not expected, but just in case... + if (earlier >= later) { + return std::chrono::milliseconds::zero(); + } + return std::chrono::duration_cast(later - earlier); +} + +Stopwatch::Stopwatch() { + reset(); +} + +bool Stopwatch::start() { + std::lock_guard lock(m_mutex); + if (m_state != State::RESET) { + ACSDK_ERROR(LX("startFailed").d("reason", "stateNotRESET")); + return false; + } + m_startTime = std::chrono::steady_clock::now(); + m_state = State::RUNNING; + return true; +} + +bool Stopwatch::pause() { + std::lock_guard lock(m_mutex); + if (m_state != State::RUNNING) { + ACSDK_ERROR(LX("pauseFailed").d("reason", "stateNotRUNNING")); + return false; + } + m_pauseTime = std::chrono::steady_clock::now(); + m_state = State::PAUSED; + return true; +} + +bool Stopwatch::resume() { + std::lock_guard lock(m_mutex); + if (m_state != State::PAUSED) { + ACSDK_ERROR(LX("resumeFailed").d("reason", "stateNotPAUSED")); + return false; + } + m_totalTimePaused += elapsed(std::chrono::steady_clock::now(), m_pauseTime); + m_state = State::RUNNING; + return true; +} + +void Stopwatch::stop() { + std::lock_guard lock(m_mutex); + if (m_state != State::RESET && m_state != State::STOPPED) { + m_stopTime = std::chrono::steady_clock::now(); + } + m_state = State::STOPPED; +} + +void Stopwatch::reset() { + std::lock_guard lock(m_mutex); + m_state = State::RESET; + m_startTime = std::chrono::steady_clock::time_point(); + m_pauseTime = std::chrono::steady_clock::time_point(); + m_stopTime = std::chrono::steady_clock::time_point(); + m_totalTimePaused = std::chrono::milliseconds::zero(); +} + +std::chrono::milliseconds Stopwatch::getElapsed() { + std::lock_guard lock(m_mutex); + switch (m_state) { + case State::RESET: + return std::chrono::milliseconds::zero(); + case State::RUNNING: + return elapsed(std::chrono::steady_clock::now(), m_startTime) - m_totalTimePaused; + case State::PAUSED: + return elapsed(m_pauseTime, m_startTime) - m_totalTimePaused; + case State::STOPPED: + return elapsed(m_stopTime, m_startTime) - m_totalTimePaused; + } + // We should never get here. + return std::chrono::milliseconds::zero(); +} + +} // namespace timing +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/Stream/Streambuf.cpp b/AVSCommon/Utils/src/Stream/Streambuf.cpp index 4dc47c637a..0633a74f31 100644 --- a/AVSCommon/Utils/src/Stream/Streambuf.cpp +++ b/AVSCommon/Utils/src/Stream/Streambuf.cpp @@ -24,9 +24,8 @@ namespace stream { // setg only is for reading, so this operation is safe, although ugly. Streambuf::Streambuf(const unsigned char* data, size_t length) : m_begin(reinterpret_cast(const_cast(data))), - m_current(m_begin), m_end(m_begin + length) { - setg(m_begin, m_current, m_end); + setg(m_begin, m_begin, m_end); } std::streampos Streambuf::seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which) { @@ -44,7 +43,11 @@ std::streampos Streambuf::seekoff(std::streamoff off, std::ios_base::seekdir way return std::streampos(std::streamoff(-1)); } - return UpdateAndValidate(); + if (!gptr() || gptr() >= egptr() || gptr() < eback()) { + return std::streampos(std::streamoff(-1)); + } + + return gptr() - eback(); } std::streampos Streambuf::seekpos(std::streampos sp, std::ios_base::openmode which) { @@ -52,37 +55,22 @@ std::streampos Streambuf::seekpos(std::streampos sp, std::ios_base::openmode whi } Streambuf::int_type Streambuf::underflow() { - m_current = gptr(); - if (m_current == m_end) { + if (gptr() == m_end) { return Streambuf::traits_type::eof(); } - return Streambuf::traits_type::to_int_type(*m_current); -} - -Streambuf::int_type Streambuf::uflow() { - ++m_current; - setg(m_begin, m_current, m_end); - return underflow(); + return Streambuf::traits_type::to_int_type(*gptr()); } Streambuf::int_type Streambuf::pbackfail(int_type ch) { - if (m_current == m_begin || (ch != Streambuf::traits_type::eof() && ch != m_current[-1])) { + if (gptr() <= eback() || gptr() > egptr() || (ch != Streambuf::traits_type::eof() && ch != egptr()[-1])) { return Streambuf::traits_type::eof(); } - return Streambuf::traits_type::to_int_type(*--m_current); + gbump(-1); + return ch; } std::streamsize Streambuf::showmanyc() { - return m_end - m_current; -} - -std::streampos Streambuf::UpdateAndValidate() { - m_current = gptr(); - if (!gptr() || gptr() >= egptr() || gptr() < eback()) { - return std::streampos(std::streamoff(-1)); - } - - return gptr() - eback(); + return egptr() - gptr(); } } // namespace stream diff --git a/AVSCommon/Utils/src/StringUtils.cpp b/AVSCommon/Utils/src/StringUtils.cpp index 874e218c6e..4806598212 100644 --- a/AVSCommon/Utils/src/StringUtils.cpp +++ b/AVSCommon/Utils/src/StringUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -46,29 +46,68 @@ bool stringToInt(const std::string& str, int* result) { } bool stringToInt(const char* str, int* result) { + int64_t tempResult = 0; + if (!stringToInt64(str, &tempResult) || tempResult < std::numeric_limits::min() || + tempResult > std::numeric_limits::max()) { + ACSDK_ERROR(LX("stringToIntFailed").m("converted number was out of range.")); + return false; + } + + *result = static_cast(tempResult); + return true; +} + +std::string byteVectorToString(const std::vector& byteVector) { + std::stringstream ss; + bool firstTime = true; + for (const auto& byte : byteVector) { + ss << std::hex << (firstTime ? "" : " ") << "0x" << std::setfill('0') << std::setw(2) << int(byte) << std::dec; + firstTime = false; + } + return ss.str(); +} + +std::string stringToLowerCase(const std::string& input) { + std::string lowerCaseString = input; + std::transform(lowerCaseString.begin(), lowerCaseString.end(), lowerCaseString.begin(), ::tolower); + return lowerCaseString; +} + +std::string stringToUpperCase(const std::string& input) { + std::string upperCaseString = input; + std::transform(upperCaseString.begin(), upperCaseString.end(), upperCaseString.begin(), ::toupper); + return upperCaseString; +} + +bool stringToInt64(const std::string& str, int64_t* result) { + return stringToInt64(str.c_str(), result); +} + +bool stringToInt64(const char* str, int64_t* result) { if (!str) { - ACSDK_ERROR(LX("stringToIntFailed").m("str parameter is null.")); + ACSDK_ERROR(LX("stringToInt64Failed").m("string parameter is null.")); return false; } + if (!result) { - ACSDK_ERROR(LX("stringToIntFailed").m("result parameter is null.")); + ACSDK_ERROR(LX("stringToInt64Failed").m("result parameter is null.")); return false; } // ensure errno is set to zero before calling strtol. errno = 0; char* endPtr = nullptr; - long tempResult = strtol(str, &endPtr, BASE_TEN); + auto tempResult = strtoll(str, &endPtr, BASE_TEN); - // If strtol() fails, then endPtr will still point to the beginning of str - a simple way to detect error. + // If stroll() fails, then endPtr will still point to the beginning of str - a simple way to detect error. if (str == endPtr) { - ACSDK_ERROR(LX("stringToIntFailed").m("input string was not parsable as an integer.")); + ACSDK_ERROR(LX("stringToInt64Failed").m("input string was not parsable as an integer.")); return false; } - if (ERANGE == errno || tempResult < std::numeric_limits::min() || - tempResult > std::numeric_limits::max()) { - ACSDK_ERROR(LX("stringToIntFailed").m("converted number was out of range.")); + if (ERANGE == errno || tempResult < std::numeric_limits::min() || + tempResult > std::numeric_limits::max()) { + ACSDK_ERROR(LX("stringToInt64Failed").m("converted number was out of range.")); return false; } @@ -80,28 +119,22 @@ bool stringToInt(const char* str, int* result) { // If endPtr does not point to a null terminator, then parsing the number was terminated by running in to // a non-digit (and non-whitespace character), in which case the string was not just an integer (e.g. "1.23"). if (*endPtr != '\0') { - ACSDK_ERROR(LX("stringToIntFailed").m("non-whitespace in string after integer.")); + ACSDK_ERROR(LX("stringToInt64Failed").m("non-whitespace in string after integer.")); return false; } - *result = static_cast(tempResult); + *result = static_cast(tempResult); return true; } -std::string byteVectorToString(const std::vector& byteVector) { - std::stringstream ss; - bool firstTime = true; - for (const auto& byte : byteVector) { - ss << std::hex << (firstTime ? "" : " ") << "0x" << std::setfill('0') << std::setw(2) << int(byte) << std::dec; - firstTime = false; +std::string replaceAllSubstring(const std::string& str, const std::string& from, const std::string& to) { + size_t pos = 0; + std::string subject = str; + while ((pos = subject.find(from, pos)) != std::string::npos) { + subject.replace(pos, from.length(), to); + pos += to.length(); } - return ss.str(); -} - -std::string stringToLowerCase(const std::string& input) { - std::string lowerCaseString = input; - std::transform(lowerCaseString.begin(), lowerCaseString.end(), lowerCaseString.begin(), ::tolower); - return lowerCaseString; + return subject; } } // namespace string diff --git a/AVSCommon/Utils/src/TaskQueue.cpp b/AVSCommon/Utils/src/TaskQueue.cpp deleted file mode 100644 index 51c680b972..0000000000 --- a/AVSCommon/Utils/src/TaskQueue.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include "AVSCommon/Utils/Threading/TaskQueue.h" - -namespace alexaClientSDK { -namespace avsCommon { -namespace utils { -namespace threading { - -TaskQueue::TaskQueue() : m_shutdown{false} { -} - -std::unique_ptr> TaskQueue::pop() { - std::unique_lock queueLock{m_queueMutex}; - - auto shouldNotWait = [this]() { return m_shutdown || !m_queue.empty(); }; - - if (!shouldNotWait()) { - m_queueChanged.wait(queueLock, shouldNotWait); - } - - if (!m_queue.empty()) { - auto task = std::move(m_queue.front()); - - m_queue.pop_front(); - return task; - } - - return nullptr; -} - -void TaskQueue::shutdown() { - std::lock_guard queueLock{m_queueMutex}; - m_queue.clear(); - m_shutdown = true; - m_queueChanged.notify_all(); -} - -bool TaskQueue::isShutdown() { - return m_shutdown; -} - -} // namespace threading -} // namespace utils -} // namespace avsCommon -} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/TaskThread.cpp b/AVSCommon/Utils/src/TaskThread.cpp index 8078e381c0..6e6302edb2 100644 --- a/AVSCommon/Utils/src/TaskThread.cpp +++ b/AVSCommon/Utils/src/TaskThread.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,48 +13,67 @@ * permissions and limitations under the License. */ +#include "AVSCommon/Utils/Logger/Logger.h" +#include "AVSCommon/Utils/Logger/ThreadMoniker.h" #include "AVSCommon/Utils/Threading/TaskThread.h" +/// String to identify log entries originating from this file. +static const std::string TAG("TaskThread"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + namespace alexaClientSDK { namespace avsCommon { namespace utils { namespace threading { -TaskThread::TaskThread(std::shared_ptr taskQueue) : m_taskQueue{taskQueue}, m_shutdown{false} { +using namespace logger; + +TaskThread::TaskThread() : m_alreadyStarting{false}, m_moniker{ThreadMoniker::generateMoniker()} { } TaskThread::~TaskThread() { - m_shutdown = true; - + m_stop = true; if (m_thread.joinable()) { m_thread.join(); } } -void TaskThread::start() { - m_thread = std::thread{std::bind(&TaskThread::processTasksLoop, this)}; -} +bool TaskThread::start(std::function jobRunner) { + if (!jobRunner) { + ACSDK_ERROR(LX("startFailed").d("reason", "invalidFunction")); + return false; + } + + bool notRunning = false; + if (!m_alreadyStarting.compare_exchange_strong(notRunning, true)) { + ACSDK_ERROR(LX("startFailed").d("reason", "tooManyThreads")); + return false; + } -bool TaskThread::isShutdown() { - return m_shutdown; + m_oldThread = std::move(m_thread); + m_thread = std::thread{std::bind(&TaskThread::run, this, std::move(jobRunner))}; + return true; } -void TaskThread::processTasksLoop() { - while (!m_shutdown) { - auto m_actualTaskQueue = m_taskQueue.lock(); - - if (m_actualTaskQueue && !m_actualTaskQueue->isShutdown()) { - auto task = m_actualTaskQueue->pop(); - - if (task) { - task->operator()(); - } - } else { - // Since we could not get a shared pointer to the the TaskQueue, it must have been destroyed. - // The thread must shut down. - m_shutdown = true; - } +void TaskThread::run(std::function jobRunner) { + if (m_oldThread.joinable()) { + m_stop = true; + m_oldThread.join(); } + + // Reset stop flag and already starting flag. + m_stop = false; + m_alreadyStarting = false; + ThreadMoniker::setThisThreadMoniker(m_moniker); + + while (!m_stop && jobRunner()) + ; } } // namespace threading diff --git a/AVSCommon/Utils/src/TimeUtils.cpp b/AVSCommon/Utils/src/TimeUtils.cpp index f61a249b5e..ddf671a8aa 100644 --- a/AVSCommon/Utils/src/TimeUtils.cpp +++ b/AVSCommon/Utils/src/TimeUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -97,7 +97,8 @@ static const unsigned long ENCODED_TIME_STRING_EXPECTED_LENGTH = * Utility function that wraps localtime conversion to std::time_t. * * This function also creates a copy of the given timeStruct since mktime can - * change the object. + * change the object. Note that the tm_isdst is set to -1 as we leave it to + * std::mktime to determine if daylight savings is in effect. * * @param timeStruct Required pointer to timeStruct to be converted to time_t. * @param[out] ret Required pointer to object where the result will be saved. @@ -109,6 +110,7 @@ static bool convertToLocalTimeT(const std::tm* timeStruct, std::time_t* ret) { } std::tm tmCopy = *timeStruct; + tmCopy.tm_isdst = -1; *ret = std::mktime(&tmCopy); return *ret >= 0; } @@ -125,7 +127,7 @@ bool TimeUtils::convertToUtcTimeT(const std::tm* utcTm, std::time_t* ret) { return false; } - if (!convertToLocalTimeT(utcTm, &converted) || !localtimeOffset(&offset)) { + if (!convertToLocalTimeT(utcTm, &converted) || !localtimeOffset(converted, &offset)) { ACSDK_ERROR(LX("convertToUtcTimeT").m("failed to convert to local time")); return false; } @@ -193,7 +195,6 @@ bool TimeUtils::convert8601TimeStringToUnix(const std::string& timeString, int64 } // adjust for C struct tm standard - timeInfo.tm_isdst = 0; timeInfo.tm_year -= 1900; timeInfo.tm_mon -= 1; @@ -203,7 +204,6 @@ bool TimeUtils::convert8601TimeStringToUnix(const std::string& timeString, int64 if (!ok) { return false; } - *convertedTime = static_cast(convertedTimeT); return true; } @@ -220,15 +220,18 @@ bool TimeUtils::getCurrentUnixTime(int64_t* currentTime) { return now >= 0; } -bool TimeUtils::convertTimeToUtcIso8601Rfc3339(const struct timeval& timeVal, std::string* iso8601TimeString) { +bool TimeUtils::convertTimeToUtcIso8601Rfc3339( + const std::chrono::system_clock::time_point& tp, + std::string* iso8601TimeString) { // The length of the RFC 3339 string for the time is maximum 28 characters, include an extra byte for the '\0' // terminator. char buf[29]; memset(buf, 0, sizeof(buf)); // Need to assign it to time_t since time_t in some platforms is long long - // and timeVal.tv_sec is long in some platforms - const time_t timeSecs = timeVal.tv_sec; + auto ms = std::chrono::duration_cast(tp.time_since_epoch()); + auto sec = std::chrono::duration_cast(ms); + const time_t timeSecs = static_cast(sec.count()); std::tm utcTm; if (!m_safeCTimeAccess->getGmtime(timeSecs, &utcTm)) { @@ -245,22 +248,19 @@ bool TimeUtils::convertTimeToUtcIso8601Rfc3339(const struct timeval& timeVal, st } std::stringstream millisecondTrailer; - millisecondTrailer << buf << "." << std::setfill('0') << std::setw(3) << (timeVal.tv_usec / 1000) << "Z"; + millisecondTrailer << buf << "." << std::setfill('0') << std::setw(3) << (ms.count() % 1000) << "Z"; *iso8601TimeString = millisecondTrailer.str(); return true; } -bool TimeUtils::localtimeOffset(std::time_t* ret) { - static const std::chrono::time_point timePoint{std::chrono::hours(24)}; - auto fixedTime = std::chrono::system_clock::to_time_t(timePoint); - +bool TimeUtils::localtimeOffset(std::time_t referenceTime, std::time_t* ret) { std::tm utcTm; std::time_t utc; std::tm localTm; std::time_t local; - if (!m_safeCTimeAccess->getGmtime(fixedTime, &utcTm) || !convertToLocalTimeT(&utcTm, &utc) || - !m_safeCTimeAccess->getLocaltime(fixedTime, &localTm) || !convertToLocalTimeT(&localTm, &local)) { + if (!m_safeCTimeAccess->getGmtime(referenceTime, &utcTm) || !convertToLocalTimeT(&utcTm, &utc) || + !m_safeCTimeAccess->getLocaltime(referenceTime, &localTm) || !convertToLocalTimeT(&localTm, &local)) { ACSDK_ERROR(LX("localtimeOffset").m("cannot retrieve tm struct")); return false; } diff --git a/ACL/test/Transport/Common/Common.h b/AVSCommon/Utils/test/AVSCommon/Utils/Common/Common.h similarity index 73% rename from ACL/test/Transport/Common/Common.h rename to AVSCommon/Utils/test/AVSCommon/Utils/Common/Common.h index 96671ea18d..5bad7fff8a 100644 --- a/ACL/test/Transport/Common/Common.h +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Common/Common.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_COMMON_H_ -#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_COMMON_H_ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_COMMON_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_COMMON_H_ #include namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { /** * Utility function to generate a random string of characters between 'a' - 'z'. @@ -39,8 +39,8 @@ std::string createRandomAlphabetString(int stringSize); */ int generateRandomNumber(int min, int max); -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_COMMON_H_ +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_COMMON_H_ diff --git a/ACL/test/Transport/Common/MimeUtils.h b/AVSCommon/Utils/test/AVSCommon/Utils/Common/MimeUtils.h similarity index 89% rename from ACL/test/Transport/Common/MimeUtils.h rename to AVSCommon/Utils/test/AVSCommon/Utils/Common/MimeUtils.h index b8b3c73fef..d72a4b727b 100644 --- a/ACL/test/Transport/Common/MimeUtils.h +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Common/MimeUtils.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_MIMEUTILS_H_ -#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_MIMEUTILS_H_ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_MIMEUTILS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_MIMEUTILS_H_ #include #include @@ -26,8 +26,8 @@ #include "TestableMessageObserver.h" namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { /** * Utility class to abstract the notion of testing a MIME part. @@ -147,14 +147,16 @@ class TestMimeAttachmentPart : public TestMimePart { * know how to generate their own substrings. * @param boundaryString The boundary string to be used when generating the MIME * string. + * @param addPrependedNewline true if a trailing newline (CRLF) sequence is required, false otherwise. * @return The generated MIME string. */ std::string constructTestMimeString( const std::vector>& mimeParts, - const std::string& boundaryString); + const std::string& boundaryString, + bool addPrependedNewline = true); -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_MIMEUTILS_H_ +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_MIMEUTILS_H_ diff --git a/ACL/test/Transport/Common/TestableAttachmentManager.h b/AVSCommon/Utils/test/AVSCommon/Utils/Common/TestableAttachmentManager.h similarity index 79% rename from ACL/test/Transport/Common/TestableAttachmentManager.h rename to AVSCommon/Utils/test/AVSCommon/Utils/Common/TestableAttachmentManager.h index 2c6b5c82f3..0cd6f5696d 100644 --- a/ACL/test/Transport/Common/TestableAttachmentManager.h +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Common/TestableAttachmentManager.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_TESTABLEATTACHMENTMANAGER_H_ -#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_TESTABLEATTACHMENTMANAGER_H_ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_TESTABLEATTACHMENTMANAGER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_TESTABLEATTACHMENTMANAGER_H_ #include namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { /** * A version of the Decorator Pattern, this class allows us to return a special AttachmentWriter class to @@ -50,8 +50,8 @@ class TestableAttachmentManager : public avsCommon::avs::attachment::AttachmentM std::unique_ptr m_manager; }; -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_TESTABLEATTACHMENTMANAGER_H_ +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_TESTABLEATTACHMENTMANAGER_H_ diff --git a/ACL/test/Transport/Common/TestableAttachmentWriter.h b/AVSCommon/Utils/test/AVSCommon/Utils/Common/TestableAttachmentWriter.h similarity index 80% rename from ACL/test/Transport/Common/TestableAttachmentWriter.h rename to AVSCommon/Utils/test/AVSCommon/Utils/Common/TestableAttachmentWriter.h index 0007c76c33..31823f679f 100644 --- a/ACL/test/Transport/Common/TestableAttachmentWriter.h +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Common/TestableAttachmentWriter.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_TESTABLEATTACHMENTWRITER_H_ -#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_TESTABLEATTACHMENTWRITER_H_ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_TESTABLEATTACHMENTWRITER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_TESTABLEATTACHMENTWRITER_H_ #include namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { /** * A version of the Decorator Pattern, this class allows us to simulate pausing writes without requiring @@ -55,8 +55,8 @@ class TestableAttachmentWriter : public avsCommon::avs::attachment::InProcessAtt bool m_hasWriteBeenInvoked; }; -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_TESTABLEATTACHMENTWRITER_H_ +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_TESTABLEATTACHMENTWRITER_H_ diff --git a/ACL/test/Transport/Common/TestableMessageObserver.h b/AVSCommon/Utils/test/AVSCommon/Utils/Common/TestableMessageObserver.h similarity index 78% rename from ACL/test/Transport/Common/TestableMessageObserver.h rename to AVSCommon/Utils/test/AVSCommon/Utils/Common/TestableMessageObserver.h index b14c157235..7a0af214ee 100644 --- a/ACL/test/Transport/Common/TestableMessageObserver.h +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Common/TestableMessageObserver.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,19 +13,19 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_TESTABLEMESSAGEOBSERVER_H_ -#define ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_TESTABLEMESSAGEOBSERVER_H_ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_TESTABLEMESSAGEOBSERVER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_TESTABLEMESSAGEOBSERVER_H_ +#include #include #include #include #include -#include namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { /** * A useful class that allows us to test a Directive being received. @@ -52,8 +52,8 @@ class TestableMessageObserver : public avsCommon::sdkInterfaces::MessageObserver std::vector m_receivedDirectives; }; -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_ACL_TEST_TRANSPORT_COMMON_TESTABLEMESSAGEOBSERVER_H_ +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_TESTABLEMESSAGEOBSERVER_H_ diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/HTTP2/MockHTTP2MimeRequestEncodeSource.h b/AVSCommon/Utils/test/AVSCommon/Utils/HTTP2/MockHTTP2MimeRequestEncodeSource.h new file mode 100644 index 0000000000..acfb3463ca --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/HTTP2/MockHTTP2MimeRequestEncodeSource.h @@ -0,0 +1,85 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_HTTP2_MOCKHTTP2MIMEREQUESTENCODESOURCE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_HTTP2_MOCKHTTP2MIMEREQUESTENCODESOURCE_H_ + +#include +#include +#include "AVSCommon/Utils/HTTP2/HTTP2MimeRequestSourceInterface.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { +/** + * Mock class which implements the HTTP2MIMERequestSourceInterface + * to allow testing + */ +class MockHTTP2MimeRequestEncodeSource : public HTTP2MimeRequestSourceInterface { +public: + /** + * Constructor which accepts the various MIME parts to be + * passed onto the encoder as the request source + * @param m_data vector of MIME data parts + * @param m_headers vector of MIME headers + */ + MockHTTP2MimeRequestEncodeSource( + const std::vector& m_data, + const std::vector>& m_headers); + + /// @name HTTP2MimeRequestSourceInterface methods. + /// @{ + HTTP2GetMimeHeadersResult getMimePartHeaderLines() override; + HTTP2SendDataResult onSendMimePartData(char* bytes, size_t size) override; + std::vector getRequestHeaderLines() override; + /// @} + + /** + * Destructor + */ + ~MockHTTP2MimeRequestEncodeSource() = default; + + /** + * These will be kept public to help testing + */ + + /// Stores the MIME data parts + std::vector m_data; + + /// Stores the MIME header parts + std::vector> m_headers; + + /// Index into the current MIME data part + size_t m_bytesWritten; + + /// Index of current MIME part being read + size_t m_index; + + /// Enable sending PAUSE intermittently + bool m_slowSource; + + /// If ABORT is to be sent + bool m_abort; + + /// PAUSE count + size_t m_pauseCount; +}; +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_HTTP2_MOCKHTTP2MIMEREQUESTENCODESOURCE_H_ diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/HTTP2/MockHTTP2MimeResponseDecodeSink.h b/AVSCommon/Utils/test/AVSCommon/Utils/HTTP2/MockHTTP2MimeResponseDecodeSink.h new file mode 100644 index 0000000000..e7a79955cd --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/HTTP2/MockHTTP2MimeResponseDecodeSink.h @@ -0,0 +1,89 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_HTTP2_MOCKHTTP2MIMERESPONSEDECODESINK_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_HTTP2_MOCKHTTP2MIMERESPONSEDECODESINK_H_ + +#include +#include +#include + +#include "AVSCommon/Utils/HTTP2/HTTP2MimeResponseSinkInterface.h" +#include "AVSCommon/Utils/HTTP2/MockHTTP2MimeRequestEncodeSource.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { +/** + * Mock class which implements the @class HTTP2MimeResponseSinkInterface + * to allow testing + */ +class MockHTTP2MimeResponseDecodeSink : public HTTP2MimeResponseSinkInterface { +public: + /** + * Constructor to provide the HTTP headers and data + * to be passed onto the decoder + */ + MockHTTP2MimeResponseDecodeSink(); + + /// @name HTTP2MimeResponseSinkInterface methods. + /// @{ + bool onReceiveResponseCode(long responseCode) override; + bool onReceiveHeaderLine(const std::string& line) override; + bool onBeginMimePart(const std::multimap& headers) override; + avsCommon::utils::http2::HTTP2ReceiveDataStatus onReceiveMimeData(const char* bytes, size_t size) override; + bool onEndMimePart() override; + avsCommon::utils::http2::HTTP2ReceiveDataStatus onReceiveNonMimeData(const char* bytes, size_t size) override; + void onResponseFinished(avsCommon::utils::http2::HTTP2ResponseFinishedStatus status) override; + /// @} + + /** + * Helper method to compare data with source + * @param source RequestSource used to generate the original encoded message + * @return true if data is same + */ + bool hasSameContentAs(std::shared_ptr source); + + /** + * Destructor + */ + ~MockHTTP2MimeResponseDecodeSink() = default; + + /** + * These will be kept public to help testing + */ + + /// MIME data parts received + std::vector m_data; + /// MIME headers received for every part + std::vector> m_headers; + /// current MIME part index + size_t m_index{0}; + /// enable sending PAUSE intermittently + bool m_slowSink{false}; + /// If ABORT is to be sent + bool m_abort{false}; + /// PAUSE count + size_t m_pauseCount{0}; + /// Non mime data received. + std::string m_nonMimeData; +}; + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_HTTP2_MOCKHTTP2MIMERESPONSEDECODESINK_H_ diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h b/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h index bbfa416605..276935afe1 100644 --- a/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h +++ b/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -18,8 +18,9 @@ #include -#include "AVSCommon/Utils/MediaPlayer/MediaPlayerInterface.h" -#include "AVSCommon/Utils/RequiresShutdown.h" +#include +#include +#include namespace alexaClientSDK { namespace avsCommon { @@ -88,7 +89,8 @@ class MockMediaPlayer const avsCommon::utils::AudioFormat* audioFormat = nullptr) /*override*/; SourceId setSource( const std::string& url, - std::chrono::milliseconds offset = std::chrono::milliseconds::zero()) /*override*/; + std::chrono::milliseconds offset = std::chrono::milliseconds::zero(), + bool repeat = false) /*override*/; SourceId setSource(std::shared_ptr stream, bool repeat) /*override*/; void setObserver(std::shared_ptr playerObserver) /*override*/; /// @} @@ -235,6 +237,13 @@ class MockMediaPlayer */ SourceId getCurrentSourceId(); + /** + * Get the current observer. + * + * @return The current observer, or nullptr if there are none. + */ + std::shared_ptr getObserver() const; + private: struct Source; @@ -353,6 +362,9 @@ class MockMediaPlayer /// Tracks if playbackError state has been reached. SourceState error; + + /// Tracks how far mocked playback has progressed, using elapsed real time. + avsCommon::utils::timing::Stopwatch stopwatch; }; /** diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/Network/MockInternetConnectionMonitor.h b/AVSCommon/Utils/test/AVSCommon/Utils/Network/MockInternetConnectionMonitor.h new file mode 100644 index 0000000000..391c0d100a --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Network/MockInternetConnectionMonitor.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_NETWORK_INTERNETCONNECTIONMONITOR_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_NETWORK_INTERNETCONNECTIONMONITOR_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace network { +namespace test { + +class MockInternetConnectionMonitor : public avsCommon::sdkInterfaces::InternetConnectionMonitorInterface { +public: + MOCK_METHOD1( + addInternetConnectionObserver, + void(std::shared_ptr observer)); + MOCK_METHOD1( + removeInternetConnectionObserver, + void(std::shared_ptr observer)); +}; + +} // namespace test +} // namespace network +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_NETWORK_INTERNETCONNECTIONMONITOR_H_ diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/OptionalTest.cpp b/AVSCommon/Utils/test/AVSCommon/Utils/OptionalTest.cpp new file mode 100644 index 0000000000..a87cac7b44 --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/OptionalTest.cpp @@ -0,0 +1,250 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { + +/** + * Dummy structure used for our tests. + */ +struct Dummy { + /// Dummy structure name used to validate the object content. + std::string m_name; +}; + +/** + * Test structure used to ensure that Optional can be used with types without default constructor. + */ +struct StructWithoutDefaultConstructor { + /// Explicitly delete the default constructor. + StructWithoutDefaultConstructor() = delete; + + /** + * Create default constructor. + */ + StructWithoutDefaultConstructor(const StructWithoutDefaultConstructor& other) = default; + /** + * Create object with the given id. + * + * @param id The id to assign to the new object. + */ + StructWithoutDefaultConstructor(int id); + + /// This object id. + int m_id; +}; + +/** + * Class with static counters for the constructor and the destructor methods. + */ +struct ReferenceCounter { + /// Count the amount of times the constructor has been called. + static size_t s_built; + + /// Count the amount of times the destructor has been called. + static size_t s_destroyed; + + /** + * Empty constructor. + */ + ReferenceCounter(); + + /** + * Copy constructor. + */ + ReferenceCounter(const ReferenceCounter& rhs); + + /** + * Destructor. + */ + ~ReferenceCounter(); +}; + +size_t ReferenceCounter::s_built = 0; +size_t ReferenceCounter::s_destroyed = 0; + +ReferenceCounter::ReferenceCounter() { + s_built++; +} + +ReferenceCounter::ReferenceCounter(const ReferenceCounter& rhs) { + s_built++; +} + +ReferenceCounter::~ReferenceCounter() { + s_destroyed++; +} + +StructWithoutDefaultConstructor::StructWithoutDefaultConstructor(int id) : m_id{id} { +} + +TEST(OptionalTest, test_createEmptyOptional) { + Optional empty; + EXPECT_FALSE(empty.hasValue()); +} + +TEST(OptionalTest, test_createOptionalWithValue) { + Optional dummy{Dummy{}}; + EXPECT_TRUE(dummy.hasValue()); +} + +TEST(OptionalTest, test_getValueOfOptionalWithValue) { + Optional dummy{Dummy{.m_name = "EXPECTED_NAME"}}; + ASSERT_TRUE(dummy.hasValue()); + + auto name = dummy.valueOr(Dummy{.m_name = "OTHER_NAME"}).m_name; + EXPECT_EQ(name, "EXPECTED_NAME"); + + name = dummy.value().m_name; + EXPECT_EQ(name, "EXPECTED_NAME"); +} + +TEST(OptionalTest, test_getValueOfEmptyOptional) { + Optional dummy; + ASSERT_FALSE(dummy.hasValue()); + + auto name = dummy.valueOr(Dummy{.m_name = "OTHER_NAME"}).m_name; + EXPECT_EQ(name, "OTHER_NAME"); + + name = dummy.value().m_name; + EXPECT_EQ(name, std::string()); +} + +TEST(OptionalTest, test_functionWithEmptyOptionalReturn) { + auto function = []() -> Optional { return Optional(); }; + auto empty = function(); + ASSERT_FALSE(empty.hasValue()); +} + +TEST(OptionalTest, test_functionWithNonEmptyOptionalReturn) { + auto function = []() -> Optional { return Optional{Dummy{.m_name = "EXPECTED_NAME"}}; }; + auto dummy = function(); + ASSERT_TRUE(dummy.hasValue()); + ASSERT_EQ(dummy.value().m_name, "EXPECTED_NAME"); +} + +TEST(OptionalTest, test_copyOptionalWithValue) { + Optional dummy1{Dummy{.m_name = "EXPECTED_NAME"}}; + ASSERT_TRUE(dummy1.hasValue()); + + Optional dummy2{dummy1}; + EXPECT_TRUE(dummy2.hasValue()); + EXPECT_EQ(dummy1.value().m_name, dummy2.value().m_name); +} + +TEST(OptionalTest, test_copyEmptyOptional) { + Optional dummy1; + ASSERT_FALSE(dummy1.hasValue()); + + Optional dummy2{dummy1}; + EXPECT_FALSE(dummy2.hasValue()); +} + +TEST(OptionalTest, test_setNewValueForEmptyOptional) { + Dummy dummy{.m_name = "EXPECTED_NAME"}; + Optional optionalDummy; + optionalDummy.set(dummy); + + EXPECT_TRUE(optionalDummy.hasValue()); + EXPECT_EQ(dummy.m_name, optionalDummy.value().m_name); +} + +TEST(OptionalTest, test_setNewValueForNonEmptyOptional) { + Optional optionalDummy{Dummy{.m_name = "OLD_NAME"}}; + ASSERT_TRUE(optionalDummy.hasValue()); + + optionalDummy.set(Dummy{.m_name = "EXPECTED_NAME"}); + + EXPECT_TRUE(optionalDummy.hasValue()); + EXPECT_EQ(optionalDummy.value().m_name, "EXPECTED_NAME"); +} + +TEST(OptionalTest, test_resetEmptyOptional) { + Optional dummy; + ASSERT_FALSE(dummy.hasValue()); + + dummy.reset(); + EXPECT_FALSE(dummy.hasValue()); +} + +TEST(OptionalTest, test_resetNonEmptyOptional) { + Optional optionalDummy{Dummy{.m_name = "OLD_NAME"}}; + ASSERT_TRUE(optionalDummy.hasValue()); + + optionalDummy.reset(); + EXPECT_FALSE(optionalDummy.hasValue()); +} + +TEST(OptionalTest, test_optionalObjectWithoutDefaultConstructor) { + Optional emptyOptional; + EXPECT_FALSE(emptyOptional.hasValue()); + + const int id = 10; + Optional validOptional{StructWithoutDefaultConstructor(id)}; + EXPECT_EQ(validOptional.valueOr(id + 1).m_id, id); +} + +TEST(OptionalTest, test_constructorCallsMatchDestructorCalls) { + { + Optional optional{ReferenceCounter()}; + EXPECT_GT(ReferenceCounter::s_built, ReferenceCounter::s_destroyed); + + optional.set(ReferenceCounter()); + EXPECT_GT(ReferenceCounter::s_built, ReferenceCounter::s_destroyed); + + optional.reset(); + EXPECT_EQ(ReferenceCounter::s_built, ReferenceCounter::s_destroyed); + + optional.set(ReferenceCounter()); + EXPECT_GT(ReferenceCounter::s_built, ReferenceCounter::s_destroyed); + + Optional other{optional}; + EXPECT_GT(ReferenceCounter::s_built, ReferenceCounter::s_destroyed); + } + EXPECT_EQ(ReferenceCounter::s_built, ReferenceCounter::s_destroyed); +} + +TEST(OptionalTest, test_equalityOperator) { + Optional empty; + Optional valid{"valid"}; + Optional other{"other"}; + Optional validCopy{valid}; + + EXPECT_FALSE(empty == valid); + EXPECT_FALSE(valid == other); + EXPECT_EQ(valid, validCopy); +} + +TEST(OptionalTest, test_inequalityOperator) { + Optional empty; + Optional valid{"valid"}; + Optional other{"other"}; + Optional validCopy{valid}; + + EXPECT_NE(empty, valid); + EXPECT_NE(valid, other); + EXPECT_FALSE(valid != validCopy); +} + +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/WaitEvent.h b/AVSCommon/Utils/test/AVSCommon/Utils/WaitEvent.h new file mode 100644 index 0000000000..dbfa31cb74 --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/WaitEvent.h @@ -0,0 +1,100 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_WAITEVENT_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_WAITEVENT_H_ + +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace test { + +/** + * Class that can be used to wait for an event. + * + * This class is for testing purpose only and each object should only be used once. After the first @c wakeUp call, any + * call to @c wait will no longer block. Use @c reset() to reset the internal state and allow wait to block again. + */ +class WaitEvent { +public: + /** + * Constructor. + */ + WaitEvent(); + + /** + * Notify all threads that are waiting for this event. + * + * This method will also set the @c m_wakeUpTriggered to @c true to ensure that the waiting thread will not wait + * forever in case there is a race condition and @c wakeUp() is triggered before the @c wait(). + */ + void wakeUp(); + + /// The default timeout for an expected event. + static const std::chrono::seconds DEFAULT_TIMEOUT; + + /** + * Wait for wake up event. + * + * @param timeout The maximum amount of time to wait for the event. + * @return @c true if wakeUp has been called within the timeout; @c false otherwise. + */ + bool wait(const std::chrono::milliseconds& timeout = DEFAULT_TIMEOUT); + + /** + * Reset the event occurrence flag. + */ + void reset(); + +private: + /// The condition variable used to wake up the thread that is waiting. + std::condition_variable m_condition; + + /// The mutex used to lock the condition. + std::mutex m_mutex; + + /// The boolean condition to check if wakeUp has been called or not. + bool m_wakeUpTriggered; +}; + +const std::chrono::seconds WaitEvent::DEFAULT_TIMEOUT{5}; + +WaitEvent::WaitEvent() : m_wakeUpTriggered{false} { +} + +void WaitEvent::wakeUp() { + std::lock_guard lock{m_mutex}; + m_wakeUpTriggered = true; + m_condition.notify_all(); +} + +bool WaitEvent::wait(const std::chrono::milliseconds& timeout) { + std::unique_lock lock{m_mutex}; + return m_condition.wait_for(lock, timeout, [this] { return m_wakeUpTriggered; }); +} + +void WaitEvent::reset() { + std::lock_guard lock{m_mutex}; + m_wakeUpTriggered = false; +} + +} // namespace test +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_WAITEVENT_H_ diff --git a/AVSCommon/Utils/test/CMakeLists.txt b/AVSCommon/Utils/test/CMakeLists.txt index 841c6ec1d9..10fad87e0b 100644 --- a/AVSCommon/Utils/test/CMakeLists.txt +++ b/AVSCommon/Utils/test/CMakeLists.txt @@ -4,4 +4,4 @@ set(INCLUDE_PATH "${AVSCommon_INCLUDE_DIRS}" "${AVSCommon_SOURCE_DIR}/SDKInterfaces/test") -discover_unit_tests("${INCLUDE_PATH}" AVSCommon) +discover_unit_tests("${INCLUDE_PATH}" "AVSCommon;UtilsCommonTestLib") diff --git a/AVSCommon/Utils/test/Common/CMakeLists.txt b/AVSCommon/Utils/test/Common/CMakeLists.txt index 06bf61ed4d..395f26ddb9 100644 --- a/AVSCommon/Utils/test/Common/CMakeLists.txt +++ b/AVSCommon/Utils/test/Common/CMakeLists.txt @@ -1,4 +1,14 @@ -add_library(UtilsCommonTestLib MockMediaPlayer.cpp) +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +add_library(UtilsCommonTestLib + MockMediaPlayer.cpp + MockHTTP2MimeRequestEncodeSource.cpp + MockHTTP2MimeResponseDecodeSink.cpp + Common.cpp + MimeUtils.cpp + TestableAttachmentManager.cpp + TestableAttachmentWriter.cpp + TestableMessageObserver.cpp) target_include_directories(UtilsCommonTestLib PUBLIC "${AVSCommon_INCLUDE_DIRS}" "${AVSCommon_SOURCE_DIR}/Utils/test") diff --git a/ACL/test/Transport/Common/Common.cpp b/AVSCommon/Utils/test/Common/Common.cpp similarity index 70% rename from ACL/test/Transport/Common/Common.cpp rename to AVSCommon/Utils/test/Common/Common.cpp index ec152392db..bd80bae8b7 100644 --- a/ACL/test/Transport/Common/Common.cpp +++ b/AVSCommon/Utils/test/Common/Common.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -#include "Common.h" +#include "AVSCommon/Utils/Common/Common.h" #include #include @@ -22,9 +22,15 @@ #include #include +// A constant seed for random number generator, to make the test consistent every run +static const unsigned int RANDOM_NUMBER_SEED = 1; + +// The random number generator +static std::minstd_rand randGenerator; + namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { std::string createRandomAlphabetString(int stringSize) { // First, let's efficiently generate random numbers of the appropriate size. @@ -52,13 +58,17 @@ int generateRandomNumber(int min, int max) { std::swap(min, max); } - std::mt19937 rng; - rng.seed(std::random_device()()); - std::uniform_int_distribution dist(min, max); + /// Identifier to tell if the random number generated has been initialized + static bool randInit = false; + + if (!randInit) { + randGenerator.seed(RANDOM_NUMBER_SEED); + randInit = true; + } - return dist(rng); + return (randGenerator() % (max - min + 1)) + min; } -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK diff --git a/ACL/test/Transport/Common/MimeUtils.cpp b/AVSCommon/Utils/test/Common/MimeUtils.cpp similarity index 91% rename from ACL/test/Transport/Common/MimeUtils.cpp rename to AVSCommon/Utils/test/Common/MimeUtils.cpp index 384fc2b612..804f61fd64 100644 --- a/ACL/test/Transport/Common/MimeUtils.cpp +++ b/AVSCommon/Utils/test/Common/MimeUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -15,15 +15,15 @@ /// @file MimeUtils.cpp -#include "MimeUtils.h" +#include "AVSCommon/Utils/Common/MimeUtils.h" #include -#include "Common.h" +#include "AVSCommon/Utils/Common/Common.h" namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { using namespace avsCommon::avs::attachment; using namespace avsCommon::sdkInterfaces; @@ -112,8 +112,9 @@ bool TestMimeAttachmentPart::validateMimeParsing() { std::string constructTestMimeString( const std::vector>& mimeParts, - const std::string& boundaryString) { - std::string mimeString = MIME_NEWLINE + MIME_BOUNDARY_DASHES + boundaryString; + const std::string& boundaryString, + bool addPrependedNewline) { + std::string mimeString = (addPrependedNewline ? MIME_NEWLINE : "") + MIME_BOUNDARY_DASHES + boundaryString; for (auto mimePart : mimeParts) { mimeString += MIME_NEWLINE + mimePart->getMimeString(); @@ -125,6 +126,6 @@ std::string constructTestMimeString( return mimeString; } -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/Common/MockHTTP2MimeRequestEncodeSource.cpp b/AVSCommon/Utils/test/Common/MockHTTP2MimeRequestEncodeSource.cpp new file mode 100644 index 0000000000..f3ad8bab87 --- /dev/null +++ b/AVSCommon/Utils/test/Common/MockHTTP2MimeRequestEncodeSource.cpp @@ -0,0 +1,86 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include +#include +#include "AVSCommon/Utils/HTTP2/MockHTTP2MimeRequestEncodeSource.h" +#include "AVSCommon/Utils/Common/Common.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/// PAUSE probability set to 25% after first mandatory PAUSE +#define SHOULD_PAUSE (generateRandomNumber(1, 20) < 5 || m_pauseCount == 0) + +HTTP2GetMimeHeadersResult MockHTTP2MimeRequestEncodeSource::getMimePartHeaderLines() { + if (m_abort) { + return HTTP2GetMimeHeadersResult::ABORT; + } else if (m_slowSource && SHOULD_PAUSE) { + // return PAUSE probabilistically + m_pauseCount++; + return HTTP2GetMimeHeadersResult::PAUSE; + } + if (m_index < m_headers.size()) { + return HTTP2GetMimeHeadersResult(m_headers[m_index]); + } + return HTTP2GetMimeHeadersResult::COMPLETE; +} + +HTTP2SendDataResult MockHTTP2MimeRequestEncodeSource::onSendMimePartData(char* bytes, size_t size) { + if (m_abort) { + return HTTP2SendDataResult::ABORT; + } else if (m_slowSource && SHOULD_PAUSE) { + m_pauseCount++; + return HTTP2SendDataResult::PAUSE; + } + const char* testPayload = m_data[m_index].c_str(); + size_t bytesRemaining = strlen(testPayload) - m_bytesWritten; + + if (bytesRemaining == 0) { + m_index++; + m_bytesWritten = 0; + return HTTP2SendDataResult::COMPLETE; + } + + size_t bytesToWrite = size >= bytesRemaining ? bytesRemaining : size; + std::strncpy(bytes, testPayload + m_bytesWritten, bytesToWrite); + m_bytesWritten += bytesToWrite; + return HTTP2SendDataResult(bytesToWrite); +} + +std::vector MockHTTP2MimeRequestEncodeSource::getRequestHeaderLines() { + return std::vector(); +} + +MockHTTP2MimeRequestEncodeSource::MockHTTP2MimeRequestEncodeSource( + const std::vector& m_data, + const std::vector>& m_headers) : + m_data{m_data}, + m_headers{m_headers}, + m_bytesWritten{0}, + m_index{0}, + m_slowSource{false}, + m_abort{false}, + m_pauseCount{0} { +} + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/Common/MockHTTP2MimeResponseDecodeSink.cpp b/AVSCommon/Utils/test/Common/MockHTTP2MimeResponseDecodeSink.cpp new file mode 100644 index 0000000000..b417b9162a --- /dev/null +++ b/AVSCommon/Utils/test/Common/MockHTTP2MimeResponseDecodeSink.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "AVSCommon/Utils/HTTP2/MockHTTP2MimeResponseDecodeSink.h" +#include "AVSCommon/Utils/Common/Common.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace http2 { + +/// PAUSE probability set to 25% after first mandatory pause +#define SHOULD_PAUSE (generateRandomNumber(1, 20) < 5 || m_pauseCount == 0) + +MockHTTP2MimeResponseDecodeSink::MockHTTP2MimeResponseDecodeSink() : + m_index{0}, + m_slowSink{false}, + m_abort{false}, + m_pauseCount{0} { +} + +bool MockHTTP2MimeResponseDecodeSink::onReceiveResponseCode(long responseCode) { + return true; +} + +bool MockHTTP2MimeResponseDecodeSink::onReceiveHeaderLine(const std::string& line) { + return true; +} + +bool MockHTTP2MimeResponseDecodeSink::onBeginMimePart(const std::multimap& headers) { + if (m_abort) { + return false; + } + m_data.push_back(""); + m_headers.push_back(headers); + return true; +} + +bool MockHTTP2MimeResponseDecodeSink::onEndMimePart() { + if (m_abort) { + return false; + } + m_index++; + return true; +} + +HTTP2ReceiveDataStatus MockHTTP2MimeResponseDecodeSink::onReceiveMimeData(const char* bytes, size_t size) { + if (m_abort) { + return HTTP2ReceiveDataStatus::ABORT; + } else if (m_slowSink && SHOULD_PAUSE) { + m_pauseCount++; + return HTTP2ReceiveDataStatus::PAUSE; + } + char temp[size + 1]; + strncpy(temp, bytes, size); + temp[size] = '\0'; + m_data.at(m_index) += temp; + return HTTP2ReceiveDataStatus::SUCCESS; +} + +HTTP2ReceiveDataStatus MockHTTP2MimeResponseDecodeSink::onReceiveNonMimeData(const char* bytes, size_t size) { + m_nonMimeData.append(bytes, size); + return HTTP2ReceiveDataStatus::SUCCESS; +} + +void MockHTTP2MimeResponseDecodeSink::onResponseFinished(HTTP2ResponseFinishedStatus status) { +} + +bool MockHTTP2MimeResponseDecodeSink::hasSameContentAs(std::shared_ptr source) { + bool result{true}; + result &= (source->m_data.size() == m_data.size()); + size_t index{0}; + for (auto part : source->m_data) { + result &= (part == m_data.at(index)); + index++; + } + index = 0; + for (auto headers : m_headers) { + for (auto header : headers) { + auto sourceHeaders = source->m_headers.at(index); + result &= + (std::find(sourceHeaders.begin(), sourceHeaders.end(), header.first + ": " + header.second) != + sourceHeaders.end()); + } + index++; + } + return result; +} + +} // namespace http2 +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/Common/MockMediaPlayer.cpp b/AVSCommon/Utils/test/Common/MockMediaPlayer.cpp index da653fbf26..edd5a33abc 100644 --- a/AVSCommon/Utils/test/Common/MockMediaPlayer.cpp +++ b/AVSCommon/Utils/test/Common/MockMediaPlayer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -54,7 +54,10 @@ MediaPlayerInterface::SourceId MockMediaPlayer::setSource( return attachmentSetSource(attachmentReader, audioFormat); } -MediaPlayerInterface::SourceId MockMediaPlayer::setSource(const std::string& url, std::chrono::milliseconds offset) { +MediaPlayerInterface::SourceId MockMediaPlayer::setSource( + const std::string& url, + std::chrono::milliseconds offset, + bool repeat) { return urlSetSource(url); } @@ -66,6 +69,10 @@ void MockMediaPlayer::setObserver(std::shared_ptr m_playerObserver = playerObserver; } +std::shared_ptr MockMediaPlayer::getObserver() const { + return m_playerObserver; +} + void MockMediaPlayer::doShutdown() { std::lock_guard lock(m_mutex); m_playerObserver.reset(); @@ -84,6 +91,7 @@ bool MockMediaPlayer::mockPlay(SourceId sourceId) { if (!source) { return false; } + EXPECT_TRUE(source->stopwatch.start()); source->started.trigger(); return true; } @@ -93,6 +101,8 @@ bool MockMediaPlayer::mockPause(SourceId sourceId) { if (!source) { return false; } + // Ideally we would EXPECT_TRUE on pause(), however ACSDK-734 doesn't guarantee that will be okay. + source->stopwatch.pause(); source->resumed.resetStateReached(); source->paused.trigger(); return true; @@ -103,6 +113,7 @@ bool MockMediaPlayer::mockResume(SourceId sourceId) { if (!source) { return false; } + EXPECT_TRUE(source->stopwatch.resume()); source->paused.resetStateReached(); source->resumed.trigger(); return true; @@ -113,6 +124,8 @@ bool MockMediaPlayer::mockStop(SourceId sourceId) { if (!source) { return false; } + + source->stopwatch.stop(); source->stopped.trigger(); return true; } @@ -122,6 +135,7 @@ bool MockMediaPlayer::mockFinished(SourceId sourceId) { if (!source) { return false; } + source->stopwatch.stop(); source->finished.trigger(); return true; } @@ -131,6 +145,7 @@ bool MockMediaPlayer::mockError(SourceId sourceId) { if (!source) { return false; } + source->stopwatch.stop(); source->error.trigger(); return true; } @@ -150,7 +165,7 @@ std::chrono::milliseconds MockMediaPlayer::mockGetOffset(SourceId sourceId) { if (!source) { return MEDIA_PLAYER_INVALID_OFFSET; } - return source->offset; + return source->stopwatch.getElapsed() + source->offset; } bool MockMediaPlayer::waitUntilNextSetSource(const std::chrono::milliseconds timeout) { diff --git a/ACL/test/Transport/Common/TestableAttachmentManager.cpp b/AVSCommon/Utils/test/Common/TestableAttachmentManager.cpp similarity index 90% rename from ACL/test/Transport/Common/TestableAttachmentManager.cpp rename to AVSCommon/Utils/test/Common/TestableAttachmentManager.cpp index f2f708d816..8dece14b60 100644 --- a/ACL/test/Transport/Common/TestableAttachmentManager.cpp +++ b/AVSCommon/Utils/test/Common/TestableAttachmentManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -#include "TestableAttachmentManager.h" -#include "TestableAttachmentWriter.h" +#include "AVSCommon/Utils/Common/TestableAttachmentManager.h" +#include "AVSCommon/Utils/Common/TestableAttachmentWriter.h" #include "AVSCommon/Utils/SDS/InProcessSDS.h" namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { /// A small value so we can create objects with a valid SDS (even if unused). static const int DUMMY_SDS_BUFFER_SIZE = 100; @@ -71,6 +71,6 @@ std::unique_ptr TestableAttachmentManager::createReader( return m_manager->createReader(attachmentId, policy); } -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK diff --git a/ACL/test/Transport/Common/TestableAttachmentWriter.cpp b/AVSCommon/Utils/test/Common/TestableAttachmentWriter.cpp similarity index 86% rename from ACL/test/Transport/Common/TestableAttachmentWriter.cpp rename to AVSCommon/Utils/test/Common/TestableAttachmentWriter.cpp index 15802cc818..2c5535869b 100644 --- a/ACL/test/Transport/Common/TestableAttachmentWriter.cpp +++ b/AVSCommon/Utils/test/Common/TestableAttachmentWriter.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -13,12 +13,12 @@ * permissions and limitations under the License. */ -#include "TestableAttachmentWriter.h" -#include "Common.h" +#include "AVSCommon/Utils/Common/TestableAttachmentWriter.h" +#include "AVSCommon/Utils/Common/Common.h" namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { using namespace avsCommon::avs::attachment; using namespace avsCommon::utils::sds; @@ -59,6 +59,6 @@ void TestableAttachmentWriter::close() { m_writer->close(); } -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK diff --git a/ACL/test/Transport/Common/TestableMessageObserver.cpp b/AVSCommon/Utils/test/Common/TestableMessageObserver.cpp similarity index 89% rename from ACL/test/Transport/Common/TestableMessageObserver.cpp rename to AVSCommon/Utils/test/Common/TestableMessageObserver.cpp index b558f17951..2e57243666 100644 --- a/ACL/test/Transport/Common/TestableMessageObserver.cpp +++ b/AVSCommon/Utils/test/Common/TestableMessageObserver.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -15,11 +15,11 @@ #include -#include "TestableMessageObserver.h" +#include "AVSCommon/Utils/Common/TestableMessageObserver.h" namespace alexaClientSDK { -namespace acl { -namespace test { +namespace avsCommon { +namespace utils { using namespace avsCommon::sdkInterfaces; @@ -55,6 +55,6 @@ bool TestableMessageObserver::waitForDirective( }); } -} // namespace test -} // namespace acl +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/ConfigurationNodeTest.cpp b/AVSCommon/Utils/test/ConfigurationNodeTest.cpp index 588566f2e8..9ff130127e 100644 --- a/AVSCommon/Utils/test/ConfigurationNodeTest.cpp +++ b/AVSCommon/Utils/test/ConfigurationNodeTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -80,6 +80,9 @@ static const std::string NEW_STRING_VALUE2_1_1 = "new-stringValue2.1.1"; /// Bad JSON string to verify handling the failure to parse JSON static const std::string BAD_JSON = "{ bad json }"; +/// Name of array root level object. +static const std::string ARRAY_OBJECT = "arrayObject"; + /// First JSON string to parse, serving as default for configuration values. // clang-format off static const std::string FIRST_JSON = R"( @@ -124,6 +127,21 @@ static const std::string THIRD_JSON = R"( })"; // clang-format on +/// A JSON string to test array. +// clang-format off +static const std::string ARRAY_JSON = R"( + { + "arrayObject" : [ + { + "object2.1" : "new-stringValue2.1" + }, + { + "object2.1" : "new-stringValue2.1.1" + } + ] + })"; +// clang-format on + /** * Class for testing the ConfigurationNode class */ @@ -133,27 +151,42 @@ class ConfigurationNodeTest : public ::testing::Test {}; * Verify initialization a configuration. Verify both the implementation of accessor methods and the results * of merging JSON streams. */ -TEST_F(ConfigurationNodeTest, testInitializationAndAccess) { +TEST_F(ConfigurationNodeTest, test_initializationAndAccess) { // Verify a null configuration results in failure - ASSERT_FALSE(ConfigurationNode::initialize({nullptr})); + std::vector> jsonStream; + jsonStream.push_back(nullptr); + ASSERT_FALSE(ConfigurationNode::initialize(jsonStream)); + jsonStream.clear(); // Verify invalid JSON results in failure - std::stringstream badStream; - badStream << BAD_JSON; - ASSERT_FALSE(ConfigurationNode::initialize({&badStream})); + auto badStream = std::shared_ptr(new std::stringstream()); + (*badStream) << BAD_JSON; + jsonStream.push_back(badStream); + ASSERT_FALSE(ConfigurationNode::initialize(jsonStream)); + jsonStream.clear(); // Combine valid JSON streams with overlapping values. Verify reported success. - std::stringstream firstStream; - firstStream << FIRST_JSON; - std::stringstream secondStream; - secondStream << SECOND_JSON; - std::stringstream thirdStream; - thirdStream << THIRD_JSON; - ASSERT_TRUE(ConfigurationNode::initialize({&firstStream, &secondStream, &thirdStream})); + auto firstStream = std::shared_ptr(new std::stringstream()); + (*firstStream) << FIRST_JSON; + auto secondStream = std::shared_ptr(new std::stringstream()); + (*secondStream) << SECOND_JSON; + auto thirdStream = std::shared_ptr(new std::stringstream()); + (*thirdStream) << THIRD_JSON; + auto arrayStream = std::shared_ptr(new std::stringstream()); + (*arrayStream) << ARRAY_JSON; + jsonStream.push_back(firstStream); + jsonStream.push_back(secondStream); + jsonStream.push_back(thirdStream); + jsonStream.push_back(arrayStream); + ASSERT_TRUE(ConfigurationNode::initialize(jsonStream)); + jsonStream.clear(); // Verify failure reported for subsequent initializations. - firstStream << FIRST_JSON; - ASSERT_FALSE(ConfigurationNode::initialize({&firstStream})); + auto firstStream1 = std::shared_ptr(new std::stringstream()); + (*firstStream1) << FIRST_JSON; + jsonStream.push_back(firstStream1); + ASSERT_FALSE(ConfigurationNode::initialize(jsonStream)); + jsonStream.clear(); // Verify non-found name results in a ConfigurationNode that evaluates to false. ASSERT_FALSE(ConfigurationNode::getRoot()[NON_OBJECT]); @@ -198,6 +231,33 @@ TEST_F(ConfigurationNodeTest, testInitializationAndAccess) { std::string string211; ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT2][OBJECT2_1].getString(STRING2_1_1, &string211)); ASSERT_EQ(string211, NEW_STRING_VALUE2_1_1); + + // Verify getting a non-array object with getArray will return an empty Configuration node. + ASSERT_FALSE(ConfigurationNode::getRoot().getArray(OBJECT1)); + + // Verify getting the array size of a non-array object will return zero. + ASSERT_TRUE(0 == ConfigurationNode::getRoot()[OBJECT1].getArraySize()); + + // Verify getting the array from a non-array object will return an empty Configuration node. + ASSERT_FALSE(ConfigurationNode::getRoot()[OBJECT1][1]); + + // Verify getting a array object with getArray will return an valid Configuration node. + auto array = ConfigurationNode::getRoot().getArray(ARRAY_OBJECT); + ASSERT_TRUE(array); + + // Make sure that the array size is 2 + auto arraySize = array.getArraySize(); + ASSERT_TRUE(2U == arraySize); + + // Make sure accessing an array outside range will return an empty Configuration Node. + ASSERT_FALSE(array[arraySize]); + + // Check if we can get the string from the first and second array item + std::string arrayString; + ASSERT_TRUE(array[0].getString(OBJECT2_1, &arrayString)); + ASSERT_EQ(arrayString, NEW_STRING_VALUE2_1); + ASSERT_TRUE(array[1].getString(OBJECT2_1, &arrayString)); + ASSERT_EQ(arrayString, NEW_STRING_VALUE2_1_1); } } // namespace test diff --git a/AVSCommon/Utils/test/ExecutorTest.cpp b/AVSCommon/Utils/test/ExecutorTest.cpp index d2c7d0a849..992a800809 100644 --- a/AVSCommon/Utils/test/ExecutorTest.cpp +++ b/AVSCommon/Utils/test/ExecutorTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ #include "ExecutorTestUtils.h" #include "AVSCommon/Utils/Threading/Executor.h" +#include "AVSCommon/Utils/WaitEvent.h" namespace alexaClientSDK { namespace avsCommon { @@ -25,44 +26,46 @@ namespace utils { namespace threading { namespace test { +using namespace utils::test; + class ExecutorTest : public ::testing::Test { public: Executor executor; }; -TEST_F(ExecutorTest, submitStdFunctionAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitStdFunctionAndVerifyExecution) { std::function function = []() {}; auto future = executor.submit(function); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } -TEST_F(ExecutorTest, submitStdBindAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitStdBindAndVerifyExecution) { auto future = executor.submit(std::bind(exampleFunctionParams, 0)); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } -TEST_F(ExecutorTest, submitLambdaAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitLambdaAndVerifyExecution) { auto future = executor.submit([]() {}); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } -TEST_F(ExecutorTest, submitFunctionPointerAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctionPointerAndVerifyExecution) { auto future = executor.submit(&exampleFunction); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } -TEST_F(ExecutorTest, submitFunctorAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctorAndVerifyExecution) { ExampleFunctor exampleFunctor; auto future = executor.submit(exampleFunctor); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } -TEST_F(ExecutorTest, submitFunctionWithPrimitiveReturnTypeNoArgsAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctionWithPrimitiveReturnTypeNoArgsAndVerifyExecution) { int value = VALUE; auto future = executor.submit([=]() { return value; }); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); @@ -70,7 +73,7 @@ TEST_F(ExecutorTest, submitFunctionWithPrimitiveReturnTypeNoArgsAndVerifyExecuti ASSERT_EQ(future.get(), value); } -TEST_F(ExecutorTest, submitFunctionWithObjectReturnTypeNoArgsAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctionWithObjectReturnTypeNoArgsAndVerifyExecution) { SimpleObject value(VALUE); auto future = executor.submit([=]() { return value; }); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); @@ -78,21 +81,21 @@ TEST_F(ExecutorTest, submitFunctionWithObjectReturnTypeNoArgsAndVerifyExecution) ASSERT_EQ(future.get().getValue(), value.getValue()); } -TEST_F(ExecutorTest, submitFunctionWithNoReturnTypePrimitiveArgsAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctionWithNoReturnTypePrimitiveArgsAndVerifyExecution) { int value = VALUE; auto future = executor.submit([](int number) {}, value); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } -TEST_F(ExecutorTest, submitFunctionWithNoReturnTypeObjectArgsAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctionWithNoReturnTypeObjectArgsAndVerifyExecution) { SimpleObject arg(0); auto future = executor.submit([](SimpleObject object) {}, arg); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } -TEST_F(ExecutorTest, submitFunctionWithPrimitiveReturnTypeObjectArgsAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctionWithPrimitiveReturnTypeObjectArgsAndVerifyExecution) { int value = VALUE; SimpleObject arg(0); auto future = executor.submit([=](SimpleObject object) { return value; }, arg); @@ -101,7 +104,7 @@ TEST_F(ExecutorTest, submitFunctionWithPrimitiveReturnTypeObjectArgsAndVerifyExe ASSERT_EQ(future.get(), value); } -TEST_F(ExecutorTest, submitFunctionWithObjectReturnTypePrimitiveArgsAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctionWithObjectReturnTypePrimitiveArgsAndVerifyExecution) { int arg = 0; SimpleObject value(VALUE); auto future = executor.submit([=](int primitive) { return value; }, arg); @@ -110,7 +113,7 @@ TEST_F(ExecutorTest, submitFunctionWithObjectReturnTypePrimitiveArgsAndVerifyExe ASSERT_EQ(future.get().getValue(), value.getValue()); } -TEST_F(ExecutorTest, submitFunctionWithPrimitiveReturnTypePrimitiveArgsAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctionWithPrimitiveReturnTypePrimitiveArgsAndVerifyExecution) { int arg = 0; int value = VALUE; auto future = executor.submit([=](int number) { return value; }, arg); @@ -119,7 +122,7 @@ TEST_F(ExecutorTest, submitFunctionWithPrimitiveReturnTypePrimitiveArgsAndVerify ASSERT_EQ(future.get(), value); } -TEST_F(ExecutorTest, submitFunctionWithObjectReturnTypeObjectArgsAndVerifyExecution) { +TEST_F(ExecutorTest, test_submitFunctionWithObjectReturnTypeObjectArgsAndVerifyExecution) { SimpleObject value(VALUE); SimpleObject arg(0); auto future = executor.submit([=](SimpleObject object) { return value; }, arg); @@ -128,7 +131,7 @@ TEST_F(ExecutorTest, submitFunctionWithObjectReturnTypeObjectArgsAndVerifyExecut ASSERT_EQ(future.get().getValue(), value.getValue()); } -TEST_F(ExecutorTest, submitToFront) { +TEST_F(ExecutorTest, test_submitToFront) { std::atomic ready(false); std::atomic blocked(false); std::list order; @@ -167,6 +170,30 @@ TEST_F(ExecutorTest, submitToFront) { ASSERT_EQ(order.back(), 2); } +TEST_F(ExecutorTest, test_executionOrderEqualToSubmitOrder) { + WaitEvent waitSetUp; + executor.submit([&waitSetUp] { waitSetUp.wait(SHORT_TIMEOUT_MS); }); + + // submit a task which will block the executor + executor.submit([&waitSetUp] { waitSetUp.wait(SHORT_TIMEOUT_MS); }); + + std::list order; + std::list expectedOrder = {1, 2, 3}; + for (auto& value : expectedOrder) { + // submit tasks in the expected order. + executor.submit([&order, value] { order.push_back(value); }); + } + + // unblock the executor + waitSetUp.wakeUp(); + + // wait for all tasks to complete + executor.waitForSubmittedTasks(); + + // verify execution order + ASSERT_EQ(order, expectedOrder); +} + /// Used by @c futureWaitsForTaskCleanup delay and timestamp the time of lambda parameter destruction. struct SlowDestructor { /// Constructor. @@ -193,7 +220,7 @@ struct SlowDestructor { }; /// This test verifies that the executor waits to fulfill its promise until after the task is cleaned up. -TEST_F(ExecutorTest, futureWaitsForTaskCleanup) { +TEST_F(ExecutorTest, test_futureWaitsForTaskCleanup) { std::atomic cleanedUp(false); SlowDestructor slowDestructor; @@ -212,7 +239,7 @@ TEST_F(ExecutorTest, futureWaitsForTaskCleanup) { } /// This test verifies that the shutdown function completes the current task and does not accept new tasks. -TEST_F(ExecutorTest, shutdown) { +TEST_F(ExecutorTest, test_shutdown) { std::atomic ready(false); std::atomic blocked(false); @@ -247,6 +274,47 @@ TEST_F(ExecutorTest, shutdown) { ASSERT_FALSE(rejected.valid()); } +/// Test that calling submit after shutdown will fail the job. +TEST_F(ExecutorTest, test_pushAfterExecutordownFail) { + executor.shutdown(); + ASSERT_TRUE(executor.isShutdown()); + + EXPECT_FALSE(executor.submit([] {}).valid()); + EXPECT_FALSE(executor.submitToFront([] {}).valid()); +} + +/// Test that shutdown cancel jobs in the queue. +TEST_F(ExecutorTest, test_shutdownCancelJob) { + bool executed = false; + WaitEvent waitSetUp, waitJobStart; + std::future jobToDropResult; + + // Job that should be cancelled and never run. + auto jobToDrop = [&executed] { executed = true; }; + + // Job used to validate that jobToDrop return value becomes available (but invalid). + auto jobToWaitDrop = [&jobToDropResult, &waitSetUp, &waitJobStart] { + waitJobStart.wakeUp(); + waitSetUp.wait(SHORT_TIMEOUT_MS); + jobToDropResult.wait_for(SHORT_TIMEOUT_MS); + }; + + // 1st job waits for setup to be done then wait for the second job to be cancelled. + executor.submit(jobToWaitDrop); + + // 2nd job that should never run. When cancelled, its return will become available. + jobToDropResult = executor.submit(jobToDrop); + + // Wake up first job and wait for it to start running. + waitSetUp.wakeUp(); + waitJobStart.wait(); + + // Shutdown should cancel enqueued jobs and wait for the ongoing job. + executor.shutdown(); + + // Executed should still be false. + EXPECT_FALSE(executed); +} } // namespace test } // namespace threading } // namespace utils diff --git a/AVSCommon/Utils/test/ExecutorTestUtils.h b/AVSCommon/Utils/test/ExecutorTestUtils.h index c035759cb4..96d18d725c 100644 --- a/AVSCommon/Utils/test/ExecutorTestUtils.h +++ b/AVSCommon/Utils/test/ExecutorTestUtils.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -27,8 +27,8 @@ namespace test { /// Simple integer value to be reused in tests static const int VALUE = 1234; -/// Timeout to use for waiting on calls within testss -static const std::chrono::milliseconds SHORT_TIMEOUT_MS = std::chrono::milliseconds(50); +/// Timeout to use for waiting on calls within tests +static const std::chrono::milliseconds SHORT_TIMEOUT_MS = std::chrono::milliseconds(100); /// A simple task which takes an integer argument and returns the argument when executed static auto TASK = [](int arg) { return arg; }; diff --git a/AVSCommon/Utils/test/HTTPContentTest.cpp b/AVSCommon/Utils/test/HTTPContentTest.cpp new file mode 100644 index 0000000000..da8a1a6ec8 --- /dev/null +++ b/AVSCommon/Utils/test/HTTPContentTest.cpp @@ -0,0 +1,124 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include "AVSCommon/Utils/HTTPContent.h" +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace logger { +namespace test { + +using namespace ::testing; + +/// A status code that represents success. +const static long SUCCESS_STATUS_CODE{200}; + +/// A status code that represents partial content. +const static long SUCCESS_PARTIAL_CONTENT_STATUS_CODE{206}; + +/// A status code that represents failure. +const static long BAD_STATUS_CODE{0}; + +/// A content type. +const static std::string TEST_CONTENT_TYPE{"unknown"}; + +/** + * Class for testing the HTTPContent structure. + */ +class HTTPContentTest : public ::testing::Test { +public: + /// Set up the test harness for running a test. + void SetUp() override; + +protected: + /// A promise to the caller of @c getContent() that the HTTP status code will be set. + std::promise m_statusCodePromise; + + /// A promise to the caller of @c getContent() that the HTTP content type will be set. + std::promise m_contentTypePromise; + + /// The @c HTTPContent for testing. + std::unique_ptr m_httpContent; +}; + +void HTTPContentTest::SetUp() { + auto httpStatusCodeFuture = m_statusCodePromise.get_future(); + auto contentTypeFuture = m_contentTypePromise.get_future(); + + m_httpContent = + memory::make_unique(std::move(httpStatusCodeFuture), std::move(contentTypeFuture), nullptr); +} + +/// Test that isStatusCodeSuccess returns true for @c SUCCESS_STATUS_CODE. +TEST_F(HTTPContentTest, test_readStatusCodeSuccess) { + m_statusCodePromise.set_value(SUCCESS_STATUS_CODE); + m_contentTypePromise.set_value(TEST_CONTENT_TYPE); + + EXPECT_TRUE(m_httpContent->isStatusCodeSuccess()); +} + +/// Test that isStatusCodeSuccess returns true for @c SUCCESS_PARTIAL_CONTENT_STATUS_CODE. +TEST_F(HTTPContentTest, test_readStatusCodePartialContentSuccess) { + m_statusCodePromise.set_value(SUCCESS_PARTIAL_CONTENT_STATUS_CODE); + m_contentTypePromise.set_value(TEST_CONTENT_TYPE); + + EXPECT_TRUE(m_httpContent->isStatusCodeSuccess()); +} + +/// Test that isStatusCodeSuccess returns false for @c BAD_STATUS_CODE. +TEST_F(HTTPContentTest, test_readStatusCodeNotSuccess) { + m_statusCodePromise.set_value(BAD_STATUS_CODE); + m_contentTypePromise.set_value(TEST_CONTENT_TYPE); + + EXPECT_FALSE(m_httpContent->isStatusCodeSuccess()); +} + +/// Test that we can use @c getStatusCode() to get the status code after using @c isStatusCodeSuccess(). +TEST_F(HTTPContentTest, test_readStatusCodeMoreThanOnce) { + m_statusCodePromise.set_value(BAD_STATUS_CODE); + m_contentTypePromise.set_value(TEST_CONTENT_TYPE); + + EXPECT_FALSE(m_httpContent->isStatusCodeSuccess()); + + EXPECT_EQ(m_httpContent->getStatusCode(), BAD_STATUS_CODE); +} + +/// Test that we can use @c getContentType() to get the status code after using @c isStatusCodeSuccess(). +TEST_F(HTTPContentTest, test_readContentTypeMoreThanOnce) { + m_statusCodePromise.set_value(BAD_STATUS_CODE); + m_contentTypePromise.set_value(TEST_CONTENT_TYPE); + + EXPECT_EQ(m_httpContent->getContentType(), TEST_CONTENT_TYPE); + EXPECT_EQ(m_httpContent->getContentType(), TEST_CONTENT_TYPE); +} + +/// Test that we can retrieve the attachment reader, even if it's nullptr. +TEST_F(HTTPContentTest, test_getDataStream) { + EXPECT_EQ(m_httpContent->getDataStream(), nullptr); +} + +} // namespace test +} // namespace logger +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/JSONGeneratorTest.cpp b/AVSCommon/Utils/test/JSONGeneratorTest.cpp new file mode 100644 index 0000000000..ebfa2de1d5 --- /dev/null +++ b/AVSCommon/Utils/test/JSONGeneratorTest.cpp @@ -0,0 +1,170 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include + +#include "AVSCommon/Utils/JSON/JSONGenerator.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace json { +namespace test { + +using namespace ::testing; +using namespace rapidjson; + +class JsonGeneratorTest : public Test { +protected: + JsonGenerator m_generator; +}; + +/// Test json generator when no member is given. +TEST_F(JsonGeneratorTest, test_emptyJson) { + EXPECT_EQ(m_generator.toString(), "{}"); +} + +/// Test json generator object creation. +TEST_F(JsonGeneratorTest, test_jsonObject) { + EXPECT_TRUE(m_generator.startObject("key")); + EXPECT_TRUE(m_generator.finishObject()); + + auto expected = R"({"key":{}})"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test json generator int creation. +TEST_F(JsonGeneratorTest, test_jsonInt) { + int value = std::numeric_limits::max(); + EXPECT_TRUE(m_generator.addMember("member", value)); + + auto expected = R"({"member":)" + std::to_string(value) + "}"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test json generator long creation. +TEST_F(JsonGeneratorTest, test_jsonLong) { + int64_t value = std::numeric_limits::max(); + EXPECT_TRUE(m_generator.addMember("member", value)); + + auto expected = R"({"member":)" + std::to_string(value) + "}"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test json generator long creation. +TEST_F(JsonGeneratorTest, test_jsonUInt) { + unsigned int value = std::numeric_limits::max(); + EXPECT_TRUE(m_generator.addMember("member", value)); + + auto expected = R"({"member":)" + std::to_string(value) + "}"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test json generator long creation. +TEST_F(JsonGeneratorTest, test_jsonULong) { + uint64_t value = std::numeric_limits::max(); + EXPECT_TRUE(m_generator.addMember("member", value)); + + auto expected = R"({"member":)" + std::to_string(value) + "}"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test json generator boolean creation. +TEST_F(JsonGeneratorTest, test_jsonBool) { + bool value = true; + EXPECT_TRUE(m_generator.addMember("member", value)); + + auto expected = R"({"member":true})"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test json generator char creation. +TEST_F(JsonGeneratorTest, test_jsonCString) { + EXPECT_TRUE(m_generator.addMember("member", "value")); + + auto expected = R"({"member":"value"})"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test json generator char creation. +TEST_F(JsonGeneratorTest, test_jsonNullCString) { + EXPECT_FALSE(m_generator.addMember("member", nullptr)); + + auto expected = R"({})"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test json raw creation. +TEST_F(JsonGeneratorTest, test_jsonRawJsonMember) { + EXPECT_TRUE(m_generator.addRawJsonMember("member1", R"({"member11":"value11"})")); + EXPECT_TRUE(m_generator.addMember("member2", "value2")); + + auto expected = R"({"member1":{"member11":"value11"},"member2":"value2"})"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test json raw validation. +TEST_F(JsonGeneratorTest, test_jsonRawJsonMemberFailed) { + EXPECT_FALSE(m_generator.addRawJsonMember("member1", R"(invalid)")); + EXPECT_TRUE(m_generator.addMember("member2", "value2")); + + auto expected = R"({"member2":"value2"})"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test close when there is no open object. +TEST_F(JsonGeneratorTest, test_closeTooMany) { + EXPECT_TRUE(m_generator.finishObject()); + EXPECT_FALSE(m_generator.finishObject()); +} + +/// Test to string with open objects. +TEST_F(JsonGeneratorTest, test_openObjects) { + EXPECT_TRUE(m_generator.startObject("key")); + + auto expected = R"({"key":{)"; + EXPECT_EQ(m_generator.toString(false), expected); +} + +/// Test finalize open objects. +TEST_F(JsonGeneratorTest, test_finalizeObjects) { + EXPECT_TRUE(m_generator.startObject("key1")); + EXPECT_TRUE(m_generator.startObject("key2")); + + auto expected = R"({"key1":{"key2":{}}})"; + EXPECT_EQ(m_generator.toString(), expected); +} + +/// Test operations after finalize. +TEST_F(JsonGeneratorTest, test_addMemberAfterFinalize) { + EXPECT_EQ(m_generator.toString(), "{}"); + EXPECT_EQ(m_generator.toString(), "{}"); + ASSERT_TRUE(m_generator.isFinalized()); + EXPECT_FALSE(m_generator.startObject("key2")); + EXPECT_FALSE(m_generator.addMember("key1", "value")); + EXPECT_FALSE(m_generator.addMember("key2", 10)); + EXPECT_FALSE(m_generator.addMember("key3", 10u)); + EXPECT_FALSE(m_generator.addMember("key4", static_cast(10L))); + EXPECT_FALSE(m_generator.addMember("key5", static_cast(10uL))); + + auto expected = "{}"; + EXPECT_EQ(m_generator.toString(), expected); +} + +} // namespace test +} // namespace json +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/JSONUtilTest.cpp b/AVSCommon/Utils/test/JSONUtilTest.cpp index b7fce64290..5b4898a684 100644 --- a/AVSCommon/Utils/test/JSONUtilTest.cpp +++ b/AVSCommon/Utils/test/JSONUtilTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -69,6 +69,8 @@ static const int OUTPUT_DEFAULT_INT_VALUE = 42; static const std::string EXPECTED_STRING_VALUE = "expectedValue"; /// Expected int value. static const int EXPECTED_INT_VALUE = 123; +/// Expected uint64_t value. +static const uint64_t EXPECTED_UNSIGNED_INT64_VALUE = UINT64_MAX; /// Expected int value converted to a string. static const std::string EXPECTED_INT_VALUE_STRINGIFIED = "123"; @@ -131,7 +133,7 @@ class JSONUtilTest : public ::testing::Test {}; * Tests retrieveValue(const std::string jsonString, const std::string& key, T* value) * with T = std::string for getting child object as a string. */ -TEST_F(JSONUtilTest, validJsonChildObjectAsString) { +TEST_F(JSONUtilTest, test_validJsonChildObjectAsString) { std::string value; ASSERT_TRUE(jsonUtils::retrieveValue(EMPTY_DIRECTIVE, DIRECTIVE_KEY, &value)); ASSERT_EQ(value, STRING_VALUE_EMPTY_JSON_OBJECT); @@ -141,7 +143,7 @@ TEST_F(JSONUtilTest, validJsonChildObjectAsString) { * Tests retrieveValue(const std::string jsonString, const std::string& key, T* value) * with T = std::string for getting value of a scalar string. */ -TEST_F(JSONUtilTest, validJsonScalarString) { +TEST_F(JSONUtilTest, test_validJsonScalarString) { std::string value; ASSERT_TRUE(jsonUtils::retrieveValue(VALID_JSON_STRING_VALUE, VALUE_KEY, &value)); ASSERT_EQ(value, EXPECTED_STRING_VALUE); @@ -151,7 +153,7 @@ TEST_F(JSONUtilTest, validJsonScalarString) { * Tests retrieveValue(const std::string jsonString, const std::string& key, T* value) * with T = int64 for getting an integer value. */ -TEST_F(JSONUtilTest, validJsonInteger) { +TEST_F(JSONUtilTest, test_validJsonInteger) { int64_t value = OUTPUT_DEFAULT_INT_VALUE; ASSERT_TRUE(jsonUtils::retrieveValue(VALID_JSON_INTEGER_VALUE, VALUE_KEY, &value)); ASSERT_EQ(value, EXPECTED_INT_VALUE); @@ -161,7 +163,7 @@ TEST_F(JSONUtilTest, validJsonInteger) { * Tests retrieveValue(const std::string jsonString, const std::string& key, T* value) * with T = int64 and an invalid JSON. Returns false. */ -TEST_F(JSONUtilTest, retrieveValueStringBasedInt64FromInvalidJSON) { +TEST_F(JSONUtilTest, test_retrieveValueStringBasedInt64FromInvalidJSON) { int64_t value = OUTPUT_DEFAULT_INT_VALUE; ASSERT_FALSE(retrieveValue(INVALID_JSON, VALUE_KEY, &value)); ASSERT_EQ(value, OUTPUT_DEFAULT_INT_VALUE); @@ -171,7 +173,7 @@ TEST_F(JSONUtilTest, retrieveValueStringBasedInt64FromInvalidJSON) { * Tests retrieveValue(const std::string jsonString, const std::string& key, T* value) * with T = std::string and an invalid JSON. Returns false. */ -TEST_F(JSONUtilTest, retrieveValueStringBasedStringFromInvalidJSON) { +TEST_F(JSONUtilTest, test_retrieveValueStringBasedStringFromInvalidJSON) { std::string value = OUTPUT_DEFAULT_TEXT_STRING; ASSERT_FALSE(retrieveValue(INVALID_JSON, VALUE_KEY, &value)); ASSERT_EQ(value, OUTPUT_DEFAULT_TEXT_STRING); @@ -181,7 +183,7 @@ TEST_F(JSONUtilTest, retrieveValueStringBasedStringFromInvalidJSON) { * Tests retrieveValue(const std::string jsonString, const std::string& key, T* value) * with T = int64 and an incorrect key. Returns false. */ -TEST_F(JSONUtilTest, retrieveValueStringBasedWithIncorrectKey) { +TEST_F(JSONUtilTest, test_retrieveValueStringBasedWithIncorrectKey) { int64_t value = OUTPUT_DEFAULT_INT_VALUE; ASSERT_FALSE(retrieveValue(VALID_JSON_INTEGER_VALUE, MISSING_KEY, &value)); ASSERT_EQ(value, OUTPUT_DEFAULT_INT_VALUE); @@ -191,7 +193,7 @@ TEST_F(JSONUtilTest, retrieveValueStringBasedWithIncorrectKey) { * Tests retrieveValue(const std::string jsonString, const std::string& key, T* value) * with T = int64 and a null output param. Returns false. */ -TEST_F(JSONUtilTest, retrieveValueStringBasedWithNull) { +TEST_F(JSONUtilTest, test_retrieveValueStringBasedWithNull) { int64_t* value = nullptr; ASSERT_FALSE(retrieveValue(VALID_JSON_INTEGER_VALUE, VALUE_KEY, value)); } @@ -200,7 +202,7 @@ TEST_F(JSONUtilTest, retrieveValueStringBasedWithNull) { * Tests retrieveValue(const rapidjson::Value& jsonNode, const std::string& key, T* value) * with T = int64 and a value of invalid type. Returns false. */ -TEST_F(JSONUtilTest, retrieveValueDocumentBasedWithInvalidValueType) { +TEST_F(JSONUtilTest, test_retrieveValueDocumentBasedWithInvalidValueType) { Document doc; doc.Parse(VALID_JSON_STRING_VALUE); int64_t value; @@ -211,7 +213,7 @@ TEST_F(JSONUtilTest, retrieveValueDocumentBasedWithInvalidValueType) { * Tests retrieveValue(const rapidjson::Value& jsonNode, const std::string& key, T* value) * with T = int64 and a null output param. Returns false. */ -TEST_F(JSONUtilTest, retrieveValueDocumentBasedWithNull) { +TEST_F(JSONUtilTest, test_retrieveValueDocumentBasedWithNull) { Document doc; doc.Parse(VALID_JSON_INTEGER_VALUE); int64_t* value = nullptr; @@ -222,7 +224,7 @@ TEST_F(JSONUtilTest, retrieveValueDocumentBasedWithNull) { * Tests retrieveValue(const rapidjson::Value& jsonNode, const std::string& key, T* value) * with T = int64 and a valid value. Returns true and obtains the correct value. */ -TEST_F(JSONUtilTest, retrieveValueDocumentBasedWithValidInt64) { +TEST_F(JSONUtilTest, test_retrieveValueDocumentBasedWithValidInt64) { Document doc; doc.Parse(VALID_JSON_INTEGER_VALUE); int64_t value; @@ -233,7 +235,7 @@ TEST_F(JSONUtilTest, retrieveValueDocumentBasedWithValidInt64) { /** * Tests findNode with a Null output param. Returns false. */ -TEST_F(JSONUtilTest, findNodeNull) { +TEST_F(JSONUtilTest, test_findNodeNull) { Document doc; doc.Parse(SPEAK_DIRECTIVE); ASSERT_FALSE(findNode(doc, JSON_MESSAGE_HEADER_STRING, nullptr)); @@ -242,7 +244,7 @@ TEST_F(JSONUtilTest, findNodeNull) { /** * Tests findNode with a valid key. Returns true with iterator != MemberEnd(). */ -TEST_F(JSONUtilTest, findNodeKeyExists) { +TEST_F(JSONUtilTest, test_findNodeKeyExists) { Document doc; doc.Parse(SPEAK_DIRECTIVE); Value::ConstMemberIterator iterator; @@ -253,7 +255,7 @@ TEST_F(JSONUtilTest, findNodeKeyExists) { /** * Tests findNode with a non-existent key. Returns false. */ -TEST_F(JSONUtilTest, findNodeKeyMissing) { +TEST_F(JSONUtilTest, test_findNodeKeyMissing) { Document doc; doc.Parse(SPEAK_DIRECTIVE); Value::ConstMemberIterator iterator; @@ -263,14 +265,14 @@ TEST_F(JSONUtilTest, findNodeKeyMissing) { /** * Tests parseJSON with a null output param. Returns false. */ -TEST_F(JSONUtilTest, parseJSONNullOutputParam) { +TEST_F(JSONUtilTest, test_parseJSONNullOutputParam) { ASSERT_FALSE(parseJSON(SPEAK_DIRECTIVE, nullptr)); } /** * Tests parseJSON with a valid json. Returns true. */ -TEST_F(JSONUtilTest, parseJSONValidJSON) { +TEST_F(JSONUtilTest, test_parseJSONValidJSON) { Document doc; ASSERT_TRUE(parseJSON(SPEAK_DIRECTIVE, &doc)); ASSERT_FALSE(doc.HasParseError()); @@ -279,7 +281,7 @@ TEST_F(JSONUtilTest, parseJSONValidJSON) { /** * Tests parseJSON with an invalid json. Returns false. */ -TEST_F(JSONUtilTest, parseJSONInvalidJSON) { +TEST_F(JSONUtilTest, test_parseJSONInvalidJSON) { Document doc; ASSERT_FALSE(parseJSON(INVALID_JSON, &doc)); ASSERT_TRUE(doc.HasParseError()); @@ -289,7 +291,7 @@ TEST_F(JSONUtilTest, parseJSONInvalidJSON) { * Tests convertToValue with Value of rapidjson::Type::kStringType. Returns * true and contains the correct value. */ -TEST_F(JSONUtilTest, convertToStringValueWithString) { +TEST_F(JSONUtilTest, test_convertToStringValueWithString) { rapidjson::Value expected; expected.SetString(STRING_VALUE.c_str(), STRING_VALUE.length()); std::string actual; @@ -301,7 +303,7 @@ TEST_F(JSONUtilTest, convertToStringValueWithString) { * Tests convertToValue with Value of rapidjson::Type::kObjectType. * Returns true and contains the correct value. */ -TEST_F(JSONUtilTest, convertToStringValueWithObject) { +TEST_F(JSONUtilTest, test_convertToStringValueWithObject) { rapidjson::Value emptyObject(kObjectType); std::string actual; ASSERT_TRUE(convertToValue(emptyObject, &actual)); @@ -312,7 +314,7 @@ TEST_F(JSONUtilTest, convertToStringValueWithObject) { * Tests convertToValue with and invalid Value of rapidjson::Type::kNullType. * Returns false. */ -TEST_F(JSONUtilTest, convertToStringValueWithInvalidValue) { +TEST_F(JSONUtilTest, test_convertToStringValueWithInvalidValue) { rapidjson::Value nullValue(kNullType); std::string value; ASSERT_FALSE(convertToValue(nullValue, &value)); @@ -322,7 +324,7 @@ TEST_F(JSONUtilTest, convertToStringValueWithInvalidValue) { * Tests convertToValue with null output param. * Returns false. */ -TEST_F(JSONUtilTest, convertToStringValueWithNullOutputParam) { +TEST_F(JSONUtilTest, test_convertToStringValueWithNullOutputParam) { rapidjson::Value node; node.SetString(STRING_VALUE.c_str(), STRING_VALUE.length()); std::string* value = nullptr; @@ -332,7 +334,7 @@ TEST_F(JSONUtilTest, convertToStringValueWithNullOutputParam) { /** * Tests convertToValue with valid int64_6. Returns true and contains the correct value. */ -TEST_F(JSONUtilTest, convertToInt64ValueWithInt64) { +TEST_F(JSONUtilTest, test_convertToInt64ValueWithInt64) { rapidjson::Value expected(EXPECTED_INT_VALUE); int64_t actual; ASSERT_TRUE(convertToValue(expected, &actual)); @@ -342,17 +344,46 @@ TEST_F(JSONUtilTest, convertToInt64ValueWithInt64) { /** * Tests convertToValue with double. Returns false. */ -TEST_F(JSONUtilTest, convertToInt64ValueWithDouble) { +TEST_F(JSONUtilTest, test_convertToInt64ValueWithDouble) { rapidjson::Value expected(A_DOUBLE); int64_t actual; ASSERT_FALSE(convertToValue(expected, &actual)); } +/** + * Tests convertToValue with null output param. + * Returns false. + */ +TEST_F(JSONUtilTest, test_convertToUint64ValueWithNullOutputParam) { + rapidjson::Value node(EXPECTED_UNSIGNED_INT64_VALUE); + uint64_t* value = nullptr; + ASSERT_FALSE(convertToValue(node, value)); +} + +/** + * Tests convertToValue with valid uint64_t. Returns true and contains the correct value. + */ +TEST_F(JSONUtilTest, test_convertToUint64ValueWithUint64) { + rapidjson::Value expected(EXPECTED_UNSIGNED_INT64_VALUE); + uint64_t actual; + ASSERT_TRUE(convertToValue(expected, &actual)); + ASSERT_EQ(expected.GetUint64(), actual); +} + +/** + * Tests convertToValue with double. Returns false. + */ +TEST_F(JSONUtilTest, test_convertToUint64ValueWithDouble) { + rapidjson::Value expected(A_DOUBLE); + uint64_t actual; + ASSERT_FALSE(convertToValue(expected, &actual)); +} + /** * Tests convertToValue with null output param. * Returns false. */ -TEST_F(JSONUtilTest, convertToInt64ValueWithNullOutputParam) { +TEST_F(JSONUtilTest, test_convertToInt64ValueWithNullOutputParam) { rapidjson::Value node(EXPECTED_INT_VALUE); int64_t* value = nullptr; ASSERT_FALSE(convertToValue(node, value)); @@ -362,7 +393,7 @@ TEST_F(JSONUtilTest, convertToInt64ValueWithNullOutputParam) { * Tests convertToValue with null output param. * Returns false. */ -TEST_F(JSONUtilTest, convertToBoolValueWithNullOutputParam) { +TEST_F(JSONUtilTest, test_convertToBoolValueWithNullOutputParam) { rapidjson::Value node(A_BOOL); bool* value = nullptr; ASSERT_FALSE(convertToValue(node, value)); @@ -371,7 +402,7 @@ TEST_F(JSONUtilTest, convertToBoolValueWithNullOutputParam) { /** * Tests convertToValue with a nonbool. Returns false. */ -TEST_F(JSONUtilTest, convertToBoolValueWithNonBool) { +TEST_F(JSONUtilTest, test_convertToBoolValueWithNonBool) { rapidjson::Value expected(A_DOUBLE); bool actual; ASSERT_FALSE(convertToValue(expected, &actual)); @@ -380,7 +411,7 @@ TEST_F(JSONUtilTest, convertToBoolValueWithNonBool) { /** * Tests convertToValue with valid bool. Returns true and contains the correct value. */ -TEST_F(JSONUtilTest, convertToBoolValueWithBool) { +TEST_F(JSONUtilTest, test_convertToBoolValueWithBool) { rapidjson::Value expected(A_BOOL); bool actual; ASSERT_TRUE(convertToValue(expected, &actual)); diff --git a/AVSCommon/Utils/test/LogEntryStreamTest.cpp b/AVSCommon/Utils/test/LogEntryStreamTest.cpp index 36d71d62ed..c84f5e76c7 100644 --- a/AVSCommon/Utils/test/LogEntryStreamTest.cpp +++ b/AVSCommon/Utils/test/LogEntryStreamTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -35,13 +35,13 @@ class LogEntryStreamTest : public ::testing::Test { }; /// Test that a new LogEntryStream instance's c_str() returns an empty string. -TEST_F(LogEntryStreamTest, emptyStream) { +TEST_F(LogEntryStreamTest, test_emptyStream) { ASSERT_NE(m_stream.c_str(), nullptr); ASSERT_EQ(strlen(m_stream.c_str()), 0u); } /// Send a character to an empty LogEntryStream. Expect that c_str() returns a string with just that character. -TEST_F(LogEntryStreamTest, shortString) { +TEST_F(LogEntryStreamTest, test_shortString) { const char SOME_CHAR = 'x'; m_stream << SOME_CHAR; ASSERT_EQ(SOME_CHAR, m_stream.c_str()[0]); @@ -49,7 +49,7 @@ TEST_F(LogEntryStreamTest, shortString) { } /// Send a medium sized string test to an empty LogEntryStream. Expect that c_str() returns a matching string. -TEST_F(LogEntryStreamTest, mediumString) { +TEST_F(LogEntryStreamTest, test_mediumString) { const std::string MEDIUM_STRING = "Hello World!"; m_stream << MEDIUM_STRING; ASSERT_EQ(MEDIUM_STRING, m_stream.c_str()); @@ -57,7 +57,7 @@ TEST_F(LogEntryStreamTest, mediumString) { } /// Send a long string test to an empty LogEntryStream. Expect that c_str() returns a matching string. -TEST_F(LogEntryStreamTest, longString) { +TEST_F(LogEntryStreamTest, test_longString) { std::string longString; for (int ix = 0; ix < 100; ix++) { longString += "The quick brown fox jumped over the lazy dog."; @@ -68,7 +68,7 @@ TEST_F(LogEntryStreamTest, longString) { } /// Send a few short strings. Expect that c_str() returns the concatenation of those strings. -TEST_F(LogEntryStreamTest, aFewStrings) { +TEST_F(LogEntryStreamTest, test_aFewStrings) { const std::string SHORT_STRING_1 = "abc"; m_stream << SHORT_STRING_1; const std::string SHORT_STRING_2 = "xyz"; @@ -81,7 +81,7 @@ TEST_F(LogEntryStreamTest, aFewStrings) { } /// Send a bunch of ints and strings. Expect that c_str() matches the result of sending the same to ostringstream. -TEST_F(LogEntryStreamTest, aLotOfStrings) { +TEST_F(LogEntryStreamTest, test_aLotOfStrings) { std::ostringstream expected; const std::string MEDIUM_STRING = "Half a bee, philosophically\nMust, ipso facto, half not be."; for (int ix = 0; ix < 100; ix++) { diff --git a/AVSCommon/Utils/test/LoggerTest.cpp b/AVSCommon/Utils/test/LoggerTest.cpp index af12d543c9..8e430a0a3e 100644 --- a/AVSCommon/Utils/test/LoggerTest.cpp +++ b/AVSCommon/Utils/test/LoggerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -70,6 +70,10 @@ static const std::string UNESCAPED_METADATA_VALUE = R"(reserved_chars['\' ',' ': static const std::string TEST_MESSAGE_STRING = "Hello World!"; /// Another String used to test that the message component is logged static const std::string TEST_MESSAGE_STRING_1 = "World Hello!"; +/// A const char* nullptr to verify the logger won't crash in degenerate cases +static const char* TEST_MESSAGE_CONST_NULL_CHAR_PTR = nullptr; +/// A char* nullptr to verify the logger won't crash in degenerate cases +static char* TEST_MESSAGE_NULL_CHAR_PTR = nullptr; /** * Mock derivation of Logger for verifying calls and parameters to those calls. @@ -334,7 +338,7 @@ void LoggerTest::exerciseLevels() { * Test delivery of log messages when the log level is set to DEBUG9. This test sets the log level to DEBUG9 * and then verifies that all logs (except those compiled off) are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug9Level) { +TEST_F(LoggerTest, test_logDebug9Level) { setLevelExpectations(Level::DEBUG9); exerciseLevels(); } @@ -343,7 +347,7 @@ TEST_F(LoggerTest, logDebug9Level) { * Test delivery of log messages when the log level is set to DEBUG8. This test sets the log level to DEBUG8 * and then verifies that all logs (except those compiled off) are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug8Level) { +TEST_F(LoggerTest, test_logDebug8Level) { setLevelExpectations(Level::DEBUG8); exerciseLevels(); } @@ -352,7 +356,7 @@ TEST_F(LoggerTest, logDebug8Level) { * Test delivery of log messages when the log level is set to DEBUG7. This test sets the log level to DEBUG7 * and then verifies that all logs (except those compiled off) are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug7Level) { +TEST_F(LoggerTest, test_logDebug7Level) { setLevelExpectations(Level::DEBUG7); exerciseLevels(); } @@ -361,7 +365,7 @@ TEST_F(LoggerTest, logDebug7Level) { * Test delivery of log messages when the log level is set to DEBUG6. This test sets the log level to DEBUG6 * and then verifies that all logs (except those compiled off) are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug6Level) { +TEST_F(LoggerTest, test_logDebug6Level) { setLevelExpectations(Level::DEBUG6); exerciseLevels(); } @@ -370,7 +374,7 @@ TEST_F(LoggerTest, logDebug6Level) { * Test delivery of log messages when the log level is set to DEBUG5. This test sets the log level to DEBUG5 * and then verifies that all logs (except those compiled off) are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug5Level) { +TEST_F(LoggerTest, test_logDebug5Level) { setLevelExpectations(Level::DEBUG5); exerciseLevels(); } @@ -379,7 +383,7 @@ TEST_F(LoggerTest, logDebug5Level) { * Test delivery of log messages when the log level is set to DEBUG4. This test sets the log level to DEBUG4 * and then verifies that all logs (except those compiled off) are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug4Level) { +TEST_F(LoggerTest, test_logDebug4Level) { setLevelExpectations(Level::DEBUG4); exerciseLevels(); } @@ -388,7 +392,7 @@ TEST_F(LoggerTest, logDebug4Level) { * Test delivery of log messages when the log level is set to DEBUG3. This test sets the log level to DEBUG3 * and then verifies that all logs (except those compiled off) are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug3Level) { +TEST_F(LoggerTest, test_logDebug3Level) { setLevelExpectations(Level::DEBUG3); exerciseLevels(); } @@ -397,7 +401,7 @@ TEST_F(LoggerTest, logDebug3Level) { * Test delivery of log messages when the log level is set to DEBUG2. This test sets the log level to DEBUG2 * and then verifies that all logs (except those compiled off) are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug2Level) { +TEST_F(LoggerTest, test_logDebug2Level) { setLevelExpectations(Level::DEBUG2); exerciseLevels(); } @@ -406,7 +410,7 @@ TEST_F(LoggerTest, logDebug2Level) { * Test delivery of log messages when the log level is set to DEBUG1. This test sets the log level to DEBUG1 * and then verifies that only logs of DEBUG or above are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug1Level) { +TEST_F(LoggerTest, test_logDebug1Level) { setLevelExpectations(Level::DEBUG1); exerciseLevels(); } @@ -415,7 +419,7 @@ TEST_F(LoggerTest, logDebug1Level) { * Test delivery of log messages when the log level is set to DEBUG1. This test sets the log level to DEBUG0 * and then verifies that only logs of DEBUG or above are passed through to the emit() method. */ -TEST_F(LoggerTest, logDebug0Level) { +TEST_F(LoggerTest, test_logDebug0Level) { setLevelExpectations(Level::DEBUG0); exerciseLevels(); } @@ -424,7 +428,7 @@ TEST_F(LoggerTest, logDebug0Level) { * Test delivery of log messages when the log level is set to INFO. This test sets the log level to INFO * and then verifies that only logs of INFO or above are passed through to the emit() method. */ -TEST_F(LoggerTest, logInfoLevel) { +TEST_F(LoggerTest, test_logInfoLevel) { setLevelExpectations(Level::INFO); exerciseLevels(); } @@ -433,7 +437,7 @@ TEST_F(LoggerTest, logInfoLevel) { * Test delivery of log messages when the log level is set to WARN. This test sets the log level to WARN * and then verifies that only logs of WARN or above are passed through to the emit() method. */ -TEST_F(LoggerTest, logWarnLevel) { +TEST_F(LoggerTest, test_logWarnLevel) { setLevelExpectations(Level::WARN); exerciseLevels(); } @@ -442,7 +446,7 @@ TEST_F(LoggerTest, logWarnLevel) { * Test delivery of log messages when the log level is set to ERROR. This test sets the log level to ERROR * and then verifies that only logs of ERROR or above are passed through to the emit() method. */ -TEST_F(LoggerTest, logErrorLevel) { +TEST_F(LoggerTest, test_logErrorLevel) { setLevelExpectations(Level::ERROR); exerciseLevels(); } @@ -451,7 +455,7 @@ TEST_F(LoggerTest, logErrorLevel) { * Test delivery of log messages when the log level is set to CRITICAL. This test sets the log level to CRITICAL * and then verifies that only CRITICAL logs are passed through to the emit() method. */ -TEST_F(LoggerTest, logCriticalLevel) { +TEST_F(LoggerTest, test_logCriticalLevel) { setLevelExpectations(Level::CRITICAL); exerciseLevels(); } @@ -460,17 +464,48 @@ TEST_F(LoggerTest, logCriticalLevel) { * Test delivery of log messages when the log level is set to NONE. This test sets the log level to NONE * and then verifies that no logs are passed through to the emit() method. */ -TEST_F(LoggerTest, logNoneLevel) { +TEST_F(LoggerTest, test_logNoneLevel) { setLevelExpectations(Level::NONE); exerciseLevels(); } +/** + * Test to ensure that logger usage with possible nullptr inputs is robust. As some functionality is templated, + * we must test both char* and const char* variants, for LogEntry construction, and the .d() and .m() functionality. + */ +TEST_F(LoggerTest, test_nullInputs) { + ACSDK_GET_LOGGER_FUNCTION().setLevel(Level::INFO); + + EXPECT_CALL(*(g_log.get()), emit(Level::INFO, _, _, _)).Times(13); + + // The good case. + ACSDK_INFO(LX("testEntryName").d("key", "value")); + + // Test the constructors. + ACSDK_INFO(LX(TEST_MESSAGE_CONST_NULL_CHAR_PTR).m("testEventNameConstNullPtr")); + ACSDK_INFO(LX(TEST_MESSAGE_NULL_CHAR_PTR).m("testEventNameNullPtr")); + + // Test the .d() bad variants, both params. + ACSDK_INFO(LX("testEntryName").d("key", TEST_MESSAGE_CONST_NULL_CHAR_PTR)); + ACSDK_INFO(LX("testEntryName").d("key", TEST_MESSAGE_NULL_CHAR_PTR)); + ACSDK_INFO(LX("testEntryName").d(TEST_MESSAGE_CONST_NULL_CHAR_PTR, "value")); + ACSDK_INFO(LX("testEntryName").d(TEST_MESSAGE_NULL_CHAR_PTR, "value")); + ACSDK_INFO(LX("testEntryName").d(TEST_MESSAGE_CONST_NULL_CHAR_PTR, TEST_MESSAGE_CONST_NULL_CHAR_PTR)); + ACSDK_INFO(LX("testEntryName").d(TEST_MESSAGE_CONST_NULL_CHAR_PTR, TEST_MESSAGE_NULL_CHAR_PTR)); + ACSDK_INFO(LX("testEntryName").d(TEST_MESSAGE_NULL_CHAR_PTR, TEST_MESSAGE_CONST_NULL_CHAR_PTR)); + ACSDK_INFO(LX("testEntryName").d(TEST_MESSAGE_NULL_CHAR_PTR, TEST_MESSAGE_NULL_CHAR_PTR)); + + // Test the .m() variants. + ACSDK_INFO(LX("testEntryName").m(TEST_MESSAGE_CONST_NULL_CHAR_PTR)); + ACSDK_INFO(LX("testEntryName").m(TEST_MESSAGE_NULL_CHAR_PTR)); +} + /** * Test delivery of appropriate time values from the logging system. This test captures the current time * both before and after an invocation of ACDK_LOG_INFO and verifies that the time value passed to the * emit() method is between (inclusive) the before and after times. */ -TEST_F(LoggerTest, verifyTime) { +TEST_F(LoggerTest, test_verifyTime) { ACSDK_GET_LOGGER_FUNCTION().setLevel(Level::INFO); EXPECT_CALL(*(g_log.get()), emit(Level::INFO, _, _, _)).Times(1); @@ -485,7 +520,7 @@ TEST_F(LoggerTest, verifyTime) { * Test delivery of appropriate thread moniker values from the logging system. This test invokes ACSDK_INFO from * two threads and verifies that the thread moniker values passed to the emit() method are in fact different. */ -TEST_F(LoggerTest, verifyThreadMoniker) { +TEST_F(LoggerTest, test_verifyThreadMoniker) { ACSDK_GET_LOGGER_FUNCTION().setLevel(Level::INFO); EXPECT_CALL(*(g_log.get()), emit(Level::INFO, _, _, _)).Times(2); ACSDK_INFO(LX("testing threadMoniker (1 of 2)")); @@ -502,7 +537,7 @@ TEST_F(LoggerTest, verifyThreadMoniker) { * constructor. Expect that the source parameter passed to the LogEntry constructor is included in the text * passed to the emit() method. */ -TEST_F(LoggerTest, verifySource) { +TEST_F(LoggerTest, test_verifySource) { ACSDK_GET_LOGGER_FUNCTION().setLevel(Level::INFO); EXPECT_CALL(*(g_log.get()), emit(Level::INFO, _, _, _)).Times(1); ACSDK_INFO(LX("random_event")); @@ -514,7 +549,7 @@ TEST_F(LoggerTest, verifySource) { * constructor. Expect that the event parameter passed to the LogEntry constructor is included in the text * passed to the emit() method. */ -TEST_F(LoggerTest, verifyEvent) { +TEST_F(LoggerTest, test_verifyEvent) { ACSDK_GET_LOGGER_FUNCTION().setLevel(Level::INFO); EXPECT_CALL(*(g_log.get()), emit(Level::INFO, _, _, _)).Times(1); std::string event(TEST_EVENT_STRING); @@ -527,7 +562,7 @@ TEST_F(LoggerTest, verifyEvent) { * Expects that that metadata is constructed properly (including the escaping of reserved characters) and that * both the key and escaped value are included in the text passed to the emit() method. */ -TEST_F(LoggerTest, verifyMetadata) { +TEST_F(LoggerTest, test_verifyMetadata) { ACSDK_GET_LOGGER_FUNCTION().setLevel(Level::INFO); EXPECT_CALL(*(g_log.get()), emit(Level::INFO, _, _, _)).Times(1); ACSDK_INFO(LX("testing metadata") @@ -542,7 +577,7 @@ TEST_F(LoggerTest, verifyMetadata) { * Test passing a message parameter to the logging system. Invokes ACSDK_INFO with a message parameter. * Expects that the message is included in text passed to the emit() method. */ -TEST_F(LoggerTest, verifyMessage) { +TEST_F(LoggerTest, test_verifyMessage) { ACSDK_GET_LOGGER_FUNCTION().setLevel(Level::INFO); EXPECT_CALL(*(g_log.get()), emit(Level::INFO, _, _, _)).Times(1); std::string message(TEST_MESSAGE_STRING); @@ -553,7 +588,7 @@ TEST_F(LoggerTest, verifyMessage) { /** * Test passing sensitive data to the logging system. It should only be emitted in DEBUG builds. */ -TEST_F(LoggerTest, testSensitiveDataSuppressed) { +TEST_F(LoggerTest, test_sensitiveDataSuppressed) { ACSDK_GET_LOGGER_FUNCTION().setLevel(Level::INFO); EXPECT_CALL(*(g_log.get()), emit(Level::INFO, _, _, _)).Times(1); ACSDK_INFO(LX("testing metadata").sensitive(METADATA_KEY, UNESCAPED_METADATA_VALUE)); @@ -570,7 +605,7 @@ TEST_F(LoggerTest, testSensitiveDataSuppressed) { * callback of the MockModuleLogger is triggered. Also make sure any changes to sink's logLevel is ignored * after the MockModuleLogger's logLevel has been set. */ -TEST_F(LoggerTest, testModuleLoggerObserver) { +TEST_F(LoggerTest, test_moduleLoggerObserver) { MockModuleLogger mockModuleLogger; getLoggerTestLogger()->setLevel(Level::WARN); ASSERT_EQ(mockModuleLogger.getLogLevel(), Level::WARN); @@ -583,7 +618,7 @@ TEST_F(LoggerTest, testModuleLoggerObserver) { /** * Test observer mechanism with multiple observers. Expects all observers to be notified of the logLevel change. */ -TEST_F(LoggerTest, testMultipleModuleLoggerObservers) { +TEST_F(LoggerTest, test_multipleModuleLoggerObservers) { MockModuleLogger mockModuleLogger1; MockModuleLogger mockModuleLogger2; MockModuleLogger mockModuleLogger3; @@ -608,7 +643,7 @@ TEST_F(LoggerTest, testMultipleModuleLoggerObservers) { * Test changing of sink logger using the LoggerSinkManager. Expect the sink in * ModuleLoggers will be changed. */ -TEST_F(LoggerTest, testChangeSinkLogger) { +TEST_F(LoggerTest, test_changeSinkLogger) { std::shared_ptr sink1 = MockLogger::create(); std::shared_ptr sink1Logger = sink1; diff --git a/AVSCommon/Utils/test/MIMEParserTest.cpp b/AVSCommon/Utils/test/MIMEParserTest.cpp new file mode 100644 index 0000000000..5fe51b2eb0 --- /dev/null +++ b/AVSCommon/Utils/test/MIMEParserTest.cpp @@ -0,0 +1,679 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "AVSCommon/Utils/Common/Common.h" +#include "AVSCommon/Utils/HTTP/HttpResponseCode.h" +#include "AVSCommon/Utils/HTTP2/HTTP2MimeRequestEncoder.h" +#include "AVSCommon/Utils/HTTP2/HTTP2MimeResponseDecoder.h" +#include "AVSCommon/Utils/HTTP2/MockHTTP2MimeRequestEncodeSource.h" +#include "AVSCommon/Utils/HTTP2/MockHTTP2MimeResponseDecodeSink.h" +#include "AVSCommon/Utils/Logger/LoggerUtils.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace test { + +using namespace testing; +using namespace avsCommon::utils::http; +using namespace avsCommon::utils::http2; +using namespace avsCommon::utils; +using namespace avsCommon::utils::logger; + +/// Separator for keys and values in mime part headers. +static const std::string SEPARATOR = ": "; + +/// Guideline sizes for test payloads and headers +static const int SMALL{100}; +static const int MEDIUM{200}; +static const int LARGE{500}; +static const int XLARGE{5000}; +static const int HEADER_PART_SIZE{10}; + +/// Boundary test constants +/// Response header prefix used to set boundary for decoder +static const std::string BOUNDARY_HEADER_PREFIX = "content-type:mixed/multipart;boundary="; +/// A test boundary string, copied from a real interaction with AVS. +static const std::string MIME_TEST_BOUNDARY_STRING = "84109348-943b-4446-85e6-e73eda9fac43"; +/// The newline characters that MIME parsers expect. +static const std::string MIME_NEWLINE = "\r\n"; +/// The double dashes which may occur before and after a boundary string. +static const std::string MIME_BOUNDARY_DASHES = "--"; +/// The test boundary string with the preceding dashes. +static const std::string BOUNDARY = MIME_BOUNDARY_DASHES + MIME_TEST_BOUNDARY_STRING; +/// A complete boundary, including the CRLF prefix +static const std::string BOUNDARY_LINE = MIME_NEWLINE + BOUNDARY; +/// Header line without prefix or suffix CRLF. +static const std::string HEADER_LINE = "Content-Type: application/json"; +/// JSON payload. +static const std::string TEST_MESSAGE = + "{\"directive\":{\"header\":{\"namespace\":\"SpeechRecognizer\",\"name\":" + "\"StopCapture\",\"messageId\":\"4e5612af-e05c-4611-8910-1e23f47ffb41\"}," + "\"payload\":{}}}"; + +// The following *_LINES definitions are raw mime text for various test parts. Each one assumes that +// it will be prefixed by a boundary and a CRLF. These get concatenated by constructTestMimeString() +// which provides an initiating boundary and CRLF, and which also inserts a CRLF between each part +// that is added. Leaving out the terminal CRLFs here allows constructTestMimeString() to append a +// pair of dashes to the boundary terminating the last part. Those final dashes are the standard +// syntax for the end of a sequence of mime parts. + +/// Normal section with header, test message and terminating boundary +/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. +static const std::string NORMAL_LINES = HEADER_LINE + MIME_NEWLINE + MIME_NEWLINE + TEST_MESSAGE + BOUNDARY_LINE; +/// Normal section preceded by a duplicate boundary (one CRLF between boundaries) +/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. +static const std::string DUPLICATE_BOUNDARY_LINES = BOUNDARY + MIME_NEWLINE + NORMAL_LINES; +/// Normal section preceded by a duplicate boundary and CRLF (two CRLFs between boundaries) +/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. +static const std::string CRLF_DUPLICATE_BOUNDARY_LINES = BOUNDARY_LINE + MIME_NEWLINE + NORMAL_LINES; +/// Normal section preceded by triplicate boundaries (one CRLF between boundaries) +/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. +static const std::string TRIPLICATE_BOUNDARY_LINES = BOUNDARY + MIME_NEWLINE + BOUNDARY + MIME_NEWLINE + NORMAL_LINES; +/// Normal section preceded by triplicate boundaries with trailing CRLF (two CRLFs between boundaries) +/// @note assumes previous terminating boundary and CRLF in the mime stream that this is appended to. +static const std::string CRLF_TRIPLICATE_BOUNDARY_LINES = + BOUNDARY_LINE + MIME_NEWLINE + BOUNDARY_LINE + MIME_NEWLINE + NORMAL_LINES; + +class MIMEParserTest : public ::testing::Test { +public: + /// boundary + const std::string boundary{"wooohooo"}; + /// payloads + const std::string payload1{"The quick brown fox jumped over the lazy dog"}; + const std::string payload2{ + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + "tempor incididunt ut labore et dolore magna aliqua.\n Ut enim ad minim " + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat.\n Duis aute irure dolor in reprehenderit in " + "voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n " + "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " + "officia deserunt mollit anim id est laborum."}; + const std::string payload3{ + "Enim diam vulputate ut pharetra sit amet aliquam id. Viverra accumsan " + "in nisl nisi scelerisque eu. Ipsum nunc aliquet bibendum enim facilisis " + "gravida neque convallis a. Ullamcorper dignissim cras tincidunt " + "lobortis. Mi proin sed libero enim sed faucibus turpis in."}; + + // Header Keys + const std::string key1{"content-type"}; + const std::string key2{"content-type"}; + const std::string key3{"xyz-abc"}; + const std::string key4{"holy-cow"}; + const std::string key5{"x-amzn-id"}; + + // header Values + const std::string value1{"plain/text"}; + const std::string value2{"application/xml"}; + const std::string value3{"123243124"}; + const std::string value4{"tellmehow"}; + const std::string value5{"eg1782ge71g172ge1"}; + +/// headers +#define BUILD_HEADER(number) key##number + SEPARATOR + value##number + const std::string header1{BUILD_HEADER(1)}; + const std::string header2{BUILD_HEADER(2)}; + const std::string header3{BUILD_HEADER(3)}; + const std::string header4{BUILD_HEADER(4)}; + const std::string header5{BUILD_HEADER(5)}; +#undef BUILD_HEADER + + /// Expected output from encoding the above + const std::string encodedPayload = + "\r\n--wooohooo" + "\r\ncontent-type: application/xml" + "\r\nxyz-abc: 123243124" + "\r\nholy-cow: tellmehow" + "\r\n" + "\r\nThe quick brown fox jumped over the lazy dog" + "\r\n--wooohooo" + "\r\ncontent-type: plain/text" + "\r\nx-amzn-id: eg1782ge71g172ge1" + "\r\n" + "\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " + "ut labore et dolore magna aliqua.\n Ut enim ad minim veniam, quis nostrud exercitation ullamco " + "laboris nisi ut aliquip ex ea commodo consequat.\n Duis aute irure dolor in reprehenderit in " + "voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n Excepteur sint occaecat cupidatat " + "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + "\r\n--wooohooo" + "\r\ncontent-type: plain/text" + "\r\n" + "\r\nEnim diam vulputate ut pharetra sit amet aliquam id. Viverra accumsan in nisl nisi " + "scelerisque eu. Ipsum nunc aliquet bibendum enim facilisis gravida neque convallis a. " + "Ullamcorper dignissim cras tincidunt lobortis. Mi proin sed libero enim sed faucibus turpis in." + "\r\n" + "--wooohooo--\r\n"; + + /// Expected size + const size_t encodedSize = encodedPayload.size(); + /// Header sets for each payload + const std::vector headers1 = {header2, header3, header4}; + const std::vector headers2 = {header1, header5}; + const std::vector headers3 = {header1}; + /// Expected header values. + const std::vector> expectedHeaders = { + {{key2, value2}, {key3, value3}, {key4, value4}}, + {{key1, value1}, {key5, value5}}, + {{key1, value1}}}; + /// Expected data (payloads). + const std::vector expectedData = {payload1, payload2, payload3}; +}; + +/** + * Test the basic encoding use case with a 3 part MIME request + * Test for correct encoded size, header presence and validity + * of Return status for every call as well as bytes written. + */ +TEST_F(MIMEParserTest, test_encodingSanity) { + /// We choose an arbitrary buffer size + const int bufferSize{25}; + + std::vector data; + data.push_back(payload1); + data.push_back(payload2); + data.push_back(payload3); + + std::vector> headerSets; + headerSets.push_back(headers1); + headerSets.push_back(headers2); + headerSets.push_back(headers3); + + auto source = std::make_shared(data, headerSets); + HTTP2MimeRequestEncoder encoder{boundary, source}; + + /// characters array to store the output, size chosen to be more than + /// the size calculated above + char buf[encodedPayload.size() * 2]; + size_t index{0}; + size_t lastSize{bufferSize}; + HTTP2SendDataResult result{0}; + while (result.status == HTTP2SendStatus::CONTINUE) { + result = encoder.onSendData(buf + index, bufferSize); + index += result.size; + // size returned should be bufferSize followed by {0,buffer_size}(last chunk) + if (lastSize < result.size) { + FAIL(); + } else { + lastSize = result.size; + } + } + ASSERT_EQ(HTTP2SendStatus::COMPLETE, result.status); + ASSERT_TRUE(strncmp(encodedPayload.c_str(), buf, encodedSize) == 0); + ASSERT_EQ(index, encodedSize); +} + +TEST_F(MIMEParserTest, test_decodingSanity) { + /// We choose an arbitrary buffer size + const int bufferSize{25}; + /// We need to pass a header with boundary info + const std::string boundaryHeader{BOUNDARY_HEADER_PREFIX + boundary}; + auto sink = std::make_shared(); + HTTP2MimeResponseDecoder decoder{sink}; + size_t index{0}; + HTTP2ReceiveDataStatus status{HTTP2ReceiveDataStatus::SUCCESS}; + bool resp = decoder.onReceiveHeaderLine(boundaryHeader); + ASSERT_TRUE(resp); + decoder.onReceiveResponseCode(HTTPResponseCode::SUCCESS_OK); + while (status == HTTP2ReceiveDataStatus::SUCCESS && index < encodedSize) { + auto sizeToSend = (index + bufferSize) > encodedSize ? encodedSize - index : bufferSize; + status = decoder.onReceiveData(encodedPayload.c_str() + index, sizeToSend); + index += sizeToSend; + } + ASSERT_EQ(HTTP2ReceiveDataStatus::SUCCESS, status); + /** + * Test individual MIME parts and headers + * note: header order may have changed + */ + ASSERT_TRUE(3 == sink->m_data.size()); + ASSERT_TRUE(3 == sink->m_headers.size()); + ASSERT_TRUE(3 == sink->m_headers.at(0).size()); + ASSERT_EQ(sink->m_headers, expectedHeaders); + ASSERT_EQ(sink->m_data, expectedData); +} + +void runCodecTest( + std::shared_ptr source, + std::shared_ptr sink, + const int bufferSize) { + char buf[XLARGE]; + size_t pauseCount{0}; + + // setup encoder + std::string boundary = createRandomAlphabetString(10); + HTTP2MimeRequestEncoder encoder{boundary, source}; + + // setup decoder + HTTP2MimeResponseDecoder decoder{sink}; + size_t index{0}; + HTTP2SendDataResult result{0}; + while (result.status == HTTP2SendStatus::CONTINUE || result.status == HTTP2SendStatus::PAUSE) { + result = encoder.onSendData(buf + index, bufferSize); + if (result.status == HTTP2SendStatus::PAUSE) { + pauseCount++; + } else { + std::string chunk(buf, index, result.size); + index += result.size; + } + } + /** + * Since encoding output gives partial chunks in case of PAUSE if some bytes have already been encoded, + * the PAUSE count encountered may not match. However, since we add a PAUSE in the very beginning, that one + * is guaranteed to be received + */ + if (source->m_pauseCount > 0) { + ASSERT_TRUE(pauseCount > 0); + } + auto finalSize = index; + index = 0; + pauseCount = 0; + HTTP2ReceiveDataStatus status{HTTP2ReceiveDataStatus::SUCCESS}; + const std::string boundaryHeader{BOUNDARY_HEADER_PREFIX + boundary}; + bool resp = decoder.onReceiveHeaderLine(boundaryHeader); + ASSERT_TRUE(resp); + decoder.onReceiveResponseCode( + static_cast::type>(HTTPResponseCode::SUCCESS_OK)); + while ((status == HTTP2ReceiveDataStatus::SUCCESS || status == HTTP2ReceiveDataStatus::PAUSE) && + index < finalSize) { + auto sizeToSend = (index + bufferSize) > finalSize ? finalSize - index : bufferSize; + status = decoder.onReceiveData(buf + index, sizeToSend); + if (status == HTTP2ReceiveDataStatus::PAUSE) { + pauseCount++; + } else { + index += sizeToSend; + } + } + ASSERT_TRUE(sink->hasSameContentAs(source)); + ASSERT_EQ(pauseCount, sink->m_pauseCount); +} + +TEST_F(MIMEParserTest, test_singlePayloadSinglePass) { + // Sufficiently large buffer to accommodate payload + const int bufferSize{LARGE}; + + // setup source + std::vector data; + std::vector> headerSets; + // small single payload + data.push_back(createRandomAlphabetString(SMALL)); + std::vector headers; + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headerSets.push_back(headers); + auto source = std::make_shared(data, headerSets); + + // setup sink + auto sink = std::make_shared(); + + // run actual test + runCodecTest(source, sink, bufferSize); +} + +TEST_F(MIMEParserTest, test_singlePayloadMultiplePasses) { + // Medium sized payload and buffer + const int bufferSize{SMALL}; + // setup source + std::vector data; + std::vector> headerSets; + // single large payload that cannot fit into buffer ensuring >1 pass + data.push_back(createRandomAlphabetString(LARGE)); + std::vector headers; + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headerSets.push_back(headers); + auto source = std::make_shared(data, headerSets); + + // setup sink + auto sink = std::make_shared(); + + // run actual test + runCodecTest(source, sink, bufferSize); +} + +TEST_F(MIMEParserTest, test_multiplePayloadsSinglePass) { + const int bufferSize{LARGE}; + std::vector data; + std::vector> headerSets; + // 3 small payloads + for (int i = 0; i < 3; ++i) { + data.push_back(createRandomAlphabetString(SMALL)); + std::vector headers; + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headerSets.push_back(headers); + } + auto source = std::make_shared(data, headerSets); + + // setup sink + auto sink = std::make_shared(); + + // run actual test + runCodecTest(source, sink, bufferSize); +} + +TEST_F(MIMEParserTest, test_multiplePayloadsMultiplePasses) { + const int bufferSize{SMALL}; + std::vector data; + std::vector> headerSets; + // 3 medium payloads + for (int i = 0; i < 3; ++i) { + data.push_back(createRandomAlphabetString(MEDIUM)); + std::vector headers; + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headerSets.push_back(headers); + } + auto source = std::make_shared(data, headerSets); + + // setup sink + auto sink = std::make_shared(); + + // run actual test + runCodecTest(source, sink, bufferSize); +} + +/** + * Test feeding mime text including duplicate boundaries that we want to just skip over. + */ +TEST_F(MIMEParserTest, test_duplicateBoundaries) { + /// We choose an arbitrary buffer size + const int bufferSize{25}; + /// We need to pass a header with boundary info + const std::string boundaryHeader{BOUNDARY_HEADER_PREFIX + MIME_TEST_BOUNDARY_STRING}; + auto sink = std::make_shared(); + HTTP2MimeResponseDecoder decoder{sink}; + std::string testPayload = BOUNDARY_LINE; + for (int i = 0; i < 2; ++i) { + testPayload += MIME_NEWLINE + HEADER_LINE + MIME_NEWLINE + MIME_NEWLINE + createRandomAlphabetString(SMALL) + + BOUNDARY_LINE; + } + testPayload += MIME_NEWLINE + NORMAL_LINES; + testPayload += + MIME_NEWLINE + HEADER_LINE + MIME_NEWLINE + MIME_NEWLINE + createRandomAlphabetString(SMALL) + BOUNDARY_LINE; + testPayload += MIME_NEWLINE + DUPLICATE_BOUNDARY_LINES; + testPayload += + MIME_NEWLINE + HEADER_LINE + MIME_NEWLINE + MIME_NEWLINE + createRandomAlphabetString(SMALL) + BOUNDARY_LINE; + testPayload += MIME_NEWLINE + CRLF_DUPLICATE_BOUNDARY_LINES; + testPayload += + MIME_NEWLINE + HEADER_LINE + MIME_NEWLINE + MIME_NEWLINE + createRandomAlphabetString(SMALL) + BOUNDARY_LINE; + testPayload += MIME_NEWLINE + TRIPLICATE_BOUNDARY_LINES; + testPayload += + MIME_NEWLINE + HEADER_LINE + MIME_NEWLINE + MIME_NEWLINE + createRandomAlphabetString(SMALL) + BOUNDARY_LINE; + testPayload += MIME_NEWLINE + CRLF_TRIPLICATE_BOUNDARY_LINES; + testPayload += + MIME_NEWLINE + HEADER_LINE + MIME_NEWLINE + MIME_NEWLINE + createRandomAlphabetString(SMALL) + BOUNDARY_LINE; + testPayload += MIME_BOUNDARY_DASHES; + + size_t index{0}; + HTTP2ReceiveDataStatus status{HTTP2ReceiveDataStatus::SUCCESS}; + bool resp = decoder.onReceiveHeaderLine(boundaryHeader); + decoder.onReceiveResponseCode( + static_cast::type>(HTTPResponseCode::SUCCESS_OK)); + while (status == HTTP2ReceiveDataStatus::SUCCESS && index < testPayload.size()) { + auto sizeToSend = (index + bufferSize) > testPayload.size() ? testPayload.size() - index : bufferSize; + status = decoder.onReceiveData(testPayload.c_str() + index, sizeToSend); + index += sizeToSend; + } + ASSERT_EQ(HTTP2ReceiveDataStatus::SUCCESS, status); + ASSERT_TRUE(resp); + /// verify only the 12 messages added above are written to sink(no empty payloads from newlines) + ASSERT_TRUE(12 == sink->m_data.size()); + // verify TEST_MESSAGE was correctly decoded + for (int j = 2; j < 12; j += 2) { + ASSERT_EQ(TEST_MESSAGE, sink->m_data.at(j)); + } + // negative test + for (int j = 1; j < 12; j += 2) { + ASSERT_NE(TEST_MESSAGE, sink->m_data.at(j)); + } +} + +TEST_F(MIMEParserTest, test_aBORT) { + // setup source + std::vector data; + std::vector> headerSets; + // small single payload + data.push_back(createRandomAlphabetString(SMALL)); + std::vector headers; + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headerSets.push_back(headers); + auto source = std::make_shared(data, headerSets); + source->m_abort = true; + + // setup sink + auto sink = std::make_shared(); + sink->m_abort = true; + + // setup encoder + std::string boundary = createRandomAlphabetString(10); + HTTP2MimeRequestEncoder encoder{boundary, source}; + char buf[LARGE]; + // Ensure repeated calls return ABORT + ASSERT_TRUE(encoder.onSendData(buf, SMALL).status == HTTP2SendStatus::ABORT); + source->m_abort = false; + ASSERT_TRUE(encoder.onSendData(buf, SMALL).status == HTTP2SendStatus::ABORT); + + // setup decoder + HTTP2MimeResponseDecoder decoder{sink}; + decoder.onReceiveResponseCode( + static_cast::type>(HTTPResponseCode::SUCCESS_OK)); + // Ensure repeated calls return ABORT + ASSERT_EQ(decoder.onReceiveData(encodedPayload.c_str(), SMALL), HTTP2ReceiveDataStatus::ABORT); + sink->m_abort = false; + ASSERT_EQ(decoder.onReceiveData(encodedPayload.c_str(), SMALL), HTTP2ReceiveDataStatus::ABORT); +} + +TEST_F(MIMEParserTest, test_pAUSE) { + const int bufferSize{SMALL}; + std::vector data; + std::vector> headerSets; + // 3 medium payloads + for (int i = 0; i < 3; ++i) { + data.push_back(createRandomAlphabetString(MEDIUM)); + std::vector headers; + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headerSets.push_back(headers); + } + // setup slow source + auto source = std::make_shared(data, headerSets); + source->m_slowSource = true; + + // setup slow sink + auto sink = std::make_shared(); + sink->m_slowSink = true; + + // run actual test + runCodecTest(source, sink, bufferSize); + ASSERT_TRUE(sink->m_pauseCount > 0); + ASSERT_TRUE(source->m_pauseCount > 0); +} + +/** + * We test for cases when the amount of data to be encoded/decoded from chunk varies a lot + * between calls + */ +TEST_F(MIMEParserTest, test_variableChunkSizes) { + std::vector data; + std::vector> headerSets; + // 3 medium payloads + for (int i = 0; i < 3; ++i) { + data.push_back(createRandomAlphabetString(MEDIUM)); + std::vector headers; + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headers.push_back( + {createRandomAlphabetString(HEADER_PART_SIZE) + SEPARATOR + createRandomAlphabetString(HEADER_PART_SIZE)}); + headerSets.push_back(headers); + } + // setup source + auto source = std::make_shared(data, headerSets); + // setup sink + auto sink = std::make_shared(); + // sufficiently large buffer + char buf[XLARGE]; + + // setup encoder + std::string boundary = createRandomAlphabetString(10); + HTTP2MimeRequestEncoder encoder{boundary, source}; + + // setup decoder + HTTP2MimeResponseDecoder decoder{sink}; + + size_t index{0}; + size_t pauseCount{0}; + + HTTP2SendDataResult result{0}; + while (result.status == HTTP2SendStatus::CONTINUE || result.status == HTTP2SendStatus::PAUSE) { + // We will randomize this size from SMALL/2 to SMALL + int bufferSize{generateRandomNumber(SMALL / 2, SMALL)}; + result = encoder.onSendData(buf + index, bufferSize); + if (result.status == HTTP2SendStatus::PAUSE) { + pauseCount++; + } else { + std::string chunk(buf, index, result.size); + index += result.size; + } + } + /** + * Since encoding output gives partial chunks in case of PAUSE if some bytes have already been encoded, + * the PAUSE count encountered may not match. However, since we add a PAUSE in the very beginning, that one + * is guaranteed to be received + */ + if (source->m_pauseCount > 0) { + ASSERT_TRUE(pauseCount > 0); + } + + auto finalSize = index; + index = 0; + pauseCount = 0; + HTTP2ReceiveDataStatus status{HTTP2ReceiveDataStatus::SUCCESS}; + const std::string boundaryHeader{BOUNDARY_HEADER_PREFIX + boundary}; + bool resp = decoder.onReceiveHeaderLine(boundaryHeader); + decoder.onReceiveResponseCode( + static_cast::type>(HTTPResponseCode::SUCCESS_OK)); + ASSERT_TRUE(resp); + while ((status == HTTP2ReceiveDataStatus::SUCCESS || status == HTTP2ReceiveDataStatus::PAUSE) && + index < finalSize) { + // We will randomize this size from SMALL/2 to SMALL + int bufferSize{generateRandomNumber(SMALL / 2, SMALL)}; + auto sizeToSend = (index + bufferSize) > finalSize ? finalSize - index : bufferSize; + status = decoder.onReceiveData(buf + index, sizeToSend); + if (status == HTTP2ReceiveDataStatus::PAUSE) { + pauseCount++; + } else { + index += sizeToSend; + } + } + ASSERT_TRUE(sink->hasSameContentAs(source)); + ASSERT_EQ(pauseCount, sink->m_pauseCount); +} + +/** + * Test one of many prefix use cases. + * + * @param readablePrefix The prefix string to log if there is a failure (some prefixes are unreadable). + * @param prefix The prefix to add to the body to decode. + * @param firstChunkSize The size of the first chunk to pass to the decoder. + * @param numberChunks The number of chunks in which to send the body. + * @param expectSuccess Whether or not to expect the parse to succeed. + */ +static void testPrefixCase( + const std::string& readablePrefix, + const std::string& prefix, + size_t firstChunkSize, + int numberChunks, + bool expectSuccess) { + auto sink = std::make_shared(); + HTTP2MimeResponseDecoder decoder{sink}; + + ASSERT_TRUE(decoder.onReceiveHeaderLine(BOUNDARY_HEADER_PREFIX + MIME_TEST_BOUNDARY_STRING)); + ASSERT_TRUE(decoder.onReceiveResponseCode( + static_cast::type>(HTTPResponseCode::SUCCESS_OK))); + + std::string data = prefix + BOUNDARY + MIME_NEWLINE + NORMAL_LINES + MIME_BOUNDARY_DASHES; + auto writeQuantum = data.length(); + HTTP2ReceiveDataStatus status{HTTP2ReceiveDataStatus::SUCCESS}; + size_t index{0}; + int chunksSent = 0; + + if (numberChunks != 1 && firstChunkSize != 0 && firstChunkSize < writeQuantum) { + status = decoder.onReceiveData(data.c_str(), firstChunkSize); + index = firstChunkSize; + writeQuantum -= firstChunkSize; + chunksSent++; + } + + if (numberChunks > 1) { + writeQuantum /= (numberChunks - chunksSent); + } + + while (status == HTTP2ReceiveDataStatus::SUCCESS && index < data.length() && chunksSent < (numberChunks + 1)) { + auto size = (index + writeQuantum) > data.length() ? data.length() - index : writeQuantum; + status = decoder.onReceiveData(data.c_str() + index, size); + index += size; + chunksSent++; + } + + std::string message = "prefix=" + readablePrefix + ", firstChunkSize=" + std::to_string(firstChunkSize) + + ", numberChunks=" + std::to_string(numberChunks); + + if (expectSuccess) { + ASSERT_EQ(HTTP2ReceiveDataStatus::SUCCESS, status) << message; + ASSERT_EQ(sink->m_data.size(), static_cast(1)) << message; + ASSERT_EQ(TEST_MESSAGE, sink->m_data[0]) << message; + } else { + ASSERT_NE(HTTP2ReceiveDataStatus::SUCCESS, status) << message; + } +} + +TEST_F(MIMEParserTest, test_prefixCases) { + // Value used to drive testes of first chunk sizes 0 (i.e. none), 1, 2, and 3. + static const int MAX_FIRST_CHUNK_SIZE = 3; + + // Value used to drive sending data in 1, 2, 3, 4 or 5 parts. + static const int MAX_CHUNKS = 5; + + for (int firstChunkSize = 0; firstChunkSize <= MAX_FIRST_CHUNK_SIZE; firstChunkSize++) { + for (int numberChunks = 1; numberChunks <= MAX_CHUNKS; numberChunks++) { + testPrefixCase("empty", "", firstChunkSize, numberChunks, true); + testPrefixCase("\\r\\n", MIME_NEWLINE, firstChunkSize, numberChunks, true); + testPrefixCase("\\r", "\r", firstChunkSize, numberChunks, false); + testPrefixCase("\\n", "\n", firstChunkSize, numberChunks, false); + testPrefixCase("x", "x", firstChunkSize, numberChunks, false); + } + } +} + +} // namespace test +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/RequiresShutdownTest.cpp b/AVSCommon/Utils/test/RequiresShutdownTest.cpp index 01ef70ab42..294d314d56 100644 --- a/AVSCommon/Utils/test/RequiresShutdownTest.cpp +++ b/AVSCommon/Utils/test/RequiresShutdownTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -58,7 +58,7 @@ void Object::doShutdown() { * verifies that we don't crash, but functional verification currently requires a manual examination of the console * output from this test. */ -TEST(RequiresShutdownTest, allTestCases) { +TEST(RequiresShutdownTest, test_allTestCases) { // shared_ptr loop that implements and calls proper shutdown functions auto loopCallGoodShutdownMemberA = std::make_shared("loopCallGoodShutdownMemberA"); auto loopCallGoodShutdownMemberB = std::make_shared("loopCallGoodShutdownMemberB"); diff --git a/AVSCommon/Utils/test/SafeTimeAccessTest.cpp b/AVSCommon/Utils/test/SafeTimeAccessTest.cpp index 6e56dc436d..2304bb665c 100644 --- a/AVSCommon/Utils/test/SafeTimeAccessTest.cpp +++ b/AVSCommon/Utils/test/SafeTimeAccessTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -30,13 +30,13 @@ namespace test { const time_t LARGE_TIME_VALUE = (0x1 << 30) - 1; /// Test to verify that getGmtime returns failure if a nullptr is passed for the result variable. -TEST(SafeCTimeAccessTest, getGmtimeNullReturnValue) { +TEST(SafeCTimeAccessTest, test_getGmtimeNullReturnValue) { auto safeCTimeAccess = SafeCTimeAccess::instance(); ASSERT_FALSE(safeCTimeAccess->getGmtime(0, nullptr)); } /// Test to verify that getLocaltime returns failure if a nullptr is passed for the result variable. -TEST(SafeCTimeAccessTest, getLocaltimeNullReturnValue) { +TEST(SafeCTimeAccessTest, test_getLocaltimeNullReturnValue) { auto safeCTimeAccess = SafeCTimeAccess::instance(); ASSERT_FALSE(safeCTimeAccess->getLocaltime(0, nullptr)); } @@ -86,7 +86,7 @@ static void testLocaltimeHelper(const std::tm& expected, const time_t& t) { } /// Test to verify that getGmtime returns the correct calendar date for the Unix epoch. -TEST(SafeCTimeAccessTest, getGmtimeAtTheEpoch) { +TEST(SafeCTimeAccessTest, test_getGmtimeAtTheEpoch) { std::tm epoch; epoch.tm_sec = 0; epoch.tm_min = 0; @@ -101,7 +101,7 @@ TEST(SafeCTimeAccessTest, getGmtimeAtTheEpoch) { } /// Test to verify that getGmtime returns the same calendar date as std::gmtime. -TEST(SafeCTimeAccessTest, getGmtime) { +TEST(SafeCTimeAccessTest, test_getGmtime) { for (time_t t = 0; t < LARGE_TIME_VALUE; t = 2 * (t + 1)) { auto gmtimeResult = std::gmtime(&t); ASSERT_NE(nullptr, gmtimeResult); @@ -110,7 +110,7 @@ TEST(SafeCTimeAccessTest, getGmtime) { } /// Test to verify that getLocaltime returns the same calendar date as std::localtime. -TEST(SafeCTimeAccessTest, getLocaltime) { +TEST(SafeCTimeAccessTest, test_getLocaltime) { for (time_t t = 0; t < LARGE_TIME_VALUE; t = 2 * (t + 1)) { auto localtimeResult = std::localtime(&t); ASSERT_NE(nullptr, localtimeResult); @@ -201,13 +201,13 @@ static void checkSafeCTimeFunction(const TestType& type) { } /// Test to make sure that multithreaded access SafeCTimeAccess::getGmtime is safe. -TEST(SafeCTimeAccessTest, DISABLED_gmTimeMultithreadedAccess) { +TEST(SafeCTimeAccessTest, DISABLED_test_gmTimeMultithreadedAccess) { // TODO: ACSDK-1208 investigate Pi failure checkSafeCTimeFunction(TestType::GMTIME); } /// Test to make sure that multithreaded access SafeCTimeAccess::getLocaltimetime is safe. -TEST(SafeCTimeAccessTest, DISABLED_localtimeMultithreadedAccess) { +TEST(SafeCTimeAccessTest, DISABLED_test_localtimeMultithreadedAccess) { // TODO: ACSDK-1208 investigate Pi failure checkSafeCTimeFunction(TestType::LOCALTIME); } diff --git a/AVSCommon/Utils/test/SharedDataStreamTest.cpp b/AVSCommon/Utils/test/SharedDataStreamTest.cpp index 50e0bdfe8b..20fb6e6b09 100644 --- a/AVSCommon/Utils/test/SharedDataStreamTest.cpp +++ b/AVSCommon/Utils/test/SharedDataStreamTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -336,7 +336,7 @@ class SharedDataStreamTest : public ::testing::Test { }; /// This tests @c SharedDataStream::calculateCreateSize() and @c SharedDataStream::create(). -TEST_F(SharedDataStreamTest, sdsCalculateCreateSize) { +TEST_F(SharedDataStreamTest, test_sdsCalculateCreateSize) { static const size_t SDK_MAXREADERS_REQUIRED = 2; static const size_t SDK_WORDSIZE_REQUIRED = sizeof(uint16_t); size_t maxReaders, wordCount, wordSize; @@ -412,7 +412,7 @@ TEST_F(SharedDataStreamTest, sdsCalculateCreateSize) { } /// This tests @c SharedDataStream::open(). -TEST_F(SharedDataStreamTest, sdsOpen) { +TEST_F(SharedDataStreamTest, test_sdsOpen) { static const size_t WORDSIZE = 2; static const size_t WORDCOUNT = 10; static const size_t MAXREADERS = 2; @@ -454,7 +454,7 @@ TEST_F(SharedDataStreamTest, sdsOpen) { } /// This tests @c SharedDataStream::createWriter(). -TEST_F(SharedDataStreamTest, createWriter) { +TEST_F(SharedDataStreamTest, test_createWriter) { static const size_t WORDSIZE = 1; static const size_t WORDCOUNT = 1; static const size_t MAXREADERS = 1; @@ -506,7 +506,7 @@ TEST_F(SharedDataStreamTest, createWriter) { } /// This tests @c SharedDataStream::createReader(). -TEST_F(SharedDataStreamTest, createReader) { +TEST_F(SharedDataStreamTest, test_createReader) { static const size_t WORDSIZE = 1; static const size_t WORDCOUNT = 1; static const size_t MAXREADERS = 2; @@ -571,7 +571,7 @@ TEST_F(SharedDataStreamTest, createReader) { } /// This tests @c SharedDataStream::Reader::read(). -TEST_F(SharedDataStreamTest, readerRead) { +TEST_F(SharedDataStreamTest, test_readerRead) { static const size_t WORDSIZE = 2; static const size_t WORDCOUNT = 2; static const size_t MAXREADERS = 2; @@ -647,7 +647,7 @@ TEST_F(SharedDataStreamTest, readerRead) { } /// This tests @c SharedDataStream::Reader::seek(). -TEST_F(SharedDataStreamTest, readerSeek) { +TEST_F(SharedDataStreamTest, test_readerSeek) { static const size_t WORDSIZE = 2; static const size_t WORDCOUNT = 10; static const size_t MAXREADERS = 2; @@ -794,7 +794,7 @@ TEST_F(SharedDataStreamTest, readerSeek) { } /// This tests @c SharedDataStream::Reader::tell(). -TEST_F(SharedDataStreamTest, readerTell) { +TEST_F(SharedDataStreamTest, test_readerTell) { static const size_t WORDSIZE = 2; static const size_t WORDCOUNT = 10; static const size_t MAXREADERS = 2; @@ -857,7 +857,7 @@ TEST_F(SharedDataStreamTest, readerTell) { } /// This tests @c SharedDataStream::Reader::close(). -TEST_F(SharedDataStreamTest, readerClose) { +TEST_F(SharedDataStreamTest, test_readerClose) { static const size_t WORDSIZE = 2; static const size_t WORDCOUNT = 10; static const size_t MAXREADERS = 2; @@ -894,7 +894,7 @@ TEST_F(SharedDataStreamTest, readerClose) { } /// This tests @c SharedDataStream::Reader::getId(). -TEST_F(SharedDataStreamTest, readerGetId) { +TEST_F(SharedDataStreamTest, test_readerGetId) { static const size_t WORDSIZE = 1; static const size_t WORDCOUNT = 1; static const size_t MAXREADERS = 10; @@ -925,7 +925,7 @@ TEST_F(SharedDataStreamTest, readerGetId) { } /// This tests @c SharedDataStream::Reader::getWordSize(). -TEST_F(SharedDataStreamTest, readerGetWordSize) { +TEST_F(SharedDataStreamTest, test_readerGetWordSize) { static const size_t MINWORDSIZE = 1; static const size_t MAXWORDSIZE = 8; static const size_t WORDCOUNT = 1; @@ -943,7 +943,7 @@ TEST_F(SharedDataStreamTest, readerGetWordSize) { } /// This tests @c SharedDataStream::Writer::write(). -TEST_F(SharedDataStreamTest, writerWrite) { +TEST_F(SharedDataStreamTest, test_writerWrite) { static const size_t WORDSIZE = 2; static const size_t WORDCOUNT = 2; static const size_t MAXREADERS = 1; @@ -1014,7 +1014,7 @@ TEST_F(SharedDataStreamTest, writerWrite) { } /// This tests @c SharedDataStream::Writer::tell(). -TEST_F(SharedDataStreamTest, writerTell) { +TEST_F(SharedDataStreamTest, test_writerTell) { static const size_t WORDSIZE = 1; static const size_t WORDCOUNT = 1; static const size_t MAXREADERS = 1; @@ -1043,7 +1043,7 @@ TEST_F(SharedDataStreamTest, writerTell) { } /// This tests @c SharedDataStream::Writer::close(). -TEST_F(SharedDataStreamTest, writerClose) { +TEST_F(SharedDataStreamTest, test_writerClose) { static const size_t WORDSIZE = 1; static const size_t WORDCOUNT = 1; static const size_t MAXREADERS = 1; @@ -1066,7 +1066,7 @@ TEST_F(SharedDataStreamTest, writerClose) { } /// This tests @c SharedDataStream::Writer::getWordSize(). -TEST_F(SharedDataStreamTest, writerGetWordSize) { +TEST_F(SharedDataStreamTest, test_writerGetWordSize) { static const size_t MINWORDSIZE = 1; static const size_t MAXWORDSIZE = 8; static const size_t WORDCOUNT = 1; @@ -1084,7 +1084,7 @@ TEST_F(SharedDataStreamTest, writerGetWordSize) { } /// This tests a nonblockable, slow @c Writer streaming concurrently to two fast @c Readers (one of each type). -TEST_F(SharedDataStreamTest, concurrencyNonblockableWriterDualReader) { +TEST_F(SharedDataStreamTest, testTimer_concurrencyNonblockableWriterDualReader) { static const size_t WORDSIZE = 2; static const size_t WRITE_FREQUENCY_HZ = 1000; static const size_t READ_FREQUENCY_HZ = 0; @@ -1119,7 +1119,7 @@ TEST_F(SharedDataStreamTest, concurrencyNonblockableWriterDualReader) { } /// This tests an all-or-nothing, fast @c Writer streaming concurrently to a slow non-blocking @c Reader. -TEST_F(SharedDataStreamTest, concurrencyAllOrNothingWriterNonblockingReader) { +TEST_F(SharedDataStreamTest, test_concurrencyAllOrNothingWriterNonblockingReader) { static const size_t WORDSIZE = 1; static const size_t WRITE_FREQUENCY_HZ = 320000; static const size_t READ_FREQUENCY_HZ = 160000; @@ -1148,7 +1148,7 @@ TEST_F(SharedDataStreamTest, concurrencyAllOrNothingWriterNonblockingReader) { } /// This tests a @c Writer from one SDS streaming to a @c Reader from a different SDS, usig a shared @c Buffer. -TEST_F(SharedDataStreamTest, concurrencyMultipleSds) { +TEST_F(SharedDataStreamTest, test_concurrencyMultipleSds) { static const size_t WORDSIZE = 1; static const size_t WRITE_FREQUENCY_HZ = 320000; static const size_t READ_FREQUENCY_HZ = 160000; @@ -1180,7 +1180,7 @@ TEST_F(SharedDataStreamTest, concurrencyMultipleSds) { } /// This tests that a @c Reader closes if a @c Writer is attached and closed before writing anything -TEST_F(SharedDataStreamTest, writerClosedBeforeWriting) { +TEST_F(SharedDataStreamTest, test_writerClosedBeforeWriting) { static const size_t WORDSIZE = 2; static const size_t WORDCOUNT = 2; static const size_t MAXREADERS = 2; @@ -1218,7 +1218,7 @@ TEST_F(SharedDataStreamTest, writerClosedBeforeWriting) { } /// This tests that a @c Reader closes if a @c Writer is attached and closed before the @c Reader is first attached -TEST_F(SharedDataStreamTest, writerClosedBeforeAttachingReader) { +TEST_F(SharedDataStreamTest, test_writerClosedBeforeAttachingReader) { static const size_t WORDSIZE = 2; static const size_t WORDCOUNT = 2; static const size_t MAXREADERS = 2; diff --git a/AVSCommon/Utils/test/StopwatchTest.cpp b/AVSCommon/Utils/test/StopwatchTest.cpp new file mode 100644 index 0000000000..fb6ea7fde0 --- /dev/null +++ b/AVSCommon/Utils/test/StopwatchTest.cpp @@ -0,0 +1,195 @@ +/* + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +/// @file StopwatchTest.cpp + +#include +#include + +#include + +#include "AVSCommon/Utils/Timing/Stopwatch.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace timing { +namespace test { + +static const std::chrono::milliseconds TESTABLE_TIME_INCREMENT{100}; + +/// Test harness for Stopwatch class. +class StopwatchTest : public ::testing::Test { +public: + bool checkElapsed(int expectedIncrement); + + Stopwatch m_stopwatch; +}; + +bool StopwatchTest::checkElapsed(int expectedIncrement) { + auto elapsed = m_stopwatch.getElapsed(); + if (elapsed < TESTABLE_TIME_INCREMENT * (expectedIncrement - 1)) { + return false; + } + if (elapsed > TESTABLE_TIME_INCREMENT * (expectedIncrement + 1)) { + return false; + } + return true; +} + +/** + * Test good sequencing of method calls. + */ +TEST_F(StopwatchTest, test_goodSequencing) { + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(m_stopwatch.resume()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(m_stopwatch.resume()); + m_stopwatch.stop(); + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(m_stopwatch.resume()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(m_stopwatch.resume()); + m_stopwatch.stop(); +} + +/** + * Test bad sequencing of method calls. + */ +TEST_F(StopwatchTest, test_badSequencing) { + // Must be reset to start(). + + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_FALSE(m_stopwatch.start()); + + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_FALSE(m_stopwatch.start()); + + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(m_stopwatch.resume()); + ASSERT_FALSE(m_stopwatch.start()); + + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(m_stopwatch.resume()); + m_stopwatch.stop(); + ASSERT_FALSE(m_stopwatch.start()); + + // Must be started to pause(). + + m_stopwatch.reset(); + ASSERT_FALSE(m_stopwatch.pause()); + + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_FALSE(m_stopwatch.pause()); + + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(m_stopwatch.resume()); + ASSERT_TRUE(m_stopwatch.pause()); + + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + m_stopwatch.stop(); + ASSERT_FALSE(m_stopwatch.pause()); + + // Must be paused to resume(). + + m_stopwatch.reset(); + ASSERT_FALSE(m_stopwatch.resume()); + + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_FALSE(m_stopwatch.resume()); + + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(m_stopwatch.resume()); + ASSERT_FALSE(m_stopwatch.resume()); + + m_stopwatch.reset(); + ASSERT_TRUE(m_stopwatch.start()); + m_stopwatch.stop(); + ASSERT_FALSE(m_stopwatch.resume()); +} + +/** + * Test report of elapsed time. This test is timing sensitive. + */ +TEST_F(StopwatchTest, testSlow_elapsed) { + // Expect progression after start(). + ASSERT_TRUE(m_stopwatch.start()); + std::this_thread::sleep_for(TESTABLE_TIME_INCREMENT * 2); + ASSERT_TRUE(checkElapsed(2)); + + // Expect NO progression during pause(). + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(checkElapsed(2)); + std::this_thread::sleep_for(TESTABLE_TIME_INCREMENT * 2); + ASSERT_TRUE(checkElapsed(2)); + + // Expect progression after resume(). + ASSERT_TRUE(m_stopwatch.resume()); + ASSERT_TRUE(checkElapsed(2)); + std::this_thread::sleep_for(TESTABLE_TIME_INCREMENT * 2); + ASSERT_TRUE(checkElapsed(4)); + + // Expect NO progression during pause(). + ASSERT_TRUE(m_stopwatch.pause()); + ASSERT_TRUE(checkElapsed(4)); + std::this_thread::sleep_for(TESTABLE_TIME_INCREMENT * 2); + ASSERT_TRUE(checkElapsed(4)); + + // Expect progression after resume(). + ASSERT_TRUE(m_stopwatch.resume()); + ASSERT_TRUE(checkElapsed(4)); + std::this_thread::sleep_for(TESTABLE_TIME_INCREMENT * 2); + ASSERT_TRUE(checkElapsed(6)); + + // Expect NO progression after stop(). + m_stopwatch.stop(); + ASSERT_TRUE(checkElapsed(6)); + std::this_thread::sleep_for(TESTABLE_TIME_INCREMENT * 2); + ASSERT_TRUE(checkElapsed(6)); + + // Expect NO progression after reset(). + m_stopwatch.reset(); + ASSERT_TRUE(checkElapsed(0)); + std::this_thread::sleep_for(TESTABLE_TIME_INCREMENT * 2); + ASSERT_TRUE(checkElapsed(0)); + + // Expect start() works after reset().. + ASSERT_TRUE(m_stopwatch.start()); + std::this_thread::sleep_for(TESTABLE_TIME_INCREMENT * 2); + ASSERT_TRUE(checkElapsed(2)); +} + +} // namespace test +} // namespace timing +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/StreamFunctionsTest.cpp b/AVSCommon/Utils/test/StreamFunctionsTest.cpp index 441d2998a2..bf9b8bca78 100644 --- a/AVSCommon/Utils/test/StreamFunctionsTest.cpp +++ b/AVSCommon/Utils/test/StreamFunctionsTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -46,14 +46,14 @@ class StreamFunctionsTest : public ::testing::Test { /** * Verify that audio bytes passed in are returned exactly the same */ -TEST_F(StreamFunctionsTest, StreamFromData) { +TEST_F(StreamFunctionsTest, test_streamFromData) { ASSERT_TRUE(streamAndDataAreEqual(*m_stream, TEST_DATA, sizeof(TEST_DATA))); } /** * Verify that non-char data streams work correctly */ -TEST_F(StreamFunctionsTest, DataContainsUnprintableChars) { +TEST_F(StreamFunctionsTest, test_dataContainsUnprintableChars) { const std::vector> testData = { {5, 0, 3, 6}, // NULLS in data {0, 0, 6, 6}, // NULLS at beginning @@ -74,7 +74,7 @@ TEST_F(StreamFunctionsTest, DataContainsUnprintableChars) { /** * Verify that empty datasets work */ -TEST_F(StreamFunctionsTest, EmptyVector) { +TEST_F(StreamFunctionsTest, test_emptyVector) { const unsigned char empty[] = {}; auto stream = stream::streamFromData(empty, sizeof(empty)); ASSERT_TRUE(streamAndDataAreEqual(*stream, empty, sizeof(empty))); @@ -83,7 +83,7 @@ TEST_F(StreamFunctionsTest, EmptyVector) { /** * Verify that multiple streams created from the same source can be operated on independently */ -TEST_F(StreamFunctionsTest, MultipleStreams) { +TEST_F(StreamFunctionsTest, test_multipleStreams) { // get two streams to the same data auto stream1 = stream::streamFromData(TEST_DATA, sizeof(TEST_DATA)); auto stream2 = stream::streamFromData(TEST_DATA, sizeof(TEST_DATA)); @@ -101,7 +101,7 @@ TEST_F(StreamFunctionsTest, MultipleStreams) { /** * Verify that seekg works going forward */ -TEST_F(StreamFunctionsTest, seekgBasicForward) { +TEST_F(StreamFunctionsTest, test_seekgBasicForward) { const std::streampos step = 2; m_stream->seekg(step); ASSERT_TRUE(streamAndDataAreEqual(*m_stream, TEST_DATA + step, sizeof(TEST_DATA) - step)); @@ -110,7 +110,7 @@ TEST_F(StreamFunctionsTest, seekgBasicForward) { /** * Verify that seekg can reset */ -TEST_F(StreamFunctionsTest, seekgBasicReset) { +TEST_F(StreamFunctionsTest, test_seekgBasicReset) { // get 4 chars from char from one stream char c; m_stream->get(c); @@ -127,14 +127,14 @@ TEST_F(StreamFunctionsTest, seekgBasicReset) { /** * Verify that tellg works on creation */ -TEST_F(StreamFunctionsTest, tellgBasic) { +TEST_F(StreamFunctionsTest, test_tellgBasic) { ASSERT_EQ(0, m_stream->tellg()); } /** * Verify that the stream will have a bad tellg result when seeking past end */ -TEST_F(StreamFunctionsTest, tellgPastEnd) { +TEST_F(StreamFunctionsTest, test_tellgPastEnd) { m_stream->seekg(sizeof(TEST_DATA) + 1); ASSERT_EQ(-1, m_stream->tellg()); } @@ -142,7 +142,7 @@ TEST_F(StreamFunctionsTest, tellgPastEnd) { /** * Verify that the stream will have a bad tellg result when seeking before beginning */ -TEST_F(StreamFunctionsTest, tellgBeforeBeginning) { +TEST_F(StreamFunctionsTest, test_tellgBeforeBeginning) { m_stream->seekg(-1); ASSERT_EQ(-1, m_stream->tellg()); } @@ -150,7 +150,7 @@ TEST_F(StreamFunctionsTest, tellgBeforeBeginning) { /** * Verify that tellg is set correctly after seeking */ -TEST_F(StreamFunctionsTest, tellgAfterSeeking) { +TEST_F(StreamFunctionsTest, test_tellgAfterSeeking) { const std::streampos step = 2; m_stream->seekg(step); ASSERT_EQ(step, m_stream->tellg()); @@ -160,7 +160,7 @@ TEST_F(StreamFunctionsTest, tellgAfterSeeking) { /** * Verify that tellg is set correctly after reading from stream */ -TEST_F(StreamFunctionsTest, tellgAfterReading) { +TEST_F(StreamFunctionsTest, test_tellgAfterReading) { // get 4 chars from char from one stream const int numberToRead = 4; diff --git a/AVSCommon/Utils/test/StreambufTest.cpp b/AVSCommon/Utils/test/StreambufTest.cpp index 4fc8352146..eb67144e20 100644 --- a/AVSCommon/Utils/test/StreambufTest.cpp +++ b/AVSCommon/Utils/test/StreambufTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -36,14 +36,14 @@ class StreambufTest : public ::testing::Test { /** * Verify that the Streambuf is created correctly */ -TEST_F(StreambufTest, Creation) { +TEST_F(StreambufTest, test_creation) { ASSERT_EQ(testData[0], m_sb.sgetc()); } /** * Verify that the seekoff can be called based from the beginning */ -TEST_F(StreambufTest, seekoffBeginning) { +TEST_F(StreambufTest, test_seekoffBeginning) { const std::vector positions = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for (const auto& pos : positions) { ASSERT_EQ(pos, m_sb.seekoff(pos, std::ios_base::beg)); @@ -54,7 +54,7 @@ TEST_F(StreambufTest, seekoffBeginning) { /** * Verify that the seekoff can be called based from the current positon */ -TEST_F(StreambufTest, seekoffCurrentForward) { +TEST_F(StreambufTest, test_seekoffCurrentForward) { const std::streampos pos = 3; ASSERT_EQ(pos, m_sb.seekoff(pos, std::ios_base::cur)); ASSERT_EQ(testData[pos], m_sb.sgetc()); @@ -66,7 +66,7 @@ TEST_F(StreambufTest, seekoffCurrentForward) { /** * Verify that you can seek all the way until the end correctly */ -TEST_F(StreambufTest, seekoffFromBeginningUntilEnd) { +TEST_F(StreambufTest, test_seekoffFromBeginningUntilEnd) { const std::streampos step = 1; m_sb.seekoff(0, std::ios_base::beg); for (size_t i = 0; i < testData.size() - 1; ++i) { @@ -80,7 +80,7 @@ TEST_F(StreambufTest, seekoffFromBeginningUntilEnd) { /** * Verify that you can seek all the way from the end to the beginning */ -TEST_F(StreambufTest, seekoffFromEndUntilBeginning) { +TEST_F(StreambufTest, test_seekoffFromEndUntilBeginning) { const std::streampos step = -1; m_sb.seekoff(-1, std::ios_base::end); for (size_t i = 0; i < testData.size() - 1; ++i) { @@ -94,7 +94,7 @@ TEST_F(StreambufTest, seekoffFromEndUntilBeginning) { /** * Verify that you can seek backward from the end */ -TEST_F(StreambufTest, seekoffCurrentBackward) { +TEST_F(StreambufTest, test_seekoffCurrentBackward) { auto end = m_sb.seekoff(-1, std::ios_base::end); const std::streampos pos = 3; @@ -108,21 +108,21 @@ TEST_F(StreambufTest, seekoffCurrentBackward) { /** * Verify that a seek to before the stream results in an error */ -TEST_F(StreambufTest, seekoffBeforeStart) { +TEST_F(StreambufTest, test_seekoffBeforeStart) { ASSERT_EQ(-1, m_sb.seekoff(-1, std::ios_base::beg)); } /** * Verify that a seek to after the stream results in an error */ -TEST_F(StreambufTest, seekoffPastEnd) { +TEST_F(StreambufTest, test_seekoffPastEnd) { ASSERT_EQ(-1, m_sb.seekoff(1, std::ios_base::end)); } /** * Verify that a basic seekpos works */ -TEST_F(StreambufTest, seekpos) { +TEST_F(StreambufTest, test_seekpos) { const std::streampos pos = 3; ASSERT_EQ(pos, m_sb.seekpos(pos)); ASSERT_EQ(testData[pos], m_sb.sgetc()); @@ -131,21 +131,21 @@ TEST_F(StreambufTest, seekpos) { /** * Verify that a basic seekpos before the beginning results in an error */ -TEST_F(StreambufTest, seekposBeforeStart) { +TEST_F(StreambufTest, test_seekposBeforeStart) { ASSERT_EQ(-1, m_sb.seekpos(-1)); } /** * Verify that a basic seekpos after the end results in an error */ -TEST_F(StreambufTest, seekposAfterEnd) { +TEST_F(StreambufTest, test_seekposAfterEnd) { ASSERT_EQ(-1, m_sb.seekpos(sizeof(testData) + 1)); } /** * Verify that a seekpos to the end is correct */ -TEST_F(StreambufTest, seekposToEnd) { +TEST_F(StreambufTest, test_seekposToEnd) { auto end = m_sb.seekoff(0, std::ios_base::end); ASSERT_EQ(end, m_sb.seekpos(end)); } diff --git a/AVSCommon/Utils/test/StringUtilsTest.cpp b/AVSCommon/Utils/test/StringUtilsTest.cpp index 12cf1eb84c..a315cd7134 100644 --- a/AVSCommon/Utils/test/StringUtilsTest.cpp +++ b/AVSCommon/Utils/test/StringUtilsTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ using namespace alexaClientSDK::avsCommon::utils::string; /** * Verify that converting an empty string to an integer fails. */ -TEST(StringUtilsTest, testEmptyStringFails) { +TEST(StringUtilsTest, test_emptyStringFails) { int result = 0; ASSERT_FALSE(stringToInt("", &result)); } @@ -35,7 +35,7 @@ TEST(StringUtilsTest, testEmptyStringFails) { /** * Verify that converting a simple decimal integer string to integer succeeds. */ -TEST(StringUtilsTest, testSimpleDecimalInteger) { +TEST(StringUtilsTest, test_simpleDecimalInteger) { int result = 0; ASSERT_TRUE(stringToInt("123", &result)); ASSERT_EQ(123, result); @@ -44,7 +44,7 @@ TEST(StringUtilsTest, testSimpleDecimalInteger) { /** * Verify that converting a negative decimal integer string to integer succeeds. */ -TEST(StringUtilsTest, testNegativeInt) { +TEST(StringUtilsTest, test_negativeInt) { int result = 0; ASSERT_TRUE(stringToInt("-987654", &result)); ASSERT_EQ(-987654, result); @@ -53,7 +53,7 @@ TEST(StringUtilsTest, testNegativeInt) { /** * Verify that converting a decimal integer string with leading whitespace to integer succeeds. */ -TEST(StringUtilsTest, testInitialWhitespaceSucceeds) { +TEST(StringUtilsTest, test_initialWhitespaceSucceeds) { int result = 0; ASSERT_TRUE(stringToInt("\t 10101", &result)); ASSERT_EQ(10101, result); @@ -62,7 +62,7 @@ TEST(StringUtilsTest, testInitialWhitespaceSucceeds) { /** * Verify that converting a decimal integer string with trailing whitespace to integer succeeds. */ -TEST(StringUtilsTest, testTrailingWhitespaceSucceeds) { +TEST(StringUtilsTest, test_trailingWhitespaceSucceeds) { int result = 0; ASSERT_TRUE(stringToInt("982389\t ", &result)); ASSERT_EQ(982389, result); @@ -71,7 +71,7 @@ TEST(StringUtilsTest, testTrailingWhitespaceSucceeds) { /** * Verify that converting a decimal integer string with leading and trailing whitespace to integer succeeds. */ -TEST(StringUtilsTest, testLeadingAndTrailingWhitespaceSucceeds) { +TEST(StringUtilsTest, test_leadingAndTrailingWhitespaceSucceeds) { int result = 0; ASSERT_TRUE(stringToInt(" 982389 ", &result)); ASSERT_EQ(982389, result); @@ -80,7 +80,7 @@ TEST(StringUtilsTest, testLeadingAndTrailingWhitespaceSucceeds) { /** * Verify that converting a decimal integer with leading non-whitespace and non-decimal digit characters fails. */ -TEST(StringUtilsTest, testNonWhitespacePrefixFails) { +TEST(StringUtilsTest, test_nonWhitespacePrefixFails) { int result = 0; ASSERT_FALSE(stringToInt("a123", &result)); } @@ -88,7 +88,7 @@ TEST(StringUtilsTest, testNonWhitespacePrefixFails) { /** * Verify that converting a decimal integer with trailing non-whitespace and non-decimal digit characters fails. */ -TEST(StringUtilsTest, testNonWhitespaceSuffixFails) { +TEST(StringUtilsTest, test_nonWhitespaceSuffixFails) { int result = 0; ASSERT_FALSE(stringToInt("123a", &result)); } @@ -97,7 +97,7 @@ TEST(StringUtilsTest, testNonWhitespaceSuffixFails) { * Verify that converting a decimal integer with leading and trailing non-whitespace and non-decimal digit * characters fails. */ -TEST(StringUtilsTest, testNonWhitespacePrefixAndSuffixFails) { +TEST(StringUtilsTest, test_nonWhitespacePrefixAndSuffixFails) { int result = 0; ASSERT_FALSE(stringToInt("a123a", &result)); } @@ -105,7 +105,7 @@ TEST(StringUtilsTest, testNonWhitespacePrefixAndSuffixFails) { /** * Verify that converting a decimal integer with both leading whitespace and non-whitespace characters fails. */ -TEST(StringUtilsTest, testNonWhitespaceAndNonWhitespacePrefixFails) { +TEST(StringUtilsTest, test_nonWhitespaceAndNonWhitespacePrefixFails) { int result = 0; ASSERT_FALSE(stringToInt(" e123", &result)); } @@ -113,7 +113,7 @@ TEST(StringUtilsTest, testNonWhitespaceAndNonWhitespacePrefixFails) { /** * Verify that converting a decimal integer with both trailing whitespace and non-whitespace characters fails. */ -TEST(StringUtilsTest, testNonWhitespaceAndNonWhitespaceSuffixFails) { +TEST(StringUtilsTest, test_nonWhitespaceAndNonWhitespaceSuffixFails) { int result = 0; ASSERT_FALSE(stringToInt("123e ", &result)); } @@ -121,7 +121,7 @@ TEST(StringUtilsTest, testNonWhitespaceAndNonWhitespaceSuffixFails) { /** * Verify that converting a decimal integer with leading and trailing whitespace and non-whitespace characters fails. */ -TEST(StringUtilsTest, testNonWhitespaceAndNonWhitespacePrefixAndSuffixFails) { +TEST(StringUtilsTest, test_nonWhitespaceAndNonWhitespacePrefixAndSuffixFails) { int result = 0; ASSERT_FALSE(stringToInt(" e123e ", &result)); } @@ -129,7 +129,7 @@ TEST(StringUtilsTest, testNonWhitespaceAndNonWhitespacePrefixAndSuffixFails) { /** * Verify that converting "0" to integer succeeds. */ -TEST(StringUtilsTest, testZeroSucceeds) { +TEST(StringUtilsTest, test_zeroSucceeds) { int result = -1; ASSERT_TRUE(stringToInt("0", &result)); ASSERT_EQ(0, result); @@ -138,7 +138,7 @@ TEST(StringUtilsTest, testZeroSucceeds) { /** * Verify that converting a floating string to integer fails. */ -TEST(StringUtilsTest, testDecimalFloatFails) { +TEST(StringUtilsTest, test_decimalFloatFails) { int result = 0; ASSERT_FALSE(stringToInt("1.234", &result)); } @@ -146,7 +146,7 @@ TEST(StringUtilsTest, testDecimalFloatFails) { /** * Verify that converting an octal integer string si interpreted as decmal with a leading zero. */ -TEST(StringUtilsTest, testOctalInterpretedAsDecimal) { +TEST(StringUtilsTest, test_octalInterpretedAsDecimal) { int result = 0; ASSERT_TRUE(stringToInt("0567", &result)); ASSERT_EQ(567, result); @@ -155,7 +155,7 @@ TEST(StringUtilsTest, testOctalInterpretedAsDecimal) { /** * Verify that converting a hex integer string to integer fails. */ -TEST(StringUtilsTest, testHexIntFails) { +TEST(StringUtilsTest, test_hexIntFails) { int result = 0; ASSERT_FALSE(stringToInt("0x321", &result)); } @@ -163,7 +163,7 @@ TEST(StringUtilsTest, testHexIntFails) { /** * Verify that converting a too large integer string to int fails. */ -TEST(StringUtilsTest, testTooLargeIntFails) { +TEST(StringUtilsTest, test_tooLargeIntFails) { int result = 0; ASSERT_FALSE(stringToInt("987654321987654321987654321", &result)); } @@ -171,7 +171,7 @@ TEST(StringUtilsTest, testTooLargeIntFails) { /** * Verify that converting a too small integer string to int fails. */ -TEST(StringUtilsTest, testTooSmallIntFails) { +TEST(StringUtilsTest, test_tooSmallIntFails) { int result = 0; ASSERT_FALSE(stringToInt("-11111111111111111111111111", &result)); } @@ -179,7 +179,7 @@ TEST(StringUtilsTest, testTooSmallIntFails) { /** * Verify that converting a string with multiple numbers in it fails. */ -TEST(StringUtilsTest, testMultipleNumbers) { +TEST(StringUtilsTest, test_multipleNumbers) { int result = 0; ASSERT_FALSE(stringToInt("123 123", &result)); ASSERT_FALSE(stringToInt(" 123 123", &result)); @@ -191,28 +191,28 @@ TEST(StringUtilsTest, testMultipleNumbers) { /** * Verify that converting a empty string to lower case works. */ -TEST(StringUtilsTest, testToLowerEmptyString) { +TEST(StringUtilsTest, test_toLowerEmptyString) { ASSERT_EQ(stringToLowerCase(""), ""); } /** * Verify that converting a lower case string to lower case works. */ -TEST(StringUtilsTest, testToLowerCaseString) { +TEST(StringUtilsTest, test_toLowerCaseString) { ASSERT_EQ(stringToLowerCase("abc"), "abc"); } /** * Verify that converting a Upper case string to lower case works. */ -TEST(StringUtilsTest, testToUpperCaseString) { +TEST(StringUtilsTest, test_toUpperCaseString) { ASSERT_EQ(stringToLowerCase("ABC"), "abc"); } /** * Verify that converting a Camel case string to lower case works. */ -TEST(StringUtilsTest, testToCamelCaseString) { +TEST(StringUtilsTest, test_toCamelCaseString) { ASSERT_EQ(stringToLowerCase("AbCd"), "abcd"); } diff --git a/AVSCommon/Utils/test/TaskQueueTest.cpp b/AVSCommon/Utils/test/TaskQueueTest.cpp deleted file mode 100644 index 8b74733ac8..0000000000 --- a/AVSCommon/Utils/test/TaskQueueTest.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include - -#include "ExecutorTestUtils.h" -#include "AVSCommon/Utils/Threading/Executor.h" -#include "AVSCommon/Utils/Threading/TaskQueue.h" - -namespace alexaClientSDK { -namespace avsCommon { -namespace utils { -namespace threading { -namespace test { - -class TaskQueueTest : public ::testing::Test { -public: - /** - * Asserts that a call to pop on an empty queue is blocking, and will be awoken by a task being pushed onto - * the queue - */ - void testQueueBlocksWhenEmpty() { - // Have another thread blocked on the queue - auto future = std::async(std::launch::async, [=]() { - auto t = queue.pop(); - return t->operator()(); - }); - - // This is expected to timeout - auto failedStatus = future.wait_for(SHORT_TIMEOUT_MS); - - ASSERT_EQ(failedStatus, std::future_status::timeout); - - // Push a task to unblock the queue - auto pushFuture = queue.push(TASK, VALUE); - - // This is expected to succeed - auto successStatus = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(successStatus, std::future_status::ready); - - // Verify the pushed future behaved correctly - auto pushStatus = pushFuture.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(pushStatus, std::future_status::ready); - ASSERT_EQ(pushFuture.get(), VALUE); - } - - TaskQueue queue; -}; - -TEST_F(TaskQueueTest, pushStdFunctionAndVerifyPopReturnsIt) { - std::function function([]() {}); - auto future = queue.push(function); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); -} - -TEST_F(TaskQueueTest, pushStdBindAndVerifyPopReturnsIt) { - auto future = queue.push(std::bind(exampleFunctionParams, 0)); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); -} - -TEST_F(TaskQueueTest, pushLambdaAndVerifyPopReturnsIt) { - auto future = queue.push([]() {}); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); -} - -TEST_F(TaskQueueTest, pushFunctionPointerAndVerifyPopReturnsIt) { - auto future = queue.push(&exampleFunction); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); -} - -TEST_F(TaskQueueTest, pushFunctorAndVerifyPopReturnsIt) { - ExampleFunctor exampleFunctor; - auto future = queue.push(exampleFunctor); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); -} - -TEST_F(TaskQueueTest, pushFunctionWithPrimitiveReturnTypeNoArgsAndVerifyPopReturnsIt) { - int value = VALUE; - auto future = queue.push([=]() { return value; }); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); - ASSERT_EQ(future.get(), value); -} - -TEST_F(TaskQueueTest, pushFunctionWithObjectReturnTypeNoArgsAndVerifyPopReturnsIt) { - SimpleObject value(VALUE); - auto future = queue.push([=]() { return value; }); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); - ASSERT_EQ(future.get().getValue(), value.getValue()); -} - -TEST_F(TaskQueueTest, pushFunctionWithNoReturnTypePrimitiveArgsAndVerifyPopReturnsIt) { - int value = VALUE; - auto future = queue.push([](int number) {}, value); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); -} - -TEST_F(TaskQueueTest, pushFunctionWithNoReturnTypeObjectArgsAndVerifyPopReturnsIt) { - SimpleObject arg(0); - auto future = queue.push([](SimpleObject object) {}, arg); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); -} - -TEST_F(TaskQueueTest, pushFunctionWithPrimitiveReturnTypeObjectArgsAndVerifyPopReturnsIt) { - int value = VALUE; - SimpleObject arg(0); - auto future = queue.push([=](SimpleObject object) { return value; }, arg); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); - ASSERT_EQ(future.get(), value); -} - -TEST_F(TaskQueueTest, pushFunctionWithObjectReturnTypePrimitiveArgsAndVerifyPopReturnsIt) { - int arg = 0; - SimpleObject value(VALUE); - auto future = queue.push([=](int primitive) { return value; }, arg); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); - ASSERT_EQ(future.get().getValue(), value.getValue()); -} - -TEST_F(TaskQueueTest, pushFunctionWithPrimitiveReturnTypePrimitiveArgsAndVerifyPopReturnsIt) { - int arg = 0; - int value = VALUE; - auto future = queue.push([=](int number) { return value; }, arg); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); - ASSERT_EQ(future.get(), value); -} - -TEST_F(TaskQueueTest, pushFunctionWithObjectReturnTypeObjectArgsAndVerifyPopReturnsIt) { - SimpleObject value(VALUE); - SimpleObject arg(0); - auto future = queue.push([=](SimpleObject object) { return value; }, arg); - auto task = queue.pop(); - task->operator()(); - auto future_status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(future_status, std::future_status::ready); - ASSERT_EQ(future.get().getValue(), value.getValue()); -} - -TEST_F(TaskQueueTest, verifyFirstInFirstOutOrderIsMaintained) { - int argOne = 1; - int argTwo = 2; - int argThree = 3; - int argFour = 4; - - auto futureOne = queue.push(TASK, argOne); - auto futureTwo = queue.push(TASK, argTwo); - auto futureThree = queue.push(TASK, argThree); - auto futureFour = queue.push(TASK, argFour); - - auto taskOne = queue.pop(); - auto taskTwo = queue.pop(); - auto taskThree = queue.pop(); - auto taskFour = queue.pop(); - - taskOne->operator()(); - taskTwo->operator()(); - taskThree->operator()(); - taskFour->operator()(); - - auto futureStatusOne = futureOne.wait_for(SHORT_TIMEOUT_MS); - auto futureStatusTwo = futureTwo.wait_for(SHORT_TIMEOUT_MS); - auto futureStatusThree = futureThree.wait_for(SHORT_TIMEOUT_MS); - auto futureStatusFour = futureFour.wait_for(SHORT_TIMEOUT_MS); - - ASSERT_EQ(futureStatusOne, std::future_status::ready); - ASSERT_EQ(futureStatusTwo, std::future_status::ready); - ASSERT_EQ(futureStatusThree, std::future_status::ready); - ASSERT_EQ(futureStatusFour, std::future_status::ready); - - ASSERT_EQ(futureOne.get(), argOne); - ASSERT_EQ(futureTwo.get(), argTwo); - ASSERT_EQ(futureThree.get(), argThree); - ASSERT_EQ(futureFour.get(), argFour); -} - -TEST_F(TaskQueueTest, popBlocksOnInitiallyEmptyQueue) { - testQueueBlocksWhenEmpty(); -} - -TEST_F(TaskQueueTest, popBlocksOnEmptyQueueAfterAllTasksArePopped) { - // Put a task on the queue, and take it off to get back to empty - auto futureOne = queue.push(TASK, VALUE); - auto taskOne = queue.pop(); - taskOne->operator()(); - auto futureOneStatus = futureOne.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(futureOneStatus, std::future_status::ready); - ASSERT_EQ(futureOne.get(), VALUE); - - testQueueBlocksWhenEmpty(); -} - -TEST_F(TaskQueueTest, isShutdownReturnsFalseWhenRunning) { - ASSERT_EQ(queue.isShutdown(), false); -} - -TEST_F(TaskQueueTest, isShutdownReturnsTrueAfterShutdown) { - queue.shutdown(); - ASSERT_EQ(queue.isShutdown(), true); -} - -TEST_F(TaskQueueTest, shutdownUnblocksAnEmptyQueue) { - // Have another thread blocked on the queue - auto future = std::async(std::launch::async, [=]() { - auto t = queue.pop(); - if (t) t->operator()(); - }); - - // This is expected to timeout - auto failedStatus = future.wait_for(SHORT_TIMEOUT_MS); - - ASSERT_EQ(failedStatus, std::future_status::timeout); - - // Shutdown to unblock the queue - queue.shutdown(); - - // This is expected to succeed - auto successStatus = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(successStatus, std::future_status::ready); -} - -TEST_F(TaskQueueTest, pushFailsToEnqueueANewTaskOnAShutdownQueue) { - // No tasks should be enqueued - queue.shutdown(); - - auto future = queue.push(TASK, VALUE); - ASSERT_EQ(future.valid(), false); - - auto retrievedTask = queue.pop(); - - ASSERT_EQ(retrievedTask, nullptr); -} - -} // namespace test -} // namespace threading -} // namespace utils -} // namespace avsCommon -} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/TaskThreadTest.cpp b/AVSCommon/Utils/test/TaskThreadTest.cpp index 6baef96727..d00d6150c9 100644 --- a/AVSCommon/Utils/test/TaskThreadTest.cpp +++ b/AVSCommon/Utils/test/TaskThreadTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -15,8 +15,9 @@ #include +#include "AVSCommon/Utils/Logger/ThreadMoniker.h" #include "AVSCommon/Utils/Threading/TaskThread.h" -#include "ExecutorTestUtils.h" +#include "AVSCommon/Utils/WaitEvent.h" namespace alexaClientSDK { namespace avsCommon { @@ -24,84 +25,151 @@ namespace utils { namespace threading { namespace test { -class TaskThreadTest : public ::testing::Test { -public: - std::shared_ptr createTaskQueue() { - return std::make_shared(); +/// Timeout used while waiting for synchronization events. +/// We picked 2s to avoid failure in slower systems (e.g.: valgrind and emulators). +const std::chrono::milliseconds WAIT_TIMEOUT{100}; + +using namespace utils::test; +using namespace logger; + +/// Test that wait will return if no job has ever started. +TEST(TaskThreadTest, test_waitForNothing) { + TaskThread taskThread; +} + +/// Test that start will fail if function is empty. +TEST(TaskThreadTest, test_startFailsDueToEmptyFunction) { + TaskThread taskThread; + std::function emptyFunction; + EXPECT_FALSE(taskThread.start(emptyFunction)); +} + +/// Test that start will trigger the provided job and thread will exit once the job is done and return @c false. +TEST(TaskThreadTest, test_simpleJob) { + bool finished = false; + auto simpleJob = [&finished] { + finished = true; + return false; }; -}; -TEST_F(TaskThreadTest, theTaskThreadReadsFromAGivenEmptyQueue) { - auto queue = createTaskQueue(); - TaskThread taskThread{queue}; - taskThread.start(); + { + TaskThread taskThread; + EXPECT_TRUE(taskThread.start(simpleJob)); + } - auto future = queue->push(TASK, VALUE); + EXPECT_TRUE(finished); +} + +/// Test that start will trigger the provided job and it will execute the job multiple times until the job returns +/// @c false. +TEST(TaskThreadTest, test_sequenceJobs) { + int taskCounter = 0; + const int runUntil = 10; + auto jobSequence = [&taskCounter] { + taskCounter++; + return taskCounter < runUntil; + }; - auto status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(status, std::future_status::ready); - ASSERT_EQ(future.get(), VALUE); + { + TaskThread taskThread; + EXPECT_TRUE(taskThread.start(jobSequence)); + } - // shutting down the queue will lead to the thread shutting down - queue->shutdown(); + EXPECT_EQ(taskCounter, runUntil); } -TEST_F(TaskThreadTest, theTaskThreadReadsFromAGivenNonEmptyQueue) { - auto queue = createTaskQueue(); - auto future = queue->push(TASK, VALUE); +/// Test that start will replace the existing next function. +/// - First function increments the counter, while the second will decrement until it reaches 0. +TEST(TaskThreadTest, test_startNewJob) { + WaitEvent waitEvent; + int taskCounter = 0; + auto increment = [&taskCounter, &waitEvent] { + taskCounter++; + waitEvent.wakeUp(); + return true; + }; - TaskThread taskThread{queue}; - taskThread.start(); + auto decrement = [&taskCounter] { + taskCounter--; + return taskCounter > 0; + }; - auto status = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(status, std::future_status::ready); - ASSERT_EQ(future.get(), VALUE); + TaskThread taskThread; + EXPECT_TRUE(taskThread.start(increment)); - // shutting down the queue will lead to the thread shutting down - queue->shutdown(); + ASSERT_TRUE(waitEvent.wait(WAIT_TIMEOUT)); + EXPECT_TRUE(taskThread.start(decrement)); } -TEST_F(TaskThreadTest, theTaskThreadShutsDownWhenTheQueueIsDestroyed) { - // the TaskThread only has a weak pointer to the queue, once it goes out of scope the thread will shutdown - TaskThread taskThread{createTaskQueue()}; - taskThread.start(); +/// Test that start will fail if called multiple times while waiting for a job. +TEST(TaskThreadTest, test_startFailDueTooManyThreads) { + WaitEvent waitEnqueue, waitStart; + auto simpleJob = [&waitEnqueue, &waitStart] { + waitStart.wakeUp(); // Job has started. + waitEnqueue.wait(WAIT_TIMEOUT); // Wait till job should finish. + return false; + }; - // wait for the thread to shutdown - std::this_thread::sleep_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(taskThread.isShutdown(), true); -} + TaskThread taskThread; + EXPECT_TRUE(taskThread.start(simpleJob)); -TEST_F(TaskThreadTest, theTaskThreadShutsDownWhenTheQueueIsShutdown) { - auto queue = createTaskQueue(); - TaskThread taskThread{queue}; - taskThread.start(); + // Wait until first job has started. + waitStart.wait(WAIT_TIMEOUT); + EXPECT_TRUE(taskThread.start([] { return false; })); - queue->shutdown(); + // This should fail since the task thread is starting. + EXPECT_FALSE(taskThread.start([] { return false; })); - // wait for the thread to shutdown - std::this_thread::sleep_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(taskThread.isShutdown(), true); + waitEnqueue.wakeUp(); } -TEST_F(TaskThreadTest, theTaskThreadIsNotStartedUntilStartIsCalled) { - auto queue = createTaskQueue(); - auto future = queue->push(TASK, VALUE); - TaskThread taskThread{queue}; +/// Test that threads related to this task thread will always have the same moniker. +TEST(TaskThreadTest, test_moniker) { + WaitEvent waitGetMoniker, waitValidateMoniker; + std::string moniker; + auto getMoniker = [&moniker, &waitGetMoniker] { + moniker = ThreadMoniker::getThisThreadMoniker(); + waitGetMoniker.wakeUp(); + return false; + }; + + auto validateMoniker = [&moniker, &waitValidateMoniker] { + EXPECT_EQ(moniker, ThreadMoniker::getThisThreadMoniker()); + waitValidateMoniker.wakeUp(); + return false; + }; + + TaskThread taskThread; + EXPECT_TRUE(taskThread.start(getMoniker)); + waitGetMoniker.wait(WAIT_TIMEOUT); - // wait for long enough that the task would normall be run - auto failedStatus = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(failedStatus, std::future_status::timeout); + EXPECT_TRUE(taskThread.start(validateMoniker)); + waitValidateMoniker.wait(WAIT_TIMEOUT); +} - // start the thread - taskThread.start(); +/// Test that threads from different @c TaskThreads will have different monikers. +TEST(TaskThreadTest, test_monikerDifferentObjects) { + WaitEvent waitGetMoniker, waitValidateMoniker; + std::string moniker; + auto getMoniker = [&moniker, &waitGetMoniker] { + moniker = ThreadMoniker::getThisThreadMoniker(); + waitGetMoniker.wakeUp(); + return false; + }; + + auto validateMoniker = [&moniker, &waitValidateMoniker] { + EXPECT_NE(moniker, ThreadMoniker::getThisThreadMoniker()); + waitValidateMoniker.wakeUp(); + return false; + }; - // now it should succeed almost immediately - auto successStatus = future.wait_for(SHORT_TIMEOUT_MS); - ASSERT_EQ(successStatus, std::future_status::ready); - ASSERT_EQ(future.get(), VALUE); + TaskThread taskThread1; + EXPECT_TRUE(taskThread1.start(getMoniker)); + waitGetMoniker.wait(WAIT_TIMEOUT); - // shutting down the queue will lead to the thread shutting down - queue->shutdown(); + TaskThread taskThread2; + EXPECT_TRUE(taskThread2.start(validateMoniker)); + waitValidateMoniker.wait(WAIT_TIMEOUT); } } // namespace test diff --git a/AVSCommon/Utils/test/TimeUtilsTest.cpp b/AVSCommon/Utils/test/TimeUtilsTest.cpp index 46a6fb1b73..8fbd863fa1 100644 --- a/AVSCommon/Utils/test/TimeUtilsTest.cpp +++ b/AVSCommon/Utils/test/TimeUtilsTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ namespace utils { namespace timing { namespace test { -TEST(TimeTest, testStringConversion) { +TEST(TimeTest, test_stringConversion) { TimeUtils timeUtils; std::string dateStr{"1986-08-10T21:30:00+0000"}; int64_t date; @@ -45,7 +45,7 @@ TEST(TimeTest, testStringConversion) { ASSERT_EQ(dateTm.tm_min, 30); } -TEST(TimeTest, testStringConversionError) { +TEST(TimeTest, test_stringConversionError) { TimeUtils timeUtils; std::string dateStr{"1986-8-10T21:30:00+0000"}; int64_t date; @@ -53,14 +53,14 @@ TEST(TimeTest, testStringConversionError) { ASSERT_FALSE(success); } -TEST(TimeTest, testStringConversionNullParam) { +TEST(TimeTest, test_stringConversionNullParam) { TimeUtils timeUtils; std::string dateStr{"1986-8-10T21:30:00+0000"}; auto success = timeUtils.convert8601TimeStringToUnix(dateStr, nullptr); ASSERT_FALSE(success); } -TEST(TimeTest, testTimeConversion) { +TEST(TimeTest, test_timeConversion) { TimeUtils timeUtils; std::time_t randomDate = 524089800; std::tm date; @@ -73,7 +73,24 @@ TEST(TimeTest, testTimeConversion) { ASSERT_EQ(randomDate, convertBack); } -TEST(TimeTest, testCurrentTime) { +TEST(TimeTest, test_timeConversionCurrentTime) { + TimeUtils timeUtils; + int64_t time = -1; + ASSERT_TRUE(timeUtils.getCurrentUnixTime(&time)); + + std::time_t currentDate = static_cast(time); + std::tm date; + + auto safeCTimeAccess = SafeCTimeAccess::instance(); + ASSERT_TRUE(safeCTimeAccess->getGmtime(currentDate, &date)); + std::time_t convertBack; + auto success = timeUtils.convertToUtcTimeT(&date, &convertBack); + + ASSERT_TRUE(success); + ASSERT_EQ(currentDate, convertBack); +} + +TEST(TimeTest, test_currentTime) { TimeUtils timeUtils; int64_t time = -1; auto success = timeUtils.getCurrentUnixTime(&time); @@ -82,7 +99,7 @@ TEST(TimeTest, testCurrentTime) { ASSERT_GT(time, 0); } -TEST(TimeTest, testCurrentTimeNullParam) { +TEST(TimeTest, test_currentTimeNullParam) { TimeUtils timeUtils; auto success = timeUtils.getCurrentUnixTime(nullptr); ASSERT_FALSE(success); @@ -92,32 +109,50 @@ TEST(TimeTest, testCurrentTimeNullParam) { * Helper function to run through the test cases for time to string conversions. * * @param expectedString The string that convertTimeToUtcIso8601Rfc3339 should generate. - * @param t The timeval to convert. + * @param sec The seconds since epoch to convert. + * @param us The microseconds since epoch to convert. */ -static void testIso8601ConversionHelper(const std::string& expectedString, const struct timeval& t) { +static void testIso8601ConversionHelper( + const std::string& expectedString, + const std::chrono::seconds sec, + const std::chrono::microseconds us) { TimeUtils timeUtils; std::string resultString; - EXPECT_TRUE(timeUtils.convertTimeToUtcIso8601Rfc3339(t, &resultString)); + std::chrono::system_clock::time_point tp; + tp += sec; + tp += us; + EXPECT_TRUE(timeUtils.convertTimeToUtcIso8601Rfc3339(tp, &resultString)); EXPECT_EQ(expectedString, resultString); } -TEST(TimeTest, testIso8601Conversion) { - testIso8601ConversionHelper("1970-01-01T00:00:00.000Z", {0, 0}); - testIso8601ConversionHelper("1970-01-01T00:00:01.000Z", {1, 0}); - testIso8601ConversionHelper("1970-01-01T00:00:00.001Z", {0, 1000}); - testIso8601ConversionHelper("1970-01-01T00:01:00.000Z", {60, 0}); - testIso8601ConversionHelper("1970-01-01T01:00:00.000Z", {60 * 60, 0}); - testIso8601ConversionHelper("1970-01-02T00:00:00.000Z", {60 * 60 * 24, 0}); - testIso8601ConversionHelper("1970-02-01T00:00:00.000Z", {60 * 60 * 24 * 31, 0}); - testIso8601ConversionHelper("1971-01-01T00:00:00.000Z", {60 * 60 * 24 * 365, 0}); - - testIso8601ConversionHelper("1970-01-02T00:00:00.000Z", {60 * 60 * 24, 999}); - testIso8601ConversionHelper("1970-01-02T00:00:00.001Z", {60 * 60 * 24, 1000}); - testIso8601ConversionHelper("1970-01-02T00:00:00.001Z", {60 * 60 * 24, 1001}); - testIso8601ConversionHelper("1970-01-02T00:00:00.001Z", {60 * 60 * 24, 1999}); - testIso8601ConversionHelper("1970-01-02T00:00:00.002Z", {60 * 60 * 24, 2000}); - testIso8601ConversionHelper("1970-01-02T00:00:00.002Z", {60 * 60 * 24, 2001}); - testIso8601ConversionHelper("1970-01-02T00:00:00.202Z", {60 * 60 * 24, 202001}); +TEST(TimeTest, test_iso8601Conversion) { + testIso8601ConversionHelper("1970-01-01T00:00:00.000Z", std::chrono::seconds{0}, std::chrono::microseconds{0}); + testIso8601ConversionHelper("1970-01-01T00:00:01.000Z", std::chrono::seconds{1}, std::chrono::microseconds{0}); + testIso8601ConversionHelper("1970-01-01T00:00:00.001Z", std::chrono::seconds{0}, std::chrono::microseconds{1000}); + testIso8601ConversionHelper("1970-01-01T00:01:00.000Z", std::chrono::seconds{60}, std::chrono::microseconds{0}); + testIso8601ConversionHelper( + "1970-01-01T01:00:00.000Z", std::chrono::seconds{60 * 60}, std::chrono::microseconds{0}); + testIso8601ConversionHelper( + "1970-01-02T00:00:00.000Z", std::chrono::seconds{60 * 60 * 24}, std::chrono::microseconds{0}); + testIso8601ConversionHelper( + "1970-02-01T00:00:00.000Z", std::chrono::seconds{60 * 60 * 24 * 31}, std::chrono::microseconds{0}); + testIso8601ConversionHelper( + "1971-01-01T00:00:00.000Z", std::chrono::seconds{60 * 60 * 24 * 365}, std::chrono::microseconds{0}); + + testIso8601ConversionHelper( + "1970-01-02T00:00:00.000Z", std::chrono::seconds{60 * 60 * 24}, std::chrono::microseconds{999}); + testIso8601ConversionHelper( + "1970-01-02T00:00:00.001Z", std::chrono::seconds{60 * 60 * 24}, std::chrono::microseconds{1000}); + testIso8601ConversionHelper( + "1970-01-02T00:00:00.001Z", std::chrono::seconds{60 * 60 * 24}, std::chrono::microseconds{1001}); + testIso8601ConversionHelper( + "1970-01-02T00:00:00.001Z", std::chrono::seconds{60 * 60 * 24}, std::chrono::microseconds{1999}); + testIso8601ConversionHelper( + "1970-01-02T00:00:00.002Z", std::chrono::seconds{60 * 60 * 24}, std::chrono::microseconds{2000}); + testIso8601ConversionHelper( + "1970-01-02T00:00:00.002Z", std::chrono::seconds{60 * 60 * 24}, std::chrono::microseconds{2001}); + testIso8601ConversionHelper( + "1970-01-02T00:00:00.202Z", std::chrono::seconds{60 * 60 * 24}, std::chrono::microseconds{202001}); } } // namespace test diff --git a/AVSCommon/Utils/test/TimerTest.cpp b/AVSCommon/Utils/test/TimerTest.cpp index afa6240a44..306e42c8f5 100644 --- a/AVSCommon/Utils/test/TimerTest.cpp +++ b/AVSCommon/Utils/test/TimerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -27,7 +27,11 @@ namespace timing { namespace test { /// Specifies the expected timing accuracy (timestamps must be within +/- ACCURACY of expected values). +#ifdef _WIN32 +static const auto ACCURACY = std::chrono::milliseconds(30); +#else static const auto ACCURACY = std::chrono::milliseconds(15); +#endif /// Used for cases where the task should return immediately, without delay. static const auto NO_DELAY = std::chrono::milliseconds(0); @@ -193,7 +197,7 @@ bool TimerTest::waitForInactive() { } /// This test runs a single-shot timer and verifies that the task is called once, at the expected time. -TEST_F(TimerTest, singleShot) { +TEST_F(TimerTest, testTimer_singleShot) { auto t0 = std::chrono::steady_clock::now(); ASSERT_EQ( m_timer->start(SHORT_DELAY, std::bind(&TimerTest::simpleTask, this, NO_DELAY)).wait_for(TIMEOUT), @@ -206,7 +210,7 @@ TEST_F(TimerTest, singleShot) { * This test runs a multi-shot ABSOLUTE timer and verifies that the task is called the expected number of times, * and that each call occurred at the expected time. */ -TEST_F(TimerTest, multiShot) { +TEST_F(TimerTest, testTimer_multiShot) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start( SHORT_DELAY, Timer::PeriodType::ABSOLUTE, ITERATIONS, std::bind(&TimerTest::simpleTask, this, NO_DELAY))); @@ -219,7 +223,7 @@ TEST_F(TimerTest, multiShot) { * This test runs a multi-shot ABSOLUTE timer with different initial delay and verifies that the task is called * the expected number of times, and that each call occurred at the expected time. */ -TEST_F(TimerTest, multiShotWithDelay) { +TEST_F(TimerTest, testTimer_multiShotWithDelay) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start( MEDIUM_DELAY, @@ -237,7 +241,7 @@ TEST_F(TimerTest, multiShotWithDelay) { * verifies that it stops when requested, and verifies that the expected number of calls occurred at their expected * times. */ -TEST_F(TimerTest, forever) { +TEST_F(TimerTest, testTimer_forever) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start( SHORT_DELAY, Timer::PeriodType::ABSOLUTE, Timer::FOREVER, std::bind(&TimerTest::simpleTask, this, NO_DELAY))); @@ -252,7 +256,7 @@ TEST_F(TimerTest, forever) { * This test runs a slow task with an ABSOLUTE timer, but one which completes in less than a period, and verifies * that the slow task does not change the number of calls or their period. */ -TEST_F(TimerTest, slowTaskLessThanPeriod) { +TEST_F(TimerTest, testTimer_slowTaskLessThanPeriod) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start( MEDIUM_DELAY, Timer::PeriodType::ABSOLUTE, ITERATIONS, std::bind(&TimerTest::simpleTask, this, SHORT_DELAY))); @@ -264,7 +268,7 @@ TEST_F(TimerTest, slowTaskLessThanPeriod) { * This test runs a slow task with an ABSOLUTE timer which does not complete within a period, and verifies that * the slow task results in skipped calls, but on a consistent period. */ -TEST_F(TimerTest, slowTaskGreaterThanPeriod) { +TEST_F(TimerTest, testTimer_slowTaskGreaterThanPeriod) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start( SHORT_DELAY, Timer::PeriodType::ABSOLUTE, ITERATIONS, std::bind(&TimerTest::simpleTask, this, MEDIUM_DELAY))); @@ -276,7 +280,7 @@ TEST_F(TimerTest, slowTaskGreaterThanPeriod) { * This test runs a slow task with an ABSOLUTE timer which does not complete within multiple periods, and verifies * that the slow task results in skipped calls, but on a consistent period. */ -TEST_F(TimerTest, slowTaskGreaterThanTwoPeriods) { +TEST_F(TimerTest, testTimer_slowTaskGreaterThanTwoPeriods) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start( SHORT_DELAY, Timer::PeriodType::ABSOLUTE, ITERATIONS, std::bind(&TimerTest::simpleTask, this, LONG_DELAY))); @@ -288,7 +292,7 @@ TEST_F(TimerTest, slowTaskGreaterThanTwoPeriods) { * This test runs a slow task with a RELATIVE timer which does not complete within a period, and verifies that * the slow task does not result in skipped calls, but calls have a consistent between-task delay. */ -TEST_F(TimerTest, endToStartPeriod) { +TEST_F(TimerTest, testTimer_endToStartPeriod) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start( SHORT_DELAY, Timer::PeriodType::RELATIVE, ITERATIONS, std::bind(&TimerTest::simpleTask, this, MEDIUM_DELAY))); @@ -300,7 +304,7 @@ TEST_F(TimerTest, endToStartPeriod) { * This test verifies that a call to stop() before the task is called results in an inactive timer which does not * execute the task. */ -TEST_F(TimerTest, stopSingleShotBeforeTask) { +TEST_F(TimerTest, testTimer_stopSingleShotBeforeTask) { ASSERT_TRUE(m_timer->start(MEDIUM_DELAY, std::bind(&TimerTest::simpleTask, this, NO_DELAY)).valid()); ASSERT_TRUE(m_timer->isActive()); std::this_thread::sleep_for(SHORT_DELAY); @@ -317,7 +321,7 @@ TEST_F(TimerTest, stopSingleShotBeforeTask) { * This test verifies that a call to stop() while a task is executing results in an inactive timer after that task * finishes executing. */ -TEST_F(TimerTest, stopSingleShotDuringTask) { +TEST_F(TimerTest, testTimer_stopSingleShotDuringTask) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start(SHORT_DELAY, std::bind(&TimerTest::simpleTask, this, SHORT_DELAY)).valid()); ASSERT_TRUE(m_timer->isActive()); @@ -331,7 +335,7 @@ TEST_F(TimerTest, stopSingleShotDuringTask) { * This test verifies that a call to stop() after a task has finished executing leaves the timer inactive and * unchanged. */ -TEST_F(TimerTest, stopSingleShotAfterTask) { +TEST_F(TimerTest, testTimer_stopSingleShotAfterTask) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start(SHORT_DELAY, std::bind(&TimerTest::simpleTask, this, SHORT_DELAY)).valid()); ASSERT_TRUE(m_timer->isActive()); @@ -346,7 +350,7 @@ TEST_F(TimerTest, stopSingleShotAfterTask) { * This test verifies that a call to stop() on a multi-shot timer results in an inactive timer with the expected * number of calls. */ -TEST_F(TimerTest, stopMultiShot) { +TEST_F(TimerTest, testTimer_stopMultiShot) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start( SHORT_DELAY, Timer::PeriodType::ABSOLUTE, ITERATIONS, std::bind(&TimerTest::simpleTask, this, NO_DELAY))); @@ -361,7 +365,7 @@ TEST_F(TimerTest, stopMultiShot) { * This test verifies that a call to start() fails on a timer which is active but has not called a task yet, but does * not interfere with the previously scheduled task. */ -TEST_F(TimerTest, startRunningBeforeTask) { +TEST_F(TimerTest, testTimer_startRunningBeforeTask) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start(SHORT_DELAY, std::bind(&TimerTest::simpleTask, this, NO_DELAY)).valid()); ASSERT_TRUE(m_timer->isActive()); @@ -379,7 +383,7 @@ TEST_F(TimerTest, startRunningBeforeTask) { * This test verifies that a call to start() fails on a timer which is currently executing a task, but does * not interfere with that task. */ -TEST_F(TimerTest, startRunningDuringTask) { +TEST_F(TimerTest, testTimer_startRunningDuringTask) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start(SHORT_DELAY, std::bind(&TimerTest::simpleTask, this, SHORT_DELAY)).valid()); ASSERT_TRUE(m_timer->isActive()); @@ -395,7 +399,7 @@ TEST_F(TimerTest, startRunningDuringTask) { * This test verifies that a call to start() succeeds on a timer which was previously used to run a task, but which is * inactive at the time the new start() is called. */ -TEST_F(TimerTest, startRunningAfterTask) { +TEST_F(TimerTest, testTimer_startRunningAfterTask) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start(SHORT_DELAY, std::bind(&TimerTest::simpleTask, this, NO_DELAY)).valid()); ASSERT_TRUE(m_timer->isActive()); @@ -411,7 +415,7 @@ TEST_F(TimerTest, startRunningAfterTask) { } /// This test verifies that a timer which is deleted while active, but before running its task, does not run the task. -TEST_F(TimerTest, deleteBeforeTask) { +TEST_F(TimerTest, test_deleteBeforeTask) { ASSERT_TRUE(m_timer->start(SHORT_DELAY, std::bind(&TimerTest::simpleTask, this, SHORT_DELAY)).valid()); ASSERT_TRUE(m_timer->isActive()); m_timer.reset(); @@ -422,7 +426,7 @@ TEST_F(TimerTest, deleteBeforeTask) { } /// This test verifies that a timer which is deleted while running a task completes the task. -TEST_F(TimerTest, deleteDuringTask) { +TEST_F(TimerTest, testTimer_deleteDuringTask) { auto t0 = std::chrono::steady_clock::now(); ASSERT_TRUE(m_timer->start( SHORT_DELAY, @@ -441,7 +445,7 @@ TEST_F(TimerTest, deleteDuringTask) { * This test verifies that a call to start() succeeds on a timer which was previously stopped while running a task, but * which is inactive at the time the new start() is called. */ -TEST_F(TimerTest, startRunningAfterStopDuringTask) { +TEST_F(TimerTest, testTimer_startRunningAfterStopDuringTask) { ASSERT_TRUE(m_timer->start(NO_DELAY, std::bind(&TimerTest::simpleTask, this, MEDIUM_DELAY)).valid()); ASSERT_TRUE(m_timer->isActive()); std::this_thread::sleep_for(SHORT_DELAY); diff --git a/AVSCommon/Utils/test/UUIDGenerationTest.cpp b/AVSCommon/Utils/test/UUIDGenerationTest.cpp index 60d80e67d7..83db26a9d6 100644 --- a/AVSCommon/Utils/test/UUIDGenerationTest.cpp +++ b/AVSCommon/Utils/test/UUIDGenerationTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ class UUIDGenerationTest : public ::testing::Test {}; /** * Call @c generateUUID and expect a string of length @c UUID_LENGTH. */ -TEST_F(UUIDGenerationTest, testUUIDStringLength) { +TEST_F(UUIDGenerationTest, test_uUIDStringLength) { ASSERT_EQ(UUID_LENGTH, generateUUID().length()); } @@ -84,7 +84,7 @@ TEST_F(UUIDGenerationTest, testUUIDStringLength) { * Call @c generateUUID and expect a string of length @c UUID_LENGTH. Check that each character in the string * is a hexedecimal number except for the hyphens. */ -TEST_F(UUIDGenerationTest, testUUIDContainsOnlyHexCharacters) { +TEST_F(UUIDGenerationTest, test_uUIDContainsOnlyHexCharacters) { auto uuid = generateUUID(); ASSERT_EQ(UUID_LENGTH, uuid.length()); for (unsigned int i = 0; i < uuid.length(); i++) { @@ -99,21 +99,21 @@ TEST_F(UUIDGenerationTest, testUUIDContainsOnlyHexCharacters) { /** * Call @c generateUUID and check that the version is set correctly. */ -TEST_F(UUIDGenerationTest, testUUIDVersion) { +TEST_F(UUIDGenerationTest, test_uUIDVersion) { ASSERT_EQ(UUID_VERSION, generateUUID().substr(UUID_VERSION_OFFSET, 1)); } /** * Call @c generateUUID and check the variant is set correctly. */ -TEST_F(UUIDGenerationTest, testUUIDVariant) { +TEST_F(UUIDGenerationTest, test_uUIDVariant) { ASSERT_EQ(UUID_VARIANT, strtoul(generateUUID().substr(UUID_VARIANT_OFFSET, 1).c_str(), nullptr, 16) & UUID_VARIANT); } /** * Call @c generateUUID and check that the hyphens are in the right positions. */ -TEST_F(UUIDGenerationTest, testUUIDHyphens) { +TEST_F(UUIDGenerationTest, test_uUIDHyphens) { std::string uuid = generateUUID(); ASSERT_EQ(HYPHEN, uuid.substr(HYPHEN1_POSITION, 1)); ASSERT_EQ(HYPHEN, uuid.substr(HYPHEN2_POSITION, 1)); @@ -125,7 +125,7 @@ TEST_F(UUIDGenerationTest, testUUIDHyphens) { * Call @c generateUUID multiple times and check the version and variant are set correctly. * Check for uniqueness of the UUIDs generated. */ -TEST_F(UUIDGenerationTest, testMultipleRequests) { +TEST_F(UUIDGenerationTest, test_multipleRequests) { std::unordered_set uuidsGenerated; for (unsigned int i = 0; i < MAX_UUIDS_TO_GENERATE; ++i) { @@ -143,7 +143,7 @@ TEST_F(UUIDGenerationTest, testMultipleRequests) { * Call @c generateUUID from multiple threads and check the version and variant are set correctly. * Check for uniqueness of the UUIDs generated. */ -TEST_F(UUIDGenerationTest, testMultipleConcurrentRequests) { +TEST_F(UUIDGenerationTest, test_multipleConcurrentRequests) { int no_of_threads = MAX_TEST_THREADS; std::vector> uuidRequesters; std::unordered_set uuidsGenerated; @@ -167,7 +167,7 @@ TEST_F(UUIDGenerationTest, testMultipleConcurrentRequests) { /** * Call @c generateUUID and ensure all hex values are generated. Will retry @c MAX_RETRIES times. */ -TEST_F(UUIDGenerationTest, testAllHexValuesGenerated) { +TEST_F(UUIDGenerationTest, test_allHexValuesGenerated) { std::unordered_set hexCharacters = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; for (unsigned int retry = 0; retry < MAX_RETRIES && !hexCharacters.empty(); retry++) { diff --git a/ApplicationUtilities/AndroidUtilities/CMakeLists.txt b/ApplicationUtilities/AndroidUtilities/CMakeLists.txt new file mode 100644 index 0000000000..3f86d6f887 --- /dev/null +++ b/ApplicationUtilities/AndroidUtilities/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(AndroidUtilities LANGUAGES CXX) + +include(../../build/BuildDefaults.cmake) + +add_subdirectory("src") +add_subdirectory("test") + diff --git a/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidLogger.h b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidLogger.h new file mode 100644 index 0000000000..0167f92c1b --- /dev/null +++ b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidLogger.h @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDLOGGER_H_ +#define ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDLOGGER_H_ + +#include +#include + +namespace alexaClientSDK { +namespace applicationUtilities { +namespace androidUtilities { + +class AndroidLogger : public alexaClientSDK::avsCommon::utils::logger::Logger { +public: + /// @name Logger method. + void emit( + alexaClientSDK::avsCommon::utils::logger::Level level, + std::chrono::system_clock::time_point time, + const char* threadMoniker, + const char* text) override; + + /** + * Constructor. + * + * @param level The lowest severity level of logs to be emitted by this Logger. + */ + AndroidLogger(alexaClientSDK::avsCommon::utils::logger::Level level); +}; + +} // namespace androidUtilities +} // namespace applicationUtilities +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDLOGGER_H_ diff --git a/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidSLESBufferQueue.h b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidSLESBufferQueue.h new file mode 100644 index 0000000000..336fca5e45 --- /dev/null +++ b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidSLESBufferQueue.h @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDSLESBUFFERQUEUE_H_ +#define ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDSLESBUFFERQUEUE_H_ + +#include +#include + +#include + +#include +#include + +namespace alexaClientSDK { +namespace applicationUtilities { +namespace androidUtilities { + +/** + * Class responsible for consuming data produced from an AndroidSLESObject. + * + * For that, it keeps a circular buffer queue that it feeds to the OpenSL ES microphone. When the microphone + * is recording it should call @c enqueueBuffers to enqueue all free buffers to the OpenSL ES object. + * + * Whenever the OpenSL ES microphone fills up a buffer, it calls the @c onBufferCompleted method, which will copy + * the recorded data to the @c AudioInputStream and enqueue the same buffer back. + * + * When recording is stopped, clearBuffers can be used to clear all buffers with any previously recorded data. + */ +class AndroidSLESBufferQueue { +public: + /** + * Creates a new @c AndroidSLESBufferQueue object. + * + * @param queueObject The AndroidSLESObject that represent the OpenSL ES microphone and queue. + * @param writer The buffer writer to copy the recorded audio to. + * @return Pointer to the new object if object could be successfully created; otherwise, return a @c nullptr. + */ + static std::unique_ptr create( + std::shared_ptr queueObject, + std::unique_ptr writer); + + /** + * The callback function to be called when a buffer has been filled with recorded data. This function will copy the + * data out of the filled buffer into its @c AudioInputStream. + */ + void onBufferCompleted(); + + /** + * Add all free buffers into the queue. + * + * @return @c true if succeeds and @c false otherwise. + */ + bool enqueueBuffers(); + + /** + * Clear all the buffers from the queue. + * + * @return @c true if succeeds and @c false otherwise. + */ + bool clearBuffers(); + + /** + * Destructor. + */ + ~AndroidSLESBufferQueue(); + + /// The number of buffers to use. + static constexpr uint32_t NUMBER_OF_BUFFERS{2u}; + +private: + /** + * Constructor. + * + * @param slObject The @c AndroidSLESObject representation which implements the OpenSL ES microphone. + * @param bufferQueue The buffer queue interface representation used to access the OpenSL ES microphone queue. + * @param writer The buffer writer to copy the recorded audio to. + */ + AndroidSLESBufferQueue( + std::shared_ptr slObject, + SLAndroidSimpleBufferQueueItf bufferQueue, + std::unique_ptr writer); + + /// Enqueue buffer and increment m_index. This function should only be called with a lock. + bool enqueueBufferLocked(); + + /// Size of each buffer. It should be greater than 0. + static constexpr size_t BUFFER_SIZE{8192u}; + + /// Mutex used to guard queue operations. + std::mutex m_mutex; + + /// Internal AndroidSLES object which represents the microphone and its queue. Keep a reference to it to ensure that + /// the object will stay valid. + std::shared_ptr m_slObject; + + /// Internal AndroidSLES recorder interface which is used to access the OpenSL ES Object. + SLAndroidSimpleBufferQueueItf m_queueInterface; + + /// Internal buffers used to record data. + /// @warning We assume that m_buffers cannot be empty. + std::array, NUMBER_OF_BUFFERS> m_buffers; + + /// The writer that will be used to write audio data into the sds. + std::unique_ptr m_writer; + + /// Index of the next available buffer. + std::size_t m_index; +}; + +} // namespace androidUtilities +} // namespace applicationUtilities +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDSLESBUFFERQUEUE_H_ diff --git a/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidSLESEngine.h b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidSLESEngine.h new file mode 100644 index 0000000000..717cfda8e4 --- /dev/null +++ b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidSLESEngine.h @@ -0,0 +1,103 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDSLESENGINE_H_ +#define ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDSLESENGINE_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace applicationUtilities { +namespace androidUtilities { + +class AndroidSLESMicrophone; + +/** + * Class that represents the OpenSL ES engine object + interfaces. + * + * The engine is used to create other OpenSL ES objects, and each application should only have one engine. Furthermore, + * the engine should be the first OpenSL ES object to be created and the last to be destroyed. + * + * The @c create method ensures that the application has only one engine. + */ +class AndroidSLESEngine : public std::enable_shared_from_this { +public: + /** + * Creates an @c AndroidSLESEngine. This method will only succeed if there is no other engine alive. + * + * @return A shared pointer to an @c AndroidSLESEngine object if succeeds; otherwise return @c nullptr. + */ + static std::shared_ptr create(); + + /** + * Creates an OpenSL ES audio recorder. + * + * @param stream The new microphone will write the audio recorded to this stream. + * @return A unique pointer to an @c AndroidSLESRecorder object if succeeds; otherwise return @c nullptr. + */ + std::unique_ptr createMicrophoneRecorder( + std::shared_ptr stream); + + /** + * Creates an OpenSL ES audio player. Although the parameters are read-only, we cannot use const because android's + * API expects non-constant parameters. + * + * @param[in] source The audio data source specification (Read-Only). + * @param[in] sink The audio data sink specification (Read-Only). + * @param[in] requireEqualizer set to true if equalizer support is required. + * @return A unique pointer to an @c AndroidSLESObject object if succeeds; otherwise return @c nullptr. + */ + std::unique_ptr createPlayer(SLDataSource& source, SLDataSink& sink, bool requireEqualizer) + const; + + /** + * Creates an OpenSL ES output mix. + * + * @return A unique pointer to an @c AndroidSLESObject object if succeeds; otherwise return @c nullptr. + */ + std::unique_ptr createOutputMix() const; + + /** + * Destructor. + */ + ~AndroidSLESEngine(); + +private: + /** + * AndroidSLESEngine constructor. + * + * @param object Pointer to an @c AndroidSLESObject abstraction. + * @param engine The engine interface used to access the @c object. + */ + AndroidSLESEngine(std::unique_ptr object, SLEngineItf engine); + + /// Internal AndroidSLES engine object which implements the engine. + std::unique_ptr m_object; + + /// Internal AndroidSLES engine interface used to access the OpenSL ES object. + SLEngineItf m_engine; + + /// Atomic flag used to ensure there is only one @c AndroidSLESEngine. + static std::atomic_flag m_created; +}; + +} // namespace androidUtilities +} // namespace applicationUtilities +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDSLESENGINE_H_ diff --git a/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidSLESMicrophone.h b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidSLESMicrophone.h new file mode 100644 index 0000000000..2df76042a1 --- /dev/null +++ b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/AndroidSLESMicrophone.h @@ -0,0 +1,119 @@ +/* + * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDSLESMICROPHONE_H_ +#define ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_ANDROIDSLESMICROPHONE_H_ + +#include +#include + +#include +#include +#include +#include