From 703b06188eae146af396f58be4e47442d7ce5b1e Mon Sep 17 00:00:00 2001 From: Michael Woo Date: Mon, 15 Nov 2021 15:35:18 -0800 Subject: [PATCH 01/38] Version 1.26.0 alexa-client-sdk Changes in this update: Feature enhancements, updates, and resolved issues from all releases are available on the [Amazon developer portal](https://developer.amazon.com/docs/alexa/avs-device-sdk/release-notes.html) --- ACL/src/AVSConnectionManager.cpp | 47 +- ACL/src/CMakeLists.txt | 2 +- ACL/src/Transport/DownchannelHandler.cpp | 20 +- ACL/src/Transport/ExchangeHandler.cpp | 6 +- ACL/src/Transport/HTTP2Transport.cpp | 121 +- ACL/src/Transport/HTTP2TransportFactory.cpp | 2 +- ACL/src/Transport/MessageRequestHandler.cpp | 20 +- ACL/src/Transport/MessageRequestQueue.cpp | 4 +- ACL/src/Transport/MessageRouter.cpp | 37 +- ACL/src/Transport/MimeResponseSink.cpp | 18 +- ACL/src/Transport/PingHandler.cpp | 21 +- ACL/src/Transport/PostConnectSequencer.cpp | 16 +- .../Transport/PostConnectSequencerFactory.cpp | 2 +- .../SynchronizedMessageRequestQueue.cpp | 2 +- ACL/test/CMakeLists.txt | 2 +- ACL/test/Transport/Common/CMakeLists.txt | 1 - ACL/test/Transport/HTTP2TransportTest.cpp | 173 +- ADSL/src/CMakeLists.txt | 2 +- ADSL/test/common/CMakeLists.txt | 1 - AFML/src/CMakeLists.txt | 2 +- .../AVSCommon/AVS/CapabilityChangeNotifier.h | 2 +- .../AVS/CapabilityChangeNotifierInterface.h | 2 +- .../AVSCommon/AVS/DialogUXStateAggregator.h | 14 +- .../AVS/include/AVSCommon/AVS/EventBuilder.h | 20 +- .../AVS/Initialization/AlexaClientSDKInit.h | 13 +- .../AVS/SpeakerConstants/SpeakerConstants.h | 6 + AVSCommon/AVS/src/AVSContext.cpp | 6 +- AVSCommon/AVS/src/AVSDirective.cpp | 16 +- .../AVS/src/AbstractAVSConnectionManager.cpp | 17 +- AVSCommon/AVS/src/AlexaClientSDKInit.cpp | 31 +- .../AVS/src/Attachment/AttachmentManager.cpp | 2 +- .../AVS/src/Attachment/AttachmentUtils.cpp | 2 +- .../Attachment/InProcessAttachmentWriter.cpp | 2 +- AVSCommon/AVS/src/CapabilityAgent.cpp | 2 +- AVSCommon/AVS/src/CapabilityResources.cpp | 2 +- .../ActionsToDirectiveMapping.cpp | 2 +- .../CapabilitySemantics.cpp | 2 +- .../StatesToRangeMapping.cpp | 2 +- .../StatesToValueMapping.cpp | 2 +- AVSCommon/AVS/src/ComponentConfiguration.cpp | 10 +- AVSCommon/AVS/src/DialogUXStateAggregator.cpp | 81 +- AVSCommon/AVS/src/DirectiveRoutingRule.cpp | 2 +- AVSCommon/AVS/src/EditableMessageRequest.cpp | 2 +- AVSCommon/AVS/src/EventBuilder.cpp | 24 +- .../AVS/src/ExceptionEncounteredSender.cpp | 2 +- .../Initialization/SDKPrimitivesProvider.cpp | 24 +- AVSCommon/AVS/src/MessageRequest.cpp | 2 +- AVSCommon/AVS/src/WaitableMessageRequest.cpp | 4 +- AVSCommon/AVS/test/AlexaClientSDKInitTest.cpp | 164 +- .../AVS/test/Attachment/Common/CMakeLists.txt | 1 - .../AVS/test/DialogUXStateAggregatorTest.cpp | 103 +- .../SDKPrimitivesProviderTest.cpp | 271 +++ AVSCommon/CMakeLists.txt | 8 +- .../Endpoints/EndpointBuilderInterface.h | 15 + .../LocalPlaybackHandlerInterface.h | 15 +- .../LocaleAssetsManagerInterface.h | 13 + .../PowerResourceManagerInterface.h | 6 + .../SDKInterfaces/StateProviderInterface.h | 17 +- .../SDKInterfaces/MockAuthObserver.h | 39 + .../MockCapabilityConfigurationInterface.h | 42 + .../SDKInterfaces/MockDirectiveHandler.h | 2 +- .../SDKInterfaces/MockLocaleAssetsManager.h | 1 + .../SDKInterfaces/Storage/MockMiscStorage.h | 103 + .../SDKInterfaces/Storage/StubMiscStorage.h | 18 + .../Timing/MockTimerDelegateFactory.h | 41 + .../test/src/StubMiscStorage.cpp | 36 +- .../Utils/FileSystem/FileSystemUtils.h | 250 ++ .../LibcurlUtils/CurlEasyHandleWrapper.h | 24 +- .../Utils/LibcurlUtils/HTTPResponse.h | 11 +- .../Utils/LibcurlUtils/LibcurlHTTP2Request.h | 3 + .../include/AVSCommon/Utils/Logger/LogEntry.h | 37 +- .../Utils/MediaPlayer/MediaDescription.h | 59 +- .../AVSCommon/Utils/PlatformDefinitions.h | 13 +- .../include/AVSCommon/Utils/SDKConfig.h.in | 48 + .../AVSCommon/Utils/Timing/TimePoint.h | 9 + .../AVSCommon/Utils/Timing/TimeUtils.h | 22 + .../src/FileSystem/FileSystemUtilsLinux.cpp | 360 +++ .../src/FileSystem/FileSystemUtilsWindows.cpp | 351 +++ .../LibcurlUtils/CurlEasyHandleWrapper.cpp | 6 +- .../HTTPContentFetcherFactory.cpp | 4 +- .../Utils/src/LibcurlUtils/HTTPResponse.cpp | 6 + .../LibCurlHttpContentFetcher.cpp | 41 +- .../LibcurlUtils/LibcurlHTTP2Connection.cpp | 48 +- .../src/LibcurlUtils/LibcurlHTTP2Request.cpp | 20 +- AVSCommon/Utils/src/Logger/LogEntry.cpp | 18 +- .../Utils/src/MediaPlayer/PlaybackContext.cpp | 6 +- AVSCommon/Utils/src/Metrics/MetricEvent.cpp | 2 +- AVSCommon/Utils/src/TimePoint.cpp | 11 + AVSCommon/Utils/src/TimeUtils.cpp | 73 +- .../Utils/Common/MockRequiresShutdown.h | 44 + .../Utils/LibcurlUtils/MockHttpGet.h | 41 + .../Utils/LibcurlUtils/MockHttpPost.h | 57 + .../test/AVSCommon/Utils/Logger/TestTrace.h | 75 + .../Utils/MediaPlayer/PlaybackContextTest.cpp | 5 +- .../Utils/Timing/TimerDelegateTest.cpp | 351 +++ AVSCommon/Utils/test/Common/CMakeLists.txt | 3 +- .../test/Common/MockRequiresShutdown.cpp | 29 + AVSCommon/Utils/test/Common/TestTrace.cpp | 45 + AVSCommon/Utils/test/FileSystemUtilsTest.cpp | 353 +++ AVSCommon/Utils/test/MultiTimerTest.cpp | 4 +- AVSCommon/Utils/test/TimeUtilsTest.cpp | 15 + AVSGatewayManager/src/CMakeLists.txt | 2 +- .../AndroidUtilities/src/CMakeLists.txt | 5 +- .../include/DefaultClient/DefaultClient.h | 17 +- .../DefaultClient/DefaultClientComponent.h | 2 + .../ExternalCapabilitiesBuilderInterface.h | 6 +- .../DefaultClient/src/CMakeLists.txt | 7 +- .../DefaultClient/src/DefaultClient.cpp | 19 +- .../src/DefaultClientComponent.cpp | 2 + .../Resources/Audio/src/CMakeLists.txt | 2 +- .../SDKComponent/src/CMakeLists.txt | 2 +- .../SystemSoundPlayer/src/CMakeLists.txt | 2 +- .../BlueZ/include/BlueZ/DBusConnection.h | 6 +- .../BlueZ/src/CMakeLists.txt | 2 +- .../BlueZ/src/DBusConnection.cpp | 15 +- CMakeLists.txt | 11 +- CapabilitiesDelegate/src/CMakeLists.txt | 2 +- .../src/CapabilitiesDelegate.cpp | 11 + .../test/CapabilitiesDelegateTest.cpp | 77 + .../AIP/include/AIP/AudioInputProcessor.h | 15 +- .../AIP/include/AIP/AudioProvider.h | 67 + .../AIP/src/AudioInputProcessor.cpp | 99 +- CapabilityAgents/AIP/src/CMakeLists.txt | 2 +- .../Alexa/AlexaEventProcessedNotifier.h | 2 +- CapabilityAgents/Alexa/src/CMakeLists.txt | 2 +- CapabilityAgents/Alexa/test/CMakeLists.txt | 5 + .../MockAlexaInterfaceMessageSenderInternal.h | 80 + .../ApiGateway/src/CMakeLists.txt | 2 +- CapabilityAgents/CMakeLists.txt | 4 + .../InteractionModelNotifier.h | 2 +- .../acsdkInteractionModel/src/CMakeLists.txt | 2 +- .../InteractionModelNotifierInterface.h | 2 +- .../ModeController/src/CMakeLists.txt | 2 +- .../PlaybackController/src/CMakeLists.txt | 2 +- .../PlaybackController/src/PlaybackRouter.cpp | 2 +- .../PowerController/src/CMakeLists.txt | 2 +- .../RangeController/src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../include/SpeakerManager/SpeakerManager.h | 70 +- .../SpeakerManager/SpeakerManagerComponent.h | 2 + .../SpeakerManagerConfigHelper.h | 107 + .../SpeakerManagerMiscStorage.h | 97 + .../SpeakerManagerStorageInterface.h | 53 + .../SpeakerManagerStorageState.h | 49 + .../SpeakerManager/src/CMakeLists.txt | 5 +- .../SpeakerManager/src/SpeakerManager.cpp | 155 +- .../src/SpeakerManagerComponent.cpp | 1 - .../src/SpeakerManagerConfigHelper.cpp | 104 + .../src/SpeakerManagerMiscStorage.cpp | 184 ++ .../test/SpeakerManagerConfigHelperTest.cpp | 236 ++ .../test/SpeakerManagerMiscStorageTest.cpp | 291 +++ .../test/SpeakerManagerTest.cpp | 330 ++- .../SpeechSynthesizer/SpeechSynthesizer.h | 14 + .../SpeechSynthesizer/src/CMakeLists.txt | 2 +- .../src/SpeechSynthesizer.cpp | 68 +- .../test/SpeechSynthesizerTest.cpp | 99 +- CapabilityAgents/System/src/CMakeLists.txt | 2 +- .../System/src/UserInactivityMonitor.cpp | 3 + .../System/test/LocaleHandlerTest.cpp | 22 +- .../TemplateRuntime/src/CMakeLists.txt | 2 +- .../ToggleController/src/CMakeLists.txt | 2 +- Captions/Component/src/CMakeLists.txt | 2 +- Captions/Implementation/src/CMakeLists.txt | 2 +- Captions/Implementation/test/CMakeLists.txt | 1 - Captions/Interface/src/CMakeLists.txt | 2 +- Captions/Interface/test/CMakeLists.txt | 1 - CertifiedSender/src/CMakeLists.txt | 2 +- CertifiedSender/test/Common/CMakeLists.txt | 1 - ContextManager/src/CMakeLists.txt | 2 +- ContextManager/src/ContextManager.cpp | 4 +- ContextManager/test/ContextManagerTest.cpp | 38 + .../Diagnostics/DevicePropertyAggregator.h | 6 +- Diagnostics/src/CMakeLists.txt | 2 +- Diagnostics/src/DevicePropertyAggregator.cpp | 10 +- .../test/DevicePropertyAggregatorTest.cpp | 14 +- .../Endpoints/DefaultEndpointBuilder.h | 1 + Endpoints/include/Endpoints/EndpointBuilder.h | 1 + Endpoints/src/CMakeLists.txt | 2 +- Endpoints/src/DefaultEndpointBuilder.cpp | 6 + Endpoints/src/Endpoint.cpp | 4 +- Endpoints/src/EndpointBuilder.cpp | 16 + Endpoints/test/CMakeLists.txt | 3 +- Endpoints/test/DefaultEndpointBuilderTest.cpp | 295 +++ Endpoints/test/EndpointBuilderTest.cpp | 497 ++++ Endpoints/test/EndpointTest.cpp | 369 +++ Integration/AlexaClientSDKConfig.json | 16 +- .../include/Integration/TestAlertObserver.h | 6 +- Integration/src/AuthDelegateTestContext.cpp | 2 +- Integration/src/TestAlertObserver.cpp | 8 +- Integration/test/AlertsIntegrationTest.cpp | 2 +- .../AudioInputProcessorIntegrationTest.cpp | 35 +- .../test/AudioPlayerIntegrationTest.cpp | 12 +- Integration/test/CMakeLists.txt | 2 +- InterruptModel/src/CMakeLists.txt | 2 +- KWD/CMakeLists.txt | 19 - KWD/KWDProvider/src/CMakeLists.txt | 14 - .../src/KeywordDetectorProvider.cpp | 42 - KWD/Sensory/src/CMakeLists.txt | 13 - KWD/Sensory/test/CMakeLists.txt | 5 - KWD/src/CMakeLists.txt | 9 - KWD/test/AbstractKeywordDetectorTest.cpp | 176 -- KWD/test/CMakeLists.txt | 1 - .../AndroidSLESMediaPlayer/src/CMakeLists.txt | 13 +- .../GStreamerMediaPlayer/src/CMakeLists.txt | 2 +- Metrics/MetricRecorder/src/CMakeLists.txt | 2 +- Metrics/SampleMetricSink/src/CMakeLists.txt | 2 +- .../include/Metrics/MediaUplCalculator.h | 4 +- .../include/Metrics/TtsUplCalculator.h | 4 +- .../include/Metrics/UplMetricSink.h | 4 +- Metrics/UplCalculator/src/CMakeLists.txt | 2 +- .../UplCalculator/src/MediaUplCalculator.cpp | 10 +- .../UplCalculator/src/TtsUplCalculator.cpp | 10 +- Metrics/UplCalculator/src/UplMetricSink.cpp | 18 +- NOTICE.txt | 62 + PlaylistParser/src/CMakeLists.txt | 2 +- PlaylistParser/src/PlaylistUtils.cpp | 9 +- .../SQLiteCBLAuthDelegateStorage.h | 19 +- .../CBLAuthDelegate/src/CMakeLists.txt | 14 +- .../src/SQLiteCBLAuthDelegateStorage.cpp | 18 +- .../SampleApp/ExternalCapabilitiesBuilder.h | 4 +- .../SampleApp/InputControllerHandler.h | 54 + SampleApp/include/SampleApp/KeywordObserver.h | 15 + .../include/SampleApp/LocaleAssetsManager.h | 4 + .../include/SampleApp/SampleApplication.h | 35 +- .../SampleApp/SampleApplicationComponent.h | 6 +- SampleApp/include/SampleApp/UIManager.h | 2 +- SampleApp/src/CMakeLists.txt | 29 +- SampleApp/src/ExternalCapabilitiesBuilder.cpp | 53 +- SampleApp/src/InputControllerHandler.cpp | 53 + SampleApp/src/KeywordObserver.cpp | 12 + SampleApp/src/LocaleAssetsManager.cpp | 47 +- SampleApp/src/SampleApplication.cpp | 268 ++- SampleApp/src/SampleApplicationComponent.cpp | 10 +- SampleApp/src/main.cpp | 28 +- Settings/src/CMakeLists.txt | 2 +- Settings/src/Types/LocaleWakeWordsSetting.cpp | 2 +- Settings/test/LocaleWakeWordsSettingTest.cpp | 9 +- .../OpusEncoderContext/src/CMakeLists.txt | 2 +- SpeechEncoder/src/CMakeLists.txt | 2 +- .../include/SQLiteStorage/SQLiteDatabase.h | 10 + .../include/SQLiteStorage/SQLiteMiscStorage.h | 21 + Storage/SQLiteStorage/src/CMakeLists.txt | 2 +- Storage/SQLiteStorage/src/SQLiteDatabase.cpp | 50 +- .../SQLiteStorage/src/SQLiteMiscStorage.cpp | 15 +- Storage/SQLiteStorage/src/SQLiteStatement.cpp | 2 +- Storage/SQLiteStorage/src/SQLiteUtils.cpp | 2 +- .../SQLiteStorage/test/SQLiteDatabaseTest.cpp | 25 + .../test/SQLiteMiscStorageTest.cpp | 5 + SynchronizeStateSender/src/CMakeLists.txt | 2 +- .../src/PostConnectSynchronizeStateSender.cpp | 2 +- ThirdParty/CMakeLists.txt | 5 +- ThirdParty/pkcs11-2.40/CMakeLists.txt | 7 + ThirdParty/pkcs11-2.40/NOTICE.txt | 43 + ThirdParty/pkcs11-2.40/include/pkcs11.h | 265 +++ ThirdParty/pkcs11-2.40/include/pkcs11f.h | 939 ++++++++ ThirdParty/pkcs11-2.40/include/pkcs11t.h | 2003 +++++++++++++++++ applications/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../acsdkAudioInputStream/src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../AuthorizationDelegateComponent.h | 6 +- .../src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- ...DefaultSampleApplicationOptionsComponent.h | 6 +- .../src/CMakeLists.txt | 2 +- ...faultSampleApplicationOptionsComponent.cpp | 4 +- .../src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../HTTPContentFetcherComponent.h | 1 + .../src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../acsdkNullSpeechEncoder/src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../acsdkOpusSpeechEncoder/src/CMakeLists.txt | 2 +- .../PreviewAlexaClient.h | 25 +- .../PreviewAlexaClientComponent.h | 10 +- .../src/CMakeLists.txt | 9 +- .../src/PreviewAlexaClient.cpp | 139 +- .../src/PreviewAlexaClientComponent.cpp | 26 +- .../src/previewMain.cpp | 30 +- .../src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../acsdkSensoryAdapter/CMakeLists.txt | 11 + .../acsdkKWD/src/KWDComponent.cpp | 68 + .../src/SensoryRegistration.cpp | 55 + .../Sensory/SensoryKeywordDetector.h | 55 +- .../acsdkSensoryAdapter/src/CMakeLists.txt | 16 + .../src/SensoryKeywordDetector.cpp | 44 +- .../acsdkSensoryAdapter/test/CMakeLists.txt | 5 + .../test/SensoryKeywordDetectorTest.cpp | 256 +-- .../acsdkAlerts/include/acsdkAlerts/Alert.h | 89 +- .../include/acsdkAlerts/AlertScheduler.h | 49 +- .../acsdkAlerts/AlertsCapabilityAgent.h | 37 +- .../include/acsdkAlerts/AlertsComponent.h | 2 + .../include/acsdkAlerts/Renderer/Renderer.h | 23 +- .../acsdkAlerts/Storage/SQLiteAlertStorage.h | 72 +- capabilities/Alerts/acsdkAlerts/src/Alert.cpp | 261 ++- .../Alerts/acsdkAlerts/src/AlertScheduler.cpp | 186 +- .../acsdkAlerts/src/AlertsCapabilityAgent.cpp | 82 +- .../Alerts/acsdkAlerts/src/CMakeLists.txt | 2 +- .../acsdkAlerts/src/Renderer/Renderer.cpp | 38 +- .../src/Storage/SQLiteAlertStorage.cpp | 625 ++++- .../acsdkAlerts/test/AlertSchedulerTest.cpp | 78 +- .../Alerts/acsdkAlerts/test/AlertTest.cpp | 112 +- .../test/AlertsCapabilityAgentTest.cpp | 13 +- .../test/Renderer/RendererTest.cpp | 12 +- .../test/SQLiteAlertStorageTest.cpp | 860 +++++++ .../AlertObserverInterface.h | 237 +- capabilities/AssetManager/.clang-format | 69 + capabilities/AssetManager/CMakeLists.txt | 8 + .../acsdkAssetManager/CMakeLists.txt | 9 + .../include/acsdkAssetManager/AssetManager.h | 192 ++ .../acsdkAssetManager/UrlAllowListWrapper.h | 84 + .../acsdkAssetManager/src/AssetManager.cpp | 336 +++ .../acsdkAssetManager/src/CMakeLists.txt | 34 + .../acsdkAssetManager/src/DavsRequester.cpp | 239 ++ .../acsdkAssetManager/src/DavsRequester.h | 100 + .../acsdkAssetManager/src/RequestFactory.cpp | 153 ++ .../acsdkAssetManager/src/RequestFactory.h | 53 + .../acsdkAssetManager/src/Requester.cpp | 326 +++ .../acsdkAssetManager/src/Requester.h | 245 ++ .../src/RequesterFactory.cpp | 191 ++ .../acsdkAssetManager/src/RequesterFactory.h | 95 + .../src/RequesterMetadata.cpp | 151 ++ .../acsdkAssetManager/src/RequesterMetadata.h | 103 + .../acsdkAssetManager/src/Resource.cpp | 233 ++ .../acsdkAssetManager/src/Resource.h | 151 ++ .../acsdkAssetManager/src/StorageManager.cpp | 286 +++ .../acsdkAssetManager/src/StorageManager.h | 209 ++ .../src/UrlAllowListWrapper.cpp | 81 + .../acsdkAssetManager/src/UrlRequester.cpp | 225 ++ .../acsdkAssetManager/src/UrlRequester.h | 108 + .../acsdkAssetManager/test/ArtifactTest.cpp | 139 ++ .../test/ArtifactUnderTest.h | 135 ++ .../test/AssetManagerEvictionTest.cpp | 169 ++ .../test/AssetManagerInitTest.cpp | 174 ++ .../test/AssetManagerSharedResourceTest.cpp | 122 + .../test/AssetManagerTest.cpp | 297 +++ .../acsdkAssetManager/test/AssetManagerTest.h | 179 ++ .../test/AssetManagerUpdateTest.cpp | 224 ++ .../acsdkAssetManager/test/CMakeLists.txt | 12 + .../acsdkAssetManagerClient/CMakeLists.txt | 7 + .../include/acsdkAssetManagerClient/AMD.h | 43 + .../acsdkAssetManagerClient/ArtifactWrapper.h | 178 ++ .../ArtifactWrapperFactory.h | 58 + .../GenericInventory.h | 142 ++ .../src/ArtifactWrapper.cpp | 222 ++ .../src/ArtifactWrapperFactory.cpp | 51 + .../src/CMakeLists.txt | 22 + .../src/GenericInventory.cpp | 189 ++ .../CMakeLists.txt | 14 + .../ArtifactChangeObserver.h | 41 + .../ArtifactUpdateValidator.h | 47 + .../ArtifactWrapperFactoryInterface.h | 51 + .../ArtifactWrapperInterface.h | 107 + .../include/acsdkAudioPlayer/AudioPlayer.h | 6 + .../include/acsdkAudioPlayer/ErrorType.h | 78 - .../acsdkAudioPlayer/src/AudioPlayer.cpp | 333 ++- .../acsdkAudioPlayer/src/CMakeLists.txt | 5 +- .../acsdkAudioPlayer/test/AudioPlayerTest.cpp | 140 +- .../AudioPlayerInterface.h | 6 +- .../include/acsdkBluetooth/Bluetooth.h | 61 + .../acsdkBluetooth/BluetoothComponent.h | 2 + .../acsdkBluetooth/BluetoothNotifier.h | 2 +- .../acsdkBluetooth/src/Bluetooth.cpp | 185 +- .../acsdkBluetooth/src/BluetoothComponent.cpp | 40 +- .../acsdkBluetooth/src/CMakeLists.txt | 2 +- .../acsdkBluetooth/test/BluetoothTest.cpp | 173 +- .../BluetoothLocalInterface.h | 78 + .../BluetoothNotifierInterface.h | 2 +- capabilities/DavsClient/.clang-format | 69 + capabilities/DavsClient/CMakeLists.txt | 10 + .../acsdkAssetsCommon/CMakeLists.txt | 9 + .../acsdkAssetsCommon/AmdMetricWrapper.h | 96 + .../acsdkAssetsCommon/ArchiveWrapper.h | 104 + .../include/acsdkAssetsCommon/Base64Url.h | 54 + .../CurlProgressCallbackInterface.h | 45 + .../include/acsdkAssetsCommon/CurlWrapper.h | 215 ++ .../include/acsdkAssetsCommon/DataChunk.h | 55 + .../acsdkAssetsCommon/DownloadChunkQueue.h | 134 ++ .../acsdkAssetsCommon/DownloadStream.h | 85 + .../include/acsdkAssetsCommon/JitterUtil.h | 47 + .../include/acsdkAssetsCommon/ResponseSink.h | 134 ++ .../src/AmdMetricWrapper.cpp | 90 + .../acsdkAssetsCommon/src/ArchiveWrapper.cpp | 238 ++ .../acsdkAssetsCommon/src/Base64Url.cpp | 140 ++ .../acsdkAssetsCommon/src/CMakeLists.txt | 34 + .../acsdkAssetsCommon/src/CurlWrapper.cpp | 651 ++++++ .../acsdkAssetsCommon/src/DataChunk.cpp | 54 + .../src/DownloadChunkQueue.cpp | 276 +++ .../acsdkAssetsCommon/src/DownloadStream.cpp | 89 + .../acsdkAssetsCommon/src/JitterUtil.cpp | 63 + .../acsdkAssetsCommon/src/ResponseSink.cpp | 169 ++ .../test/AmdMetricWrapperTest.cpp | 80 + .../acsdkAssetsCommon/test/CMakeLists.txt | 10 + .../test/CurlWrapperTest.cpp | 47 + .../test/DownloadChunkQueueTest.cpp | 146 ++ .../test/DownloadStreamTest.cpp | 84 + .../acsdkAssetsCommon/test/JitterUtilTest.cpp | 63 + .../test/mocks/AuthDelegateMock.cpp | 63 + .../test/mocks/CMakeLists.txt | 42 + .../test/mocks/CurlWrapperMock.cpp | 246 ++ .../test/mocks/DavsServiceMock.cpp | 121 + .../mocks/InternetConnectionMonitorMock.cpp | 51 + .../acsdkAssetsCommon/test/mocks/TestUtil.cpp | 76 + .../test/mocks/include/AuthDelegateMock.h | 72 + .../test/mocks/include/CurlWrapperMock.h | 40 + .../test/mocks/include/DavsServiceMock.h | 109 + .../include/InternetConnectionMonitorMock.h | 48 + .../test/mocks/include/TestUtil.h | 56 + .../acsdkAssetsInterfaces/CMakeLists.txt | 8 + .../acsdkAssetsInterfaces/ArtifactRequest.h | 76 + .../Communication/AmdCommunicationInterface.h | 70 + .../InMemoryAmdCommunicationHandler.h | 47 + .../acsdkAssetsInterfaces/DavsRequest.h | 124 + .../include/acsdkAssetsInterfaces/Endpoint.h | 30 + .../include/acsdkAssetsInterfaces/Priority.h | 57 + .../include/acsdkAssetsInterfaces/Region.h | 32 + .../acsdkAssetsInterfaces/ResultCode.h | 73 + .../include/acsdkAssetsInterfaces/State.h | 61 + .../include/acsdkAssetsInterfaces/Type.h | 32 + .../acsdkAssetsInterfaces/UrlRequest.h | 105 + .../acsdkAssetsInterfaces/VendableArtifact.h | 131 ++ .../acsdkAssetsInterfaces/src/CMakeLists.txt | 22 + .../acsdkAssetsInterfaces/src/DavsRequest.cpp | 118 + .../acsdkAssetsInterfaces/src/UrlRequest.cpp | 100 + .../src/VendableArtifact.cpp | 196 ++ .../DavsClient/acsdkDavsClient/CMakeLists.txt | 9 + .../include/acsdkDavsClient/DavsClient.h | 168 ++ .../acsdkDavsClient/DavsEndpointHandlerV3.h | 74 + .../include/acsdkDavsClient/DavsHandler.h | 371 +++ .../acsdkDavsClient/src/CMakeLists.txt | 29 + .../acsdkDavsClient/src/DavsClient.cpp | 291 +++ .../src/DavsEndpointHandlerV3.cpp | 117 + .../acsdkDavsClient/src/DavsHandler.cpp | 571 +++++ .../acsdkDavsClient/test/CMakeLists.txt | 7 + .../acsdkDavsClient/test/DavsClientTest.cpp | 436 ++++ .../test/DavsEndpointHandlerV3Test.cpp | 73 + .../acsdkDavsClientInterfaces/CMakeLists.txt | 15 + .../ArtifactHandlerInterface.h | 85 + .../CurlProgressCallbackInterface.h | 43 + .../DavsCheckCallbackInterface.h | 58 + .../DavsDownloadCallbackInterface.h | 68 + .../DavsEndpointHandlerInterface.h | 50 + .../acsdkDeviceSetup/src/CMakeLists.txt | 2 +- .../acsdkDoNotDisturb/src/CMakeLists.txt | 2 +- .../acsdkEqualizer/src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- .../ExternalMediaAdapterHandler.h | 48 +- .../StaticExternalMediaPlayerAdapterHandler.h | 16 +- .../src/CMakeLists.txt | 2 +- .../src/ExternalMediaAdapterHandler.cpp | 58 +- .../src/ExternalMediaPlayer.cpp | 153 +- ...taticExternalMediaPlayerAdapterHandler.cpp | 53 +- .../test/ExternalMediaAdapterHandlerTest.cpp | 70 +- .../test/ExternalMediaPlayerTest.cpp | 122 +- .../AdapterUtils.h | 78 +- .../ExternalMediaAdapterConstants.h | 10 +- .../ExternalMediaAdapterHandlerInterface.h | 128 +- .../ExternalMediaAdapterInterface.h | 199 +- .../src/AdapterUtils.cpp | 27 +- .../src/CMakeLists.txt | 2 +- capabilities/InputController/.clang-format | 102 + capabilities/InputController/CMakeLists.txt | 4 + .../acsdkInputController/CMakeLists.txt | 7 + .../InputControllerFactory.h | 53 + .../acsdkInputController/src/CMakeLists.txt | 19 + .../src/InputControllerCapabilityAgent.cpp | 296 +++ .../src/InputControllerCapabilityAgent.h | 124 + .../src/InputControllerFactory.cpp | 38 + .../acsdkInputController/test/CMakeLists.txt | 10 + .../InputControllerCapabilityAgentTest.cpp | 272 +++ .../CMakeLists.txt | 12 + .../InputControllerHandlerInterface.h | 74 + .../acsdkMultiRoomMusic/MRMCapabilityAgent.h | 5 + .../acsdkMultiRoomMusic/src/CMakeLists.txt | 2 +- .../src/MRMCapabilityAgent.cpp | 16 +- .../NotificationsNotifier.h | 2 +- .../acsdkNotifications/src/CMakeLists.txt | 2 +- .../src/NotificationsCapabilityAgent.cpp | 3 +- .../src/SQLiteNotificationsStorage.cpp | 12 +- .../NotificationsNotifierInterface.h | 2 +- cmakeBuild/BuildDefaults.cmake | 24 +- cmakeBuild/cmake/AssetManager.cmake | 27 + cmakeBuild/cmake/BuildOptions.cmake | 15 + cmakeBuild/cmake/ExtensionPath.cmake | 46 +- cmakeBuild/cmake/FileSystemUtils.cmake | 32 + cmakeBuild/cmake/InputController.cmake | 12 + cmakeBuild/cmake/KeywordDetector.cmake | 13 +- cmakeBuild/cmake/LibArchive.cmake | 19 + cmakeBuild/cmake/PKCS11.cmake | 53 + cmakeBuild/cmake/RTCSC.cmake | 20 + cmakeBuild/cmake/Rapidjson.cmake | 57 +- .../acsdkAuthorization/CMakeLists.txt | 1 + .../AuthorizationManagerStorage.h | 15 +- .../LWA/LWAAuthorizationStorage.h | 153 ++ .../LWA/SQLiteLWAAuthorizationStorage.h | 96 - .../private/LWA/LWAStorageConstants.h | 54 + .../private/LWA/LWAStorageDataMigration.h | 83 + .../acsdkAuthorization/private/Logging.h | 29 + .../src/AuthorizationManager.cpp | 100 +- .../src/AuthorizationManagerStorage.cpp | 59 +- .../acsdkAuthorization/src/CMakeLists.txt | 42 +- .../src/LWA/LWAAuthorizationAdapter.cpp | 120 +- .../src/LWA/LWAAuthorizationConfiguration.cpp | 32 +- .../src/LWA/LWAAuthorizationStorage.cpp | 328 +++ .../src/LWA/LWAStorageConstants.cpp | 38 + .../src/LWA/LWAStorageDataMigration.cpp | 145 ++ .../src/LWA/SQLiteLWAAuthorizationStorage.cpp | 381 ---- .../test/AuthorizationManagerTest.cpp | 510 +++++ .../acsdkAuthorization/test/CMakeLists.txt | 29 + .../test/LWAAuthStorageMigrationTest.cpp | 196 ++ .../test/LWAAuthorizationAdapterTest.cpp | 847 +++++++ .../test/LWAAuthorizationStorageTest.cpp | 191 ++ .../acsdkAuthorization/test/StubStorage.cpp | 85 + .../acsdkAuthorization/LWA/test/StubStorage.h | 52 + core/CMakeLists.txt | 3 + core/Crypto/CMakeLists.txt | 5 + core/Crypto/acsdkCrypto/CMakeLists.txt | 11 + core/Crypto/acsdkCrypto/doc/CryptoIMPL.dox | 39 + .../include/acsdkCrypto/CryptoFactory.h | 36 + .../acsdkCrypto/private/Logging.h | 30 + .../acsdkCrypto/private/OpenSslCryptoCodec.h | 126 ++ .../private/OpenSslCryptoFactory.h | 66 + .../acsdkCrypto/private/OpenSslDigest.h | 85 + .../acsdkCrypto/private/OpenSslErrorCleanup.h | 59 + .../acsdkCrypto/private/OpenSslKeyFactory.h | 66 + .../acsdkCrypto/private/OpenSslTypeMapper.h | 86 + .../acsdkCrypto/private/OpenSslTypes.h | 72 + core/Crypto/acsdkCrypto/src/CMakeLists.txt | 37 + core/Crypto/acsdkCrypto/src/CryptoFactory.cpp | 29 + .../acsdkCrypto/src/OpenSslCryptoCodec.cpp | 295 +++ .../acsdkCrypto/src/OpenSslCryptoFactory.cpp | 71 + core/Crypto/acsdkCrypto/src/OpenSslDigest.cpp | 140 ++ .../acsdkCrypto/src/OpenSslErrorCleanup.cpp | 40 + .../acsdkCrypto/src/OpenSslKeyFactory.cpp | 75 + .../acsdkCrypto/src/OpenSslTypeMapper.cpp | 113 + core/Crypto/acsdkCrypto/src/OpenSslTypes.cpp | 35 + core/Crypto/acsdkCrypto/test/CMakeLists.txt | 12 + .../test/OpenSslCryptoCodecAEADTest.cpp | 329 +++ .../test/OpenSslCryptoCodecTest.cpp | 351 +++ .../test/OpenSslCryptoFactoryTest.cpp | 121 + .../acsdkCrypto/test/OpenSslDigestTest.cpp | 119 + .../test/OpenSslKeyFactoryTest.cpp | 124 + .../test/OpenSslTypeMapperTest.cpp | 69 + .../acsdkCryptoInterfaces/CMakeLists.txt | 5 + .../acsdkCryptoInterfaces/doc/CryptoAPI.dox | 36 + .../acsdkCryptoInterfaces/doc/Namespaces.dox | 20 + .../acsdkCryptoInterfaces/AlgorithmType.h | 81 + .../CryptoCodecInterface.h | 368 +++ .../CryptoFactoryInterface.h | 93 + .../acsdkCryptoInterfaces/DigestInterface.h | 223 ++ .../acsdkCryptoInterfaces/DigestType.h | 56 + .../KeyFactoryInterface.h | 80 + .../acsdkCryptoInterfaces/KeyStoreInterface.h | 211 ++ .../src/AlgorithmType.cpp | 49 + .../acsdkCryptoInterfaces/src/CMakeLists.txt | 7 + .../acsdkCryptoInterfaces/src/DigestType.cpp | 31 + .../acsdkCryptoInterfaces/test/CMakeLists.txt | 7 + .../test/doc/Namespaces.dox | 20 + .../test/MockCryptoCodec.h | 84 + .../test/MockCryptoFactory.h | 71 + .../acsdkCryptoInterfaces/test/MockDigest.h | 98 + .../test/MockKeyFactory.h | 50 + .../acsdkCryptoInterfaces/test/MockKeyStore.h | 160 ++ core/Crypto/acsdkPkcs11/CMakeLists.txt | 8 + core/Crypto/acsdkPkcs11/doc/CryptoPKCS11.dox | 74 + .../include/acsdkPkcs11/KeyStoreFactory.h | 49 + .../acsdkPkcs11/private/ErrorCleanupGuard.h | 63 + .../acsdkPkcs11/private/Logging.h | 30 + .../acsdkPkcs11/private/PKCS11API.h | 47 + .../acsdkPkcs11/private/PKCS11Config.h | 100 + .../acsdkPkcs11/private/PKCS11Functions.h | 129 ++ .../acsdkPkcs11/private/PKCS11Key.h | 139 ++ .../acsdkPkcs11/private/PKCS11KeyDescriptor.h | 136 ++ .../acsdkPkcs11/private/PKCS11KeyStore.h | 160 ++ .../acsdkPkcs11/private/PKCS11Session.h | 100 + .../acsdkPkcs11/private/PKCS11Slot.h | 73 + core/Crypto/acsdkPkcs11/src/CMakeLists.txt | 30 + .../acsdkPkcs11/src/KeyStoreFactory.cpp | 30 + core/Crypto/acsdkPkcs11/src/PKCS11Config.cpp | 89 + .../acsdkPkcs11/src/PKCS11Functions.cpp | 135 ++ .../acsdkPkcs11/src/PKCS11FunctionsPosix.cpp | 86 + .../acsdkPkcs11/src/PKCS11FunctionsUwp.cpp | 100 + core/Crypto/acsdkPkcs11/src/PKCS11Key.cpp | 368 +++ .../acsdkPkcs11/src/PKCS11KeyDescriptor.cpp | 97 + .../Crypto/acsdkPkcs11/src/PKCS11KeyStore.cpp | 283 +++ core/Crypto/acsdkPkcs11/src/PKCS11Session.cpp | 143 ++ core/Crypto/acsdkPkcs11/src/PKCS11Slot.cpp | 78 + core/Crypto/acsdkPkcs11/test/CMakeLists.txt | 17 + .../test/ErrorCleanupGuardTest.cpp | 49 + .../acsdkPkcs11/test/PKCS11ConfigTest.cpp | 57 + .../acsdkPkcs11/test/PKCS11FunctionsTest.cpp | 72 + .../acsdkPkcs11/test/PKCS11KeyStoreTest.cpp | 124 + .../Crypto/acsdkPkcs11/test/PKCS11KeyTest.cpp | 177 ++ .../acsdkPkcs11/test/PKCS11SessionTest.cpp | 65 + .../acsdkPkcs11/test/PKCS11SlotTest.cpp | 54 + .../acsdkPkcs11/testStubs/CMakeLists.txt | 8 + .../testStubs/doc/CryptoPKCS11Stubs.dox | 32 + .../acsdkPkcs11/testStubs/src/Pkcs11Stubs.cpp | 1067 +++++++++ core/Properties/CMakeLists.txt | 4 + .../Properties/acsdkProperties/CMakeLists.txt | 8 + .../acsdkProperties/doc/Namespaces.dox | 24 + .../acsdkProperties/doc/PropertiesIMPL.dox | 54 + .../EncryptedPropertiesFactories.h | 90 + .../acsdkProperties/ErrorCallbackInterface.h | 128 ++ .../acsdkProperties/ErrorCallbackSetter.h | 73 + .../acsdkProperties/MiscStorageAdapter.h | 129 ++ .../acsdkProperties/private/Asn1Helper.h | 169 ++ .../acsdkProperties/private/Asn1Types.h | 136 ++ .../private/DataPropertyCodec.h | 91 + .../private/DataPropertyCodecState.h | 219 ++ .../private/EncryptedProperties.h | 163 ++ .../private/EncryptedPropertiesFactory.h | 85 + .../private/EncryptionKeyPropertyCodec.h | 113 + .../private/EncryptionKeyPropertyCodecState.h | 281 +++ .../acsdkProperties/private/Logging.h | 93 + .../private/MiscStorageProperties.h | 116 + .../private/MiscStoragePropertiesFactory.h | 77 + .../acsdkProperties/private/RetryExecutor.h | 251 +++ .../acsdkProperties/src/Asn1Helper.cpp | 190 ++ .../acsdkProperties/src/Asn1Types.cpp | 56 + .../acsdkProperties/src/CMakeLists.txt | 44 + .../acsdkProperties/src/DataPropertyCodec.cpp | 199 ++ .../src/DataPropertyCodecState.cpp | 161 ++ .../src/EncryptedProperties.cpp | 821 +++++++ .../src/EncryptedPropertiesFactories.cpp | 55 + .../src/EncryptedPropertiesFactory.cpp | 72 + .../src/EncryptionKeyPropertyCodec.cpp | 248 ++ .../src/EncryptionKeyPropertyCodecState.cpp | 192 ++ .../src/ErrorCallbackSetter.cpp | 30 + .../acsdkProperties/src/Logging.cpp | 47 +- .../src/MiscStorageAdapter.cpp | 40 + .../src/MiscStorageProperties.cpp | 377 ++++ .../src/MiscStoragePropertiesFactory.cpp | 108 + .../acsdkProperties/src/RetryExecutor.cpp | 160 ++ .../src/SimpleMiscStorageUriMapper.cpp | 51 + .../acsdkProperties/test/CMakeLists.txt | 20 + .../test/MiscStoragePropertiesFactoryTest.cpp | 142 ++ .../test/MiscStoragePropertiesTest.cpp | 281 +++ .../acsdkProperties/testCrypto/CMakeLists.txt | 31 + .../testCrypto/DataPropertyCodecTest.cpp | 116 + .../EncryptedPropertiesFactoryTest.cpp | 122 + .../testCrypto/EncryptedPropertiesTest.cpp | 238 ++ .../EncryptionKeyPropertyCodecTest.cpp | 160 ++ .../acsdkPropertiesInterfaces/CMakeLists.txt | 10 + .../doc/Namespaces.dox | 20 + .../doc/PropertiesAPI.dox | 37 + .../PropertiesFactoryInterface.h | 68 + .../PropertiesInterface.h | 142 ++ .../test/CMakeLists.txt | 4 + .../test/doc/Namespaces.dox | 21 + .../test/MockProperties.h | 88 + .../test/MockPropertiesFactory.h | 54 + .../test/StubProperties.h | 73 + .../test/StubPropertiesFactory.h | 68 + .../test/src/CMakeLists.txt | 10 + .../test/src/StubProperties.cpp | 118 + .../test/src/StubPropertiesFactory.cpp | 38 + .../AlexaEventProcessedNotifierInterface.h | 2 +- .../acsdkCodecUtils}/CMakeLists.txt | 4 +- core/acsdkCodecUtils/doc/CodecUtils.dox | 34 + .../include/acsdkCodecUtils/Base64.h | 64 + .../include/acsdkCodecUtils/Hex.h | 64 + .../include/acsdkCodecUtils/Types.h | 35 + .../acsdkCodecUtils/private/Base64Common.h | 50 + .../acsdkCodecUtils/private/CodecsCommon.h | 38 + core/acsdkCodecUtils/src/Base64Common.cpp | 75 + core/acsdkCodecUtils/src/Base64Internal.cpp | 158 ++ core/acsdkCodecUtils/src/Base64OpenSsl.cpp | 67 + core/acsdkCodecUtils/src/CMakeLists.txt | 36 + core/acsdkCodecUtils/src/CodecsCommon.cpp | 37 + core/acsdkCodecUtils/src/Hex.cpp | 114 + core/acsdkCodecUtils/test/Base64CodecTest.cpp | 113 + .../test/Base64InternalCodecTest.cpp | 109 + core/acsdkCodecUtils/test/CMakeLists.txt | 15 + core/acsdkCodecUtils/test/HexCodecTest.cpp | 151 ++ core/acsdkCore/src/CMakeLists.txt | 2 +- .../src/CMakeLists.txt | 2 +- ...tConnectOperationProviderRegistrarTest.cpp | 2 + .../RegistrationNotifier.h | 2 +- .../src/CMakeLists.txt | 2 +- .../RegistrationNotifierInterface.h | 2 +- .../SystemClockNotifier.h | 2 +- .../src/CMakeLists.txt | 2 +- .../SystemClockNotifierInterface.h | 2 +- doc/doxygen.cfg.in | 5 +- shared/CMakeLists.txt | 3 + shared/KWD/CMakeLists.txt | 9 + shared/KWD/acsdkKWD/CMakeLists.txt | 6 + .../acsdkKWD/include/acsdkKWD/KWDComponent.h | 54 + shared/KWD/acsdkKWD/src/CMakeLists.txt | 25 + shared/KWD/acsdkKWD/src/KWDComponent.cpp | 31 + .../acsdkKWDImplementations/CMakeLists.txt | 7 + .../AbstractKeywordDetector.h | 50 +- .../KWDNotifierFactories.h | 41 + .../inputs/alexa_joke.wav | Bin .../inputs/alexa_stop_alexa_joke.wav | Bin .../inputs/four_alexa.wav | Bin .../inputs/stop_stop.wav | Bin .../KeywordDetectorStateNotifier.h | 45 + .../acsdkKWDImplementations/KeywordNotifier.h | 43 + .../src/AbstractKeywordDetector.cpp | 67 +- .../src/CMakeLists.txt | 23 + .../src/KWDNotifierFactories.cpp | 33 + .../src/KeywordDetectorStateNotifier.cpp | 27 + .../src/KeywordNotifier.cpp | 26 + .../test/AbstractKeywordDetectorTest.cpp | 380 ++++ .../test/CMakeLists.txt | 5 + shared/KWD/acsdkKWDInterfaces/CMakeLists.txt | 13 + .../KeywordDetectorStateNotifierInterface.h | 36 + .../KeywordNotifierInterface.h | 36 + .../KWD/acsdkKWDProvider}/CMakeLists.txt | 0 .../KWDProvider/KeywordDetectorProvider.h | 51 +- .../KWD/acsdkKWDProvider/src/CMakeLists.txt | 17 + .../src/KeywordDetectorProvider.cpp | 55 + shared/acsdkCommunication/CMakeLists.txt | 16 + .../AlwaysTrueCommunicationValidator.h | 53 + .../InMemoryCommunicationInvokeHandler.h | 146 ++ .../InMemoryCommunicationPropertiesHandler.h | 264 +++ shared/acsdkCommunication/test/CMakeLists.txt | 6 + ...InMemoryCommunicationInvokeHandlerTest.cpp | 125 + ...moryCommunicationPropertiesHandlerTest.cpp | 161 ++ .../CMakeLists.txt | 13 + .../CommunicationInvokeHandlerInterface.h | 75 + .../CommunicationPropertiesHandlerInterface.h | 103 + .../CommunicationProperty.h | 145 ++ .../CommunicationPropertyChangeSubscriber.h | 46 + .../CommunicationPropertyValidatorInterface.h | 49 + .../FunctionInvokerInterface.h | 49 + shared/acsdkManufactory/src/CMakeLists.txt | 2 +- .../include/acsdkNotifier/Notifier.h | 162 -- .../include/acsdkNotifier/internal/Notifier.h | 314 +++ shared/acsdkNotifier/test/NotifierTest.cpp | 152 +- shared/acsdkNotifierInterfaces/CMakeLists.txt | 2 + .../{ => internal}/NotifierInterface.h | 34 +- .../test/CMakeLists.txt | 9 + .../internal/MockNotifier.h | 60 + shared/acsdkShared/src/CMakeLists.txt | 2 +- .../acsdkShutdownManager/ShutdownNotifier.h | 2 +- .../acsdkShutdownManager/src/CMakeLists.txt | 2 +- .../ShutdownNotifierInterface.h | 2 +- .../MockShutdownNotifier.h | 4 + .../acsdkStartupManager/StartupNotifier.h | 2 +- shared/acsdkStartupManager/src/CMakeLists.txt | 2 +- .../StartupNotifierInterface.h | 2 +- tools/Install/android.sh | 39 +- tools/Install/genConfig.sh | 64 +- tools/Install/pi.sh | 29 +- tools/Install/setup.sh | 88 +- tools/Testing.cmake | 6 + 754 files changed, 53817 insertions(+), 3651 deletions(-) create mode 100644 AVSCommon/AVS/test/Initialization/SDKPrimitivesProviderTest.cpp create mode 100644 AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAuthObserver.h create mode 100644 AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockCapabilityConfigurationInterface.h create mode 100644 AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/MockMiscStorage.h create mode 100644 AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Timing/MockTimerDelegateFactory.h create mode 100644 AVSCommon/Utils/include/AVSCommon/Utils/FileSystem/FileSystemUtils.h create mode 100644 AVSCommon/Utils/include/AVSCommon/Utils/SDKConfig.h.in create mode 100644 AVSCommon/Utils/src/FileSystem/FileSystemUtilsLinux.cpp create mode 100644 AVSCommon/Utils/src/FileSystem/FileSystemUtilsWindows.cpp create mode 100644 AVSCommon/Utils/test/AVSCommon/Utils/Common/MockRequiresShutdown.h create mode 100644 AVSCommon/Utils/test/AVSCommon/Utils/LibcurlUtils/MockHttpGet.h create mode 100644 AVSCommon/Utils/test/AVSCommon/Utils/LibcurlUtils/MockHttpPost.h create mode 100644 AVSCommon/Utils/test/AVSCommon/Utils/Logger/TestTrace.h create mode 100644 AVSCommon/Utils/test/AVSCommon/Utils/Timing/TimerDelegateTest.cpp create mode 100644 AVSCommon/Utils/test/Common/MockRequiresShutdown.cpp create mode 100644 AVSCommon/Utils/test/Common/TestTrace.cpp create mode 100644 AVSCommon/Utils/test/FileSystemUtilsTest.cpp create mode 100644 CapabilityAgents/Alexa/test/include/MockAlexaInterfaceMessageSenderInternal.h create mode 100644 CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConfigHelper.h create mode 100644 CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerMiscStorage.h create mode 100644 CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageInterface.h create mode 100644 CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageState.h create mode 100644 CapabilityAgents/SpeakerManager/src/SpeakerManagerConfigHelper.cpp create mode 100644 CapabilityAgents/SpeakerManager/src/SpeakerManagerMiscStorage.cpp create mode 100644 CapabilityAgents/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp create mode 100644 CapabilityAgents/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp create mode 100644 Endpoints/test/DefaultEndpointBuilderTest.cpp create mode 100644 Endpoints/test/EndpointBuilderTest.cpp create mode 100644 Endpoints/test/EndpointTest.cpp delete mode 100644 KWD/CMakeLists.txt delete mode 100644 KWD/KWDProvider/src/CMakeLists.txt delete mode 100644 KWD/KWDProvider/src/KeywordDetectorProvider.cpp delete mode 100644 KWD/Sensory/src/CMakeLists.txt delete mode 100644 KWD/Sensory/test/CMakeLists.txt delete mode 100644 KWD/src/CMakeLists.txt delete mode 100644 KWD/test/AbstractKeywordDetectorTest.cpp delete mode 100644 KWD/test/CMakeLists.txt create mode 100644 SampleApp/include/SampleApp/InputControllerHandler.h create mode 100644 SampleApp/src/InputControllerHandler.cpp create mode 100644 ThirdParty/pkcs11-2.40/CMakeLists.txt create mode 100644 ThirdParty/pkcs11-2.40/NOTICE.txt create mode 100644 ThirdParty/pkcs11-2.40/include/pkcs11.h create mode 100644 ThirdParty/pkcs11-2.40/include/pkcs11f.h create mode 100644 ThirdParty/pkcs11-2.40/include/pkcs11t.h create mode 100644 applications/acsdkSensoryAdapter/CMakeLists.txt create mode 100644 applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp create mode 100644 applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp rename {KWD/Sensory/include => applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter}/Sensory/SensoryKeywordDetector.h (68%) create mode 100644 applications/acsdkSensoryAdapter/src/CMakeLists.txt rename {KWD/Sensory => applications/acsdkSensoryAdapter}/src/SensoryKeywordDetector.cpp (89%) create mode 100644 applications/acsdkSensoryAdapter/test/CMakeLists.txt rename {KWD/Sensory => applications/acsdkSensoryAdapter}/test/SensoryKeywordDetectorTest.cpp (65%) create mode 100644 capabilities/Alerts/acsdkAlerts/test/SQLiteAlertStorageTest.cpp create mode 100644 capabilities/AssetManager/.clang-format create mode 100644 capabilities/AssetManager/CMakeLists.txt create mode 100644 capabilities/AssetManager/acsdkAssetManager/CMakeLists.txt create mode 100644 capabilities/AssetManager/acsdkAssetManager/include/acsdkAssetManager/AssetManager.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/include/acsdkAssetManager/UrlAllowListWrapper.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/AssetManager.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/CMakeLists.txt create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/DavsRequester.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/DavsRequester.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/RequestFactory.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/RequestFactory.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/Requester.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/Requester.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/RequesterFactory.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/RequesterFactory.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/RequesterMetadata.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/RequesterMetadata.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/Resource.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/Resource.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/StorageManager.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/StorageManager.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/UrlAllowListWrapper.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/UrlRequester.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/src/UrlRequester.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/test/ArtifactTest.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/test/ArtifactUnderTest.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/test/AssetManagerEvictionTest.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/test/AssetManagerInitTest.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/test/AssetManagerSharedResourceTest.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/test/AssetManagerTest.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/test/AssetManagerTest.h create mode 100644 capabilities/AssetManager/acsdkAssetManager/test/AssetManagerUpdateTest.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManager/test/CMakeLists.txt create mode 100644 capabilities/AssetManager/acsdkAssetManagerClient/CMakeLists.txt create mode 100644 capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/AMD.h create mode 100644 capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/ArtifactWrapper.h create mode 100644 capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/ArtifactWrapperFactory.h create mode 100644 capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/GenericInventory.h create mode 100644 capabilities/AssetManager/acsdkAssetManagerClient/src/ArtifactWrapper.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManagerClient/src/ArtifactWrapperFactory.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManagerClient/src/CMakeLists.txt create mode 100644 capabilities/AssetManager/acsdkAssetManagerClient/src/GenericInventory.cpp create mode 100644 capabilities/AssetManager/acsdkAssetManagerClientInterfaces/CMakeLists.txt create mode 100644 capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactChangeObserver.h create mode 100644 capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactUpdateValidator.h create mode 100644 capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactWrapperFactoryInterface.h create mode 100644 capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactWrapperInterface.h delete mode 100644 capabilities/AudioPlayer/acsdkAudioPlayer/include/acsdkAudioPlayer/ErrorType.h create mode 100644 capabilities/Bluetooth/acsdkBluetoothInterfaces/include/acsdkBluetoothInterfaces/BluetoothLocalInterface.h create mode 100644 capabilities/DavsClient/.clang-format create mode 100644 capabilities/DavsClient/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/AmdMetricWrapper.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/ArchiveWrapper.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/Base64Url.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/CurlProgressCallbackInterface.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/CurlWrapper.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DataChunk.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DownloadChunkQueue.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DownloadStream.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/JitterUtil.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/ResponseSink.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/AmdMetricWrapper.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/ArchiveWrapper.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/Base64Url.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/CurlWrapper.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/DataChunk.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/DownloadChunkQueue.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/DownloadStream.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/JitterUtil.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/src/ResponseSink.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/AmdMetricWrapperTest.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/CurlWrapperTest.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/DownloadChunkQueueTest.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/DownloadStreamTest.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/JitterUtilTest.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/AuthDelegateMock.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/CurlWrapperMock.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/DavsServiceMock.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/InternetConnectionMonitorMock.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/TestUtil.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/AuthDelegateMock.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/CurlWrapperMock.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/DavsServiceMock.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/InternetConnectionMonitorMock.h create mode 100644 capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/TestUtil.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/ArtifactRequest.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Communication/AmdCommunicationInterface.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Communication/InMemoryAmdCommunicationHandler.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/DavsRequest.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Endpoint.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Priority.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Region.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/ResultCode.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/State.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Type.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/UrlRequest.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/VendableArtifact.h create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/src/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/src/DavsRequest.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/src/UrlRequest.cpp create mode 100644 capabilities/DavsClient/acsdkAssetsInterfaces/src/VendableArtifact.cpp create mode 100644 capabilities/DavsClient/acsdkDavsClient/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsClient.h create mode 100644 capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsEndpointHandlerV3.h create mode 100644 capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsHandler.h create mode 100644 capabilities/DavsClient/acsdkDavsClient/src/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkDavsClient/src/DavsClient.cpp create mode 100644 capabilities/DavsClient/acsdkDavsClient/src/DavsEndpointHandlerV3.cpp create mode 100644 capabilities/DavsClient/acsdkDavsClient/src/DavsHandler.cpp create mode 100644 capabilities/DavsClient/acsdkDavsClient/test/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkDavsClient/test/DavsClientTest.cpp create mode 100644 capabilities/DavsClient/acsdkDavsClient/test/DavsEndpointHandlerV3Test.cpp create mode 100644 capabilities/DavsClient/acsdkDavsClientInterfaces/CMakeLists.txt create mode 100644 capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/ArtifactHandlerInterface.h create mode 100644 capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/CurlProgressCallbackInterface.h create mode 100644 capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsCheckCallbackInterface.h create mode 100644 capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsDownloadCallbackInterface.h create mode 100644 capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsEndpointHandlerInterface.h create mode 100644 capabilities/InputController/.clang-format create mode 100644 capabilities/InputController/CMakeLists.txt create mode 100644 capabilities/InputController/acsdkInputController/CMakeLists.txt create mode 100644 capabilities/InputController/acsdkInputController/include/acsdkInputController/InputControllerFactory.h create mode 100644 capabilities/InputController/acsdkInputController/src/CMakeLists.txt create mode 100644 capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.cpp create mode 100644 capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.h create mode 100644 capabilities/InputController/acsdkInputController/src/InputControllerFactory.cpp create mode 100644 capabilities/InputController/acsdkInputController/test/CMakeLists.txt create mode 100644 capabilities/InputController/acsdkInputController/test/InputControllerCapabilityAgentTest.cpp create mode 100644 capabilities/InputController/acsdkInputControllerInterfaces/CMakeLists.txt create mode 100644 capabilities/InputController/acsdkInputControllerInterfaces/include/acsdkInputControllerInterfaces/InputControllerHandlerInterface.h create mode 100644 cmakeBuild/cmake/AssetManager.cmake create mode 100644 cmakeBuild/cmake/FileSystemUtils.cmake create mode 100644 cmakeBuild/cmake/InputController.cmake create mode 100644 cmakeBuild/cmake/LibArchive.cmake create mode 100644 cmakeBuild/cmake/PKCS11.cmake create mode 100644 cmakeBuild/cmake/RTCSC.cmake create mode 100644 core/Authorization/acsdkAuthorization/include/acsdkAuthorization/LWA/LWAAuthorizationStorage.h delete mode 100644 core/Authorization/acsdkAuthorization/include/acsdkAuthorization/LWA/SQLiteLWAAuthorizationStorage.h create mode 100644 core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/LWA/LWAStorageConstants.h create mode 100644 core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/LWA/LWAStorageDataMigration.h create mode 100644 core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/Logging.h create mode 100644 core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationStorage.cpp create mode 100644 core/Authorization/acsdkAuthorization/src/LWA/LWAStorageConstants.cpp create mode 100644 core/Authorization/acsdkAuthorization/src/LWA/LWAStorageDataMigration.cpp delete mode 100644 core/Authorization/acsdkAuthorization/src/LWA/SQLiteLWAAuthorizationStorage.cpp create mode 100644 core/Authorization/acsdkAuthorization/test/AuthorizationManagerTest.cpp create mode 100644 core/Authorization/acsdkAuthorization/test/CMakeLists.txt create mode 100644 core/Authorization/acsdkAuthorization/test/LWAAuthStorageMigrationTest.cpp create mode 100644 core/Authorization/acsdkAuthorization/test/LWAAuthorizationAdapterTest.cpp create mode 100644 core/Authorization/acsdkAuthorization/test/LWAAuthorizationStorageTest.cpp create mode 100644 core/Authorization/acsdkAuthorization/test/StubStorage.cpp create mode 100644 core/Authorization/acsdkAuthorization/test/include/acsdkAuthorization/LWA/test/StubStorage.h create mode 100644 core/Crypto/CMakeLists.txt create mode 100644 core/Crypto/acsdkCrypto/CMakeLists.txt create mode 100644 core/Crypto/acsdkCrypto/doc/CryptoIMPL.dox create mode 100644 core/Crypto/acsdkCrypto/include/acsdkCrypto/CryptoFactory.h create mode 100644 core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/Logging.h create mode 100644 core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslCryptoCodec.h create mode 100644 core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslCryptoFactory.h create mode 100644 core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslDigest.h create mode 100644 core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslErrorCleanup.h create mode 100644 core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslKeyFactory.h create mode 100644 core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslTypeMapper.h create mode 100644 core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslTypes.h create mode 100644 core/Crypto/acsdkCrypto/src/CMakeLists.txt create mode 100644 core/Crypto/acsdkCrypto/src/CryptoFactory.cpp create mode 100644 core/Crypto/acsdkCrypto/src/OpenSslCryptoCodec.cpp create mode 100644 core/Crypto/acsdkCrypto/src/OpenSslCryptoFactory.cpp create mode 100644 core/Crypto/acsdkCrypto/src/OpenSslDigest.cpp create mode 100644 core/Crypto/acsdkCrypto/src/OpenSslErrorCleanup.cpp create mode 100644 core/Crypto/acsdkCrypto/src/OpenSslKeyFactory.cpp create mode 100644 core/Crypto/acsdkCrypto/src/OpenSslTypeMapper.cpp create mode 100644 core/Crypto/acsdkCrypto/src/OpenSslTypes.cpp create mode 100644 core/Crypto/acsdkCrypto/test/CMakeLists.txt create mode 100644 core/Crypto/acsdkCrypto/test/OpenSslCryptoCodecAEADTest.cpp create mode 100644 core/Crypto/acsdkCrypto/test/OpenSslCryptoCodecTest.cpp create mode 100644 core/Crypto/acsdkCrypto/test/OpenSslCryptoFactoryTest.cpp create mode 100644 core/Crypto/acsdkCrypto/test/OpenSslDigestTest.cpp create mode 100644 core/Crypto/acsdkCrypto/test/OpenSslKeyFactoryTest.cpp create mode 100644 core/Crypto/acsdkCrypto/test/OpenSslTypeMapperTest.cpp create mode 100644 core/Crypto/acsdkCryptoInterfaces/CMakeLists.txt create mode 100644 core/Crypto/acsdkCryptoInterfaces/doc/CryptoAPI.dox create mode 100644 core/Crypto/acsdkCryptoInterfaces/doc/Namespaces.dox create mode 100644 core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/AlgorithmType.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/CryptoCodecInterface.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/CryptoFactoryInterface.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/DigestInterface.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/DigestType.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/KeyFactoryInterface.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/KeyStoreInterface.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/src/AlgorithmType.cpp create mode 100644 core/Crypto/acsdkCryptoInterfaces/src/CMakeLists.txt create mode 100644 core/Crypto/acsdkCryptoInterfaces/src/DigestType.cpp create mode 100644 core/Crypto/acsdkCryptoInterfaces/test/CMakeLists.txt create mode 100644 core/Crypto/acsdkCryptoInterfaces/test/doc/Namespaces.dox create mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoCodec.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoFactory.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockDigest.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyFactory.h create mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyStore.h create mode 100644 core/Crypto/acsdkPkcs11/CMakeLists.txt create mode 100644 core/Crypto/acsdkPkcs11/doc/CryptoPKCS11.dox create mode 100644 core/Crypto/acsdkPkcs11/include/acsdkPkcs11/KeyStoreFactory.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/ErrorCleanupGuard.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/Logging.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11API.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Config.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Functions.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Key.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11KeyDescriptor.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11KeyStore.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Session.h create mode 100644 core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Slot.h create mode 100644 core/Crypto/acsdkPkcs11/src/CMakeLists.txt create mode 100644 core/Crypto/acsdkPkcs11/src/KeyStoreFactory.cpp create mode 100644 core/Crypto/acsdkPkcs11/src/PKCS11Config.cpp create mode 100644 core/Crypto/acsdkPkcs11/src/PKCS11Functions.cpp create mode 100644 core/Crypto/acsdkPkcs11/src/PKCS11FunctionsPosix.cpp create mode 100644 core/Crypto/acsdkPkcs11/src/PKCS11FunctionsUwp.cpp create mode 100644 core/Crypto/acsdkPkcs11/src/PKCS11Key.cpp create mode 100644 core/Crypto/acsdkPkcs11/src/PKCS11KeyDescriptor.cpp create mode 100644 core/Crypto/acsdkPkcs11/src/PKCS11KeyStore.cpp create mode 100644 core/Crypto/acsdkPkcs11/src/PKCS11Session.cpp create mode 100644 core/Crypto/acsdkPkcs11/src/PKCS11Slot.cpp create mode 100644 core/Crypto/acsdkPkcs11/test/CMakeLists.txt create mode 100644 core/Crypto/acsdkPkcs11/test/ErrorCleanupGuardTest.cpp create mode 100644 core/Crypto/acsdkPkcs11/test/PKCS11ConfigTest.cpp create mode 100644 core/Crypto/acsdkPkcs11/test/PKCS11FunctionsTest.cpp create mode 100644 core/Crypto/acsdkPkcs11/test/PKCS11KeyStoreTest.cpp create mode 100644 core/Crypto/acsdkPkcs11/test/PKCS11KeyTest.cpp create mode 100644 core/Crypto/acsdkPkcs11/test/PKCS11SessionTest.cpp create mode 100644 core/Crypto/acsdkPkcs11/test/PKCS11SlotTest.cpp create mode 100644 core/Crypto/acsdkPkcs11/testStubs/CMakeLists.txt create mode 100644 core/Crypto/acsdkPkcs11/testStubs/doc/CryptoPKCS11Stubs.dox create mode 100644 core/Crypto/acsdkPkcs11/testStubs/src/Pkcs11Stubs.cpp create mode 100644 core/Properties/CMakeLists.txt create mode 100644 core/Properties/acsdkProperties/CMakeLists.txt create mode 100644 core/Properties/acsdkProperties/doc/Namespaces.dox create mode 100644 core/Properties/acsdkProperties/doc/PropertiesIMPL.dox create mode 100644 core/Properties/acsdkProperties/include/acsdkProperties/EncryptedPropertiesFactories.h create mode 100644 core/Properties/acsdkProperties/include/acsdkProperties/ErrorCallbackInterface.h create mode 100644 core/Properties/acsdkProperties/include/acsdkProperties/ErrorCallbackSetter.h create mode 100644 core/Properties/acsdkProperties/include/acsdkProperties/MiscStorageAdapter.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Asn1Helper.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Asn1Types.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/DataPropertyCodec.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/DataPropertyCodecState.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptedProperties.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptedPropertiesFactory.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptionKeyPropertyCodec.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptionKeyPropertyCodecState.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Logging.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/MiscStorageProperties.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/MiscStoragePropertiesFactory.h create mode 100644 core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/RetryExecutor.h create mode 100644 core/Properties/acsdkProperties/src/Asn1Helper.cpp create mode 100644 core/Properties/acsdkProperties/src/Asn1Types.cpp create mode 100644 core/Properties/acsdkProperties/src/CMakeLists.txt create mode 100644 core/Properties/acsdkProperties/src/DataPropertyCodec.cpp create mode 100644 core/Properties/acsdkProperties/src/DataPropertyCodecState.cpp create mode 100644 core/Properties/acsdkProperties/src/EncryptedProperties.cpp create mode 100644 core/Properties/acsdkProperties/src/EncryptedPropertiesFactories.cpp create mode 100644 core/Properties/acsdkProperties/src/EncryptedPropertiesFactory.cpp create mode 100644 core/Properties/acsdkProperties/src/EncryptionKeyPropertyCodec.cpp create mode 100644 core/Properties/acsdkProperties/src/EncryptionKeyPropertyCodecState.cpp create mode 100644 core/Properties/acsdkProperties/src/ErrorCallbackSetter.cpp rename AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h => core/Properties/acsdkProperties/src/Logging.cpp (50%) create mode 100644 core/Properties/acsdkProperties/src/MiscStorageAdapter.cpp create mode 100644 core/Properties/acsdkProperties/src/MiscStorageProperties.cpp create mode 100644 core/Properties/acsdkProperties/src/MiscStoragePropertiesFactory.cpp create mode 100644 core/Properties/acsdkProperties/src/RetryExecutor.cpp create mode 100644 core/Properties/acsdkProperties/src/SimpleMiscStorageUriMapper.cpp create mode 100644 core/Properties/acsdkProperties/test/CMakeLists.txt create mode 100644 core/Properties/acsdkProperties/test/MiscStoragePropertiesFactoryTest.cpp create mode 100644 core/Properties/acsdkProperties/test/MiscStoragePropertiesTest.cpp create mode 100644 core/Properties/acsdkProperties/testCrypto/CMakeLists.txt create mode 100644 core/Properties/acsdkProperties/testCrypto/DataPropertyCodecTest.cpp create mode 100644 core/Properties/acsdkProperties/testCrypto/EncryptedPropertiesFactoryTest.cpp create mode 100644 core/Properties/acsdkProperties/testCrypto/EncryptedPropertiesTest.cpp create mode 100644 core/Properties/acsdkProperties/testCrypto/EncryptionKeyPropertyCodecTest.cpp create mode 100644 core/Properties/acsdkPropertiesInterfaces/CMakeLists.txt create mode 100644 core/Properties/acsdkPropertiesInterfaces/doc/Namespaces.dox create mode 100644 core/Properties/acsdkPropertiesInterfaces/doc/PropertiesAPI.dox create mode 100644 core/Properties/acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces/PropertiesFactoryInterface.h create mode 100644 core/Properties/acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces/PropertiesInterface.h create mode 100644 core/Properties/acsdkPropertiesInterfaces/test/CMakeLists.txt create mode 100644 core/Properties/acsdkPropertiesInterfaces/test/doc/Namespaces.dox create mode 100644 core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockProperties.h create mode 100644 core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockPropertiesFactory.h create mode 100644 core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/StubProperties.h create mode 100644 core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/StubPropertiesFactory.h create mode 100644 core/Properties/acsdkPropertiesInterfaces/test/src/CMakeLists.txt create mode 100644 core/Properties/acsdkPropertiesInterfaces/test/src/StubProperties.cpp create mode 100644 core/Properties/acsdkPropertiesInterfaces/test/src/StubPropertiesFactory.cpp rename {KWD/Sensory => core/acsdkCodecUtils}/CMakeLists.txt (55%) create mode 100644 core/acsdkCodecUtils/doc/CodecUtils.dox create mode 100644 core/acsdkCodecUtils/include/acsdkCodecUtils/Base64.h create mode 100644 core/acsdkCodecUtils/include/acsdkCodecUtils/Hex.h create mode 100644 core/acsdkCodecUtils/include/acsdkCodecUtils/Types.h create mode 100644 core/acsdkCodecUtils/privateInclude/acsdkCodecUtils/private/Base64Common.h create mode 100644 core/acsdkCodecUtils/privateInclude/acsdkCodecUtils/private/CodecsCommon.h create mode 100644 core/acsdkCodecUtils/src/Base64Common.cpp create mode 100644 core/acsdkCodecUtils/src/Base64Internal.cpp create mode 100644 core/acsdkCodecUtils/src/Base64OpenSsl.cpp create mode 100644 core/acsdkCodecUtils/src/CMakeLists.txt create mode 100644 core/acsdkCodecUtils/src/CodecsCommon.cpp create mode 100644 core/acsdkCodecUtils/src/Hex.cpp create mode 100644 core/acsdkCodecUtils/test/Base64CodecTest.cpp create mode 100644 core/acsdkCodecUtils/test/Base64InternalCodecTest.cpp create mode 100644 core/acsdkCodecUtils/test/CMakeLists.txt create mode 100644 core/acsdkCodecUtils/test/HexCodecTest.cpp create mode 100644 shared/KWD/CMakeLists.txt create mode 100644 shared/KWD/acsdkKWD/CMakeLists.txt create mode 100644 shared/KWD/acsdkKWD/include/acsdkKWD/KWDComponent.h create mode 100644 shared/KWD/acsdkKWD/src/CMakeLists.txt create mode 100644 shared/KWD/acsdkKWD/src/KWDComponent.cpp create mode 100644 shared/KWD/acsdkKWDImplementations/CMakeLists.txt rename {KWD/include/KWD => shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations}/AbstractKeywordDetector.h (79%) create mode 100644 shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/KWDNotifierFactories.h rename {KWD => shared/KWD/acsdkKWDImplementations}/inputs/alexa_joke.wav (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/inputs/alexa_stop_alexa_joke.wav (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/inputs/four_alexa.wav (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/inputs/stop_stop.wav (100%) create mode 100644 shared/KWD/acsdkKWDImplementations/privateInclude/acsdkKWDImplementations/KeywordDetectorStateNotifier.h create mode 100644 shared/KWD/acsdkKWDImplementations/privateInclude/acsdkKWDImplementations/KeywordNotifier.h rename {KWD => shared/KWD/acsdkKWDImplementations}/src/AbstractKeywordDetector.cpp (66%) create mode 100644 shared/KWD/acsdkKWDImplementations/src/CMakeLists.txt create mode 100644 shared/KWD/acsdkKWDImplementations/src/KWDNotifierFactories.cpp create mode 100644 shared/KWD/acsdkKWDImplementations/src/KeywordDetectorStateNotifier.cpp create mode 100644 shared/KWD/acsdkKWDImplementations/src/KeywordNotifier.cpp create mode 100644 shared/KWD/acsdkKWDImplementations/test/AbstractKeywordDetectorTest.cpp create mode 100644 shared/KWD/acsdkKWDImplementations/test/CMakeLists.txt create mode 100644 shared/KWD/acsdkKWDInterfaces/CMakeLists.txt create mode 100644 shared/KWD/acsdkKWDInterfaces/include/acsdkKWDInterfaces/KeywordDetectorStateNotifierInterface.h create mode 100644 shared/KWD/acsdkKWDInterfaces/include/acsdkKWDInterfaces/KeywordNotifierInterface.h rename {KWD/KWDProvider => shared/KWD/acsdkKWDProvider}/CMakeLists.txt (100%) rename {KWD/KWDProvider/include => shared/KWD/acsdkKWDProvider/include/acsdkKWDProvider}/KWDProvider/KeywordDetectorProvider.h (50%) create mode 100644 shared/KWD/acsdkKWDProvider/src/CMakeLists.txt create mode 100644 shared/KWD/acsdkKWDProvider/src/KeywordDetectorProvider.cpp create mode 100644 shared/acsdkCommunication/CMakeLists.txt create mode 100644 shared/acsdkCommunication/include/acsdkCommunication/AlwaysTrueCommunicationValidator.h create mode 100644 shared/acsdkCommunication/include/acsdkCommunication/InMemoryCommunicationInvokeHandler.h create mode 100644 shared/acsdkCommunication/include/acsdkCommunication/InMemoryCommunicationPropertiesHandler.h create mode 100644 shared/acsdkCommunication/test/CMakeLists.txt create mode 100644 shared/acsdkCommunication/test/InMemoryCommunicationInvokeHandlerTest.cpp create mode 100644 shared/acsdkCommunication/test/InMemoryCommunicationPropertiesHandlerTest.cpp create mode 100644 shared/acsdkCommunicationInterfaces/CMakeLists.txt create mode 100644 shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationInvokeHandlerInterface.h create mode 100644 shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertiesHandlerInterface.h create mode 100644 shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationProperty.h create mode 100644 shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertyChangeSubscriber.h create mode 100644 shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertyValidatorInterface.h create mode 100644 shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/FunctionInvokerInterface.h delete mode 100644 shared/acsdkNotifier/include/acsdkNotifier/Notifier.h create mode 100644 shared/acsdkNotifier/include/acsdkNotifier/internal/Notifier.h rename shared/acsdkNotifierInterfaces/include/acsdkNotifierInterfaces/{ => internal}/NotifierInterface.h (65%) create mode 100644 shared/acsdkNotifierInterfaces/test/CMakeLists.txt create mode 100644 shared/acsdkNotifierInterfaces/test/include/acsdkNotifierInterfaces/internal/MockNotifier.h mode change 100644 => 100755 tools/Install/setup.sh diff --git a/ACL/src/AVSConnectionManager.cpp b/ACL/src/AVSConnectionManager.cpp index 8396c2ee0a..c5983a6c8e 100644 --- a/ACL/src/AVSConnectionManager.cpp +++ b/ACL/src/AVSConnectionManager.cpp @@ -31,10 +31,17 @@ static const std::string TAG("AVSConnectionManager"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) +/** + * Create a LogEntry using this file's TAG, the specified event string, and pointer to disambiguate the instance. + * + * @param event The event string for this @c LogEntry. + */ +#define LX_P(event) LX(event).p("this", this) + std::shared_ptr AVSConnectionManager::createMessageSenderInterface( const std::shared_ptr& connectionManager) { return connectionManager; @@ -64,19 +71,20 @@ std::shared_ptr AVSConnectionManager::create( if (!messageRouter) { ACSDK_ERROR(LX("createFailed").d("reason", "nullMessageRouter").d("return", "nullptr")); + return nullptr; } for (auto observer : connectionStatusObservers) { if (!observer) { - ACSDK_ERROR(LX("createFailed").d("reason", "nullConnectionStatusObserver").d("return", "nullptr")); + ACSDK_ERROR(LX("createFailed").d("reason", "nullConnectionStatusObserver")); return nullptr; } } for (auto observer : messageObservers) { if (!observer) { - ACSDK_ERROR(LX("createFailed").d("reason", "nullMessageObserver").d("return", "nullptr")); + ACSDK_ERROR(LX("createFailed").d("reason", "nullMessageObserver")); return nullptr; } } @@ -91,7 +99,7 @@ std::shared_ptr AVSConnectionManager::create( } if (internetConnectionMonitor) { - ACSDK_DEBUG5(LX(__func__).m("Subscribing to InternetConnectionMonitor Callbacks")); + ACSDK_DEBUG5(LX("create").m("Subscribing to InternetConnectionMonitor Callbacks")); internetConnectionMonitor->addInternetConnectionObserver(connectionManager); } @@ -109,9 +117,11 @@ AVSConnectionManager::AVSConnectionManager( m_messageObservers{messageObservers}, m_messageRouter{messageRouter}, m_internetConnectionMonitor{internetConnectionMonitor} { + ACSDK_DEBUG5(LX_P("AVSConnectionManager")); } void AVSConnectionManager::doShutdown() { + ACSDK_DEBUG5(LX_P("doShutdown")); if (m_internetConnectionMonitor) { m_internetConnectionMonitor->removeInternetConnectionObserver(shared_from_this()); } @@ -136,8 +146,8 @@ void AVSConnectionManager::doShutdown() { } void AVSConnectionManager::enable() { + ACSDK_DEBUG5(LX_P("enable")); std::lock_guard lock(m_isEnabledMutex); - ACSDK_DEBUG5(LX(__func__)); m_isEnabled = true; auto messageRouter = getMessageRouter(); if (messageRouter) { @@ -146,8 +156,8 @@ void AVSConnectionManager::enable() { } void AVSConnectionManager::disable() { + ACSDK_DEBUG5(LX_P("disable")); std::lock_guard lock(m_isEnabledMutex); - ACSDK_DEBUG5(LX(__func__)); m_isEnabled = false; auto messageRouter = getMessageRouter(); if (messageRouter) { @@ -161,8 +171,9 @@ bool AVSConnectionManager::isEnabled() { } void AVSConnectionManager::reconnect() { + ACSDK_DEBUG5(LX_P("reconnect")); std::lock_guard lock(m_isEnabledMutex); - ACSDK_DEBUG5(LX(__func__).d("isEnabled", m_isEnabled)); + ACSDK_DEBUG5(LX_P("reconnect").d("isEnabled", m_isEnabled)); if (m_isEnabled) { auto messageRouter = getMessageRouter(); if (messageRouter) { @@ -175,9 +186,10 @@ void AVSConnectionManager::reconnect() { void AVSConnectionManager::sendMessage(std::shared_ptr request) { auto messageRouter = getMessageRouter(); if (messageRouter) { + ACSDK_DEBUG7(LX_P("sendMessage")); messageRouter->sendMessage(request); } else { - ACSDK_WARN(LX("sendMessageFailed") + ACSDK_WARN(LX_P("sendMessageFailed") .d("reason", "nullMessageRouter") .m("setting status for request to NOT_CONNECTED") .d("request", request->getJsonContent())); @@ -194,12 +206,12 @@ bool AVSConnectionManager::isConnected() const { } void AVSConnectionManager::onWakeConnectionRetry() { - ACSDK_DEBUG9(LX(__func__)); + ACSDK_DEBUG9(LX_P("onWakeConnectionRetry")); auto messageRouter = getMessageRouter(); if (messageRouter) { messageRouter->onWakeConnectionRetry(); } else { - ACSDK_WARN(LX("onWakeConnectionRetryFailed").d("reason", "nullMessageRouter")); + ACSDK_WARN(LX_P("onWakeConnectionRetryFailed").d("reason", "nullMessageRouter")); } } @@ -208,7 +220,7 @@ void AVSConnectionManager::setAVSGateway(const std::string& avsGateway) { if (messageRouter) { messageRouter->setAVSGateway(avsGateway); } else { - ACSDK_WARN(LX("setAVSGatewayFailed").d("reason", "nullMessageRouter")); + ACSDK_WARN(LX_P("setAVSGatewayFailed").d("reason", "nullMessageRouter")); } } @@ -217,13 +229,13 @@ std::string AVSConnectionManager::getAVSGateway() const { if (messageRouter) { return messageRouter->getAVSGateway(); } else { - ACSDK_WARN(LX("getAVSGatewayFailed").d("reason", "nullMessageRouter")); + ACSDK_WARN(LX_P("getAVSGatewayFailed").d("reason", "nullMessageRouter")); } return ""; } void AVSConnectionManager::onConnectionStatusChanged(bool connected) { - ACSDK_DEBUG5(LX(__func__).d("connected", connected).d("isEnabled", m_isEnabled)); + ACSDK_DEBUG5(LX_P("onConnectionStatusChanged").d("connected", connected).d("isEnabled", m_isEnabled)); if (m_isEnabled) { auto messageRouter = getMessageRouter(); if (messageRouter) { @@ -233,7 +245,7 @@ void AVSConnectionManager::onConnectionStatusChanged(bool connected) { messageRouter->onWakeVerifyConnectivity(); } } else { - ACSDK_WARN(LX("onConnectionStatusChangedFailed").d("reason", "nullMessageRouter")); + ACSDK_WARN(LX_P("onConnectionStatusChangedFailed").d("reason", "nullMessageRouter")); } } } @@ -241,7 +253,7 @@ void AVSConnectionManager::onConnectionStatusChanged(bool connected) { void AVSConnectionManager::addMessageObserver( std::shared_ptr observer) { if (!observer) { - ACSDK_ERROR(LX("addObserverFailed").d("reason", "nullObserver")); + ACSDK_ERROR(LX_P("addObserverFailed").d("reason", "nullObserver")); return; } @@ -252,7 +264,7 @@ void AVSConnectionManager::addMessageObserver( void AVSConnectionManager::removeMessageObserver( std::shared_ptr observer) { if (!observer) { - ACSDK_ERROR(LX("removeObserverFailed").d("reason", "nullObserver")); + ACSDK_ERROR(LX_P("removeObserverFailed").d("reason", "nullObserver")); return; } @@ -264,7 +276,8 @@ void AVSConnectionManager::onConnectionStatusChanged( const ConnectionStatusObserverInterface::Status status, const std::vector& engineConnectionStatuses) { - ACSDK_DEBUG(LX(__func__).d("status", status).d("engine_count", engineConnectionStatuses.size())); + ACSDK_DEBUG( + LX_P("onConnectionStatusChanged").d("status", status).d("engine_count", engineConnectionStatuses.size())); updateConnectionStatus(status, engineConnectionStatuses); } diff --git a/ACL/src/CMakeLists.txt b/ACL/src/CMakeLists.txt index ee1bbc040f..d648e5cf6d 100644 --- a/ACL/src/CMakeLists.txt +++ b/ACL/src/CMakeLists.txt @@ -1,7 +1,7 @@ file(GLOB_RECURSE ACL_SRC "${ACL_SOURCE_DIR}/src/*.cpp") add_definitions("-DACSDK_LOG_MODULE=acl") add_definitions("-DACSDK_OPENSSL_MIN_VER_REQUIRED=${OPENSSL_MIN_VERSION}") -add_library(ACL SHARED ${ACL_SRC}) +add_library(ACL ${ACL_SRC}) target_include_directories(ACL PUBLIC "${MultipartParser_SOURCE_DIR}") target_include_directories(ACL PUBLIC ${CURL_INCLUDE_DIRS}) target_include_directories(ACL PUBLIC "${ACL_SOURCE_DIR}/include") diff --git a/ACL/src/Transport/DownchannelHandler.cpp b/ACL/src/Transport/DownchannelHandler.cpp index ea3bacbd4a..f68908c0ba 100644 --- a/ACL/src/Transport/DownchannelHandler.cpp +++ b/ACL/src/Transport/DownchannelHandler.cpp @@ -54,15 +54,15 @@ static const std::string RESPONSE_FINISHED = "RESPONSE_FINISHED"; /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /** * Creates a MetricEvent with the given event name and datapoint and submits it with the metric recorder. - * @param metricRecorder - The @c MetricRecorderInterface used log the metric. - * @param eventName - The event name of the metric to be logged. - * @param dataPoint - The @c DataPoint to be added to the metric. + * @param metricRecorder The @c MetricRecorderInterface used log the metric. + * @param eventName The event name of the metric to be logged. + * @param dataPoint The @c DataPoint to be added to the metric. */ void submitMetric( const std::shared_ptr& metricRecorder, @@ -111,7 +111,7 @@ std::shared_ptr DownchannelHandler::create( std::shared_ptr messageConsumer, std::shared_ptr attachmentManager, const std::shared_ptr& metricRecorder) { - ACSDK_DEBUG9(LX(__func__).d("context", context.get())); + ACSDK_DEBUG9(LX("create").d("context", context.get())); if (!context) { ACSDK_CRITICAL(LX("createFailed").d("reason", "nullHttp2Transport")); @@ -144,12 +144,12 @@ std::shared_ptr DownchannelHandler::create( } std::vector DownchannelHandler::getRequestHeaderLines() { - ACSDK_DEBUG9(LX(__func__)); + ACSDK_DEBUG9(LX("getRequestHeaderLines")); return {m_authHeader}; } HTTP2SendDataResult DownchannelHandler::onSendData(char* bytes, size_t size) { - ACSDK_DEBUG9(LX(__func__).d("size", size)); + ACSDK_DEBUG9(LX("onSendData").d("size", size)); return HTTP2SendDataResult::COMPLETE; } @@ -159,7 +159,7 @@ DownchannelHandler::DownchannelHandler( const std::shared_ptr& metricRecorder) : ExchangeHandler{context, authToken}, m_metricRecorder{metricRecorder} { - ACSDK_DEBUG9(LX(__func__).d("context", context.get())); + ACSDK_DEBUG9(LX("init").d("context", context.get())); m_powerResource = PowerMonitor::getInstance()->createLocalPowerResource(TAG); if (m_powerResource) { m_powerResource->acquire(); @@ -171,7 +171,7 @@ void DownchannelHandler::onActivity() { } bool DownchannelHandler::onReceiveResponseCode(long responseCode) { - ACSDK_DEBUG5(LX(__func__).d("responseCode", responseCode)); + ACSDK_DEBUG5(LX("onReceiveResponseCode").d("responseCode", responseCode)); switch (intToHTTPResponseCode(responseCode)) { case HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED: case HTTPResponseCode::SUCCESS_CREATED: @@ -205,7 +205,7 @@ bool DownchannelHandler::onReceiveResponseCode(long responseCode) { } void DownchannelHandler::onResponseFinished(HTTP2ResponseFinishedStatus status, const std::string& nonMimeBody) { - ACSDK_DEBUG5(LX(__func__).d("status", status).d("nonMimeBody", nonMimeBody)); + ACSDK_DEBUG5(LX("onResponseFinished").d("status", status).d("nonMimeBody", nonMimeBody)); m_context->onDownchannelFinished(); submitResponseFinishedMetric(m_metricRecorder, status); } diff --git a/ACL/src/Transport/ExchangeHandler.cpp b/ACL/src/Transport/ExchangeHandler.cpp index 2c98c620bc..e47ab179d6 100644 --- a/ACL/src/Transport/ExchangeHandler.cpp +++ b/ACL/src/Transport/ExchangeHandler.cpp @@ -32,7 +32,7 @@ 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. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -42,9 +42,9 @@ ExchangeHandler::ExchangeHandler( m_context{context}, m_authToken{authToken}, m_authHeader{AUTHORIZATION_HEADER + authToken} { - ACSDK_DEBUG5(LX(__func__).d("context", context.get()).sensitive("authToken", authToken)); + ACSDK_DEBUG5(LX("init").d("context", context.get()).sensitive("authToken", authToken)); if (m_authToken.empty()) { - ACSDK_ERROR(LX(__func__).m("emptyAuthToken")); + ACSDK_ERROR(LX("initError").m("emptyAuthToken")); } } diff --git a/ACL/src/Transport/HTTP2Transport.cpp b/ACL/src/Transport/HTTP2Transport.cpp index 8c86724742..5d50ad78e3 100644 --- a/ACL/src/Transport/HTTP2Transport.cpp +++ b/ACL/src/Transport/HTTP2Transport.cpp @@ -53,12 +53,12 @@ static const std::string TAG("HTTP2Transport"); #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /** - * Create a LogEntry using this file's TAG and the specified event string. With the first entry being the instance - * memory location. + * Create a LogEntry using this file's TAG and the specified event string. With the first entry being the instance + * memory location, and the second entry is a gateway URL. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ -#define LX_P(event) LX(event).p("this", this) +#define LX_P(event) LX(event).p("this", this).sensitive("gateway", m_avsGateway) /// 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 @@ -198,7 +198,7 @@ std::shared_ptr HTTP2Transport::create( Configuration configuration, std::shared_ptr metricRecorder, std::shared_ptr eventTracer) { - ACSDK_DEBUG5(LX(__func__) + ACSDK_DEBUG5(LX("create") .d("authDelegate", authDelegate.get()) .d("avsGateway", avsGateway) .d("http2Connection", http2Connection.get()) @@ -299,7 +299,7 @@ HTTP2Transport::HTTP2Transport( } void HTTP2Transport::addObserver(std::shared_ptr transportObserver) { - ACSDK_DEBUG7(LX_P(__func__).d("transportObserver", transportObserver.get())); + ACSDK_DEBUG7(LX_P("addObserver").d("transportObserver", transportObserver.get())); if (!transportObserver) { ACSDK_ERROR(LX_P("addObserverFailed").d("reason", "nullObserver")); @@ -311,7 +311,7 @@ void HTTP2Transport::addObserver(std::shared_ptr tra } void HTTP2Transport::removeObserver(std::shared_ptr transportObserver) { - ACSDK_DEBUG7(LX_P(__func__).d("transportObserver", transportObserver.get())); + ACSDK_DEBUG7(LX_P("removeObserver").d("transportObserver", transportObserver.get())); if (!transportObserver) { ACSDK_ERROR(LX_P("removeObserverFailed").d("reason", "nullObserver")); @@ -327,7 +327,7 @@ std::shared_ptr HTTP2Transport::getHTTP2Connection() { } bool HTTP2Transport::connect() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("connect")); if (!setState(State::AUTHORIZING, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST)) { ACSDK_ERROR(LX_P("connectFailed").d("reason", "setStateFailed")); @@ -340,7 +340,7 @@ bool HTTP2Transport::connect() { } void HTTP2Transport::disconnect() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("disconnect")); std::thread localThread; { @@ -362,13 +362,13 @@ bool HTTP2Transport::isConnected() { } void HTTP2Transport::onRequestEnqueued() { - ACSDK_DEBUG7(LX_P(__func__)); + ACSDK_DEBUG7(LX_P("onRequestEnqueued")); std::lock_guard lock(m_mutex); m_wakeEvent.notifyAll(); } void HTTP2Transport::onWakeConnectionRetry() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("onWakeConnectionRetry")); std::lock_guard lock(m_mutex); if (State::WAITING_TO_RETRY_CONNECTING != m_state) { @@ -381,7 +381,7 @@ void HTTP2Transport::onWakeConnectionRetry() { } void HTTP2Transport::onWakeVerifyConnectivity() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("onWakeVerifyConnectivity")); std::lock_guard lock(m_mutex); if (!m_pingHandler) { m_timeOfLastActivity = m_timeOfLastActivity.min(); @@ -390,7 +390,7 @@ void HTTP2Transport::onWakeVerifyConnectivity() { } void HTTP2Transport::sendMessage(std::shared_ptr request) { - ACSDK_DEBUG7(LX_P(__func__)); + ACSDK_DEBUG7(LX_P("sendMessage")); if (!request) { ACSDK_ERROR(LX_P("enqueueRequestFailed").d("reason", "nullRequest")); } @@ -425,7 +425,7 @@ void HTTP2Transport::sendMessage(std::shared_ptr request) { } void HTTP2Transport::onPostConnected() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("onPostConnected")); std::lock_guard lock(m_mutex); @@ -453,7 +453,7 @@ void HTTP2Transport::onPostConnected() { } void HTTP2Transport::onUnRecoverablePostConnectFailure() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("onUnRecoverablePostConnectFailure")); std::lock_guard lock(m_mutex); @@ -485,7 +485,7 @@ void HTTP2Transport::onUnRecoverablePostConnectFailure() { void HTTP2Transport::onAuthStateChange( avsCommon::sdkInterfaces::AuthObserverInterface::State newState, avsCommon::sdkInterfaces::AuthObserverInterface::Error error) { - ACSDK_INFO(LX_P(__func__).d("newState", newState).d("error", error)); + ACSDK_INFO(LX_P("onAuthStateChange").d("newState", newState).d("error", error)); std::lock_guard lock(m_mutex); @@ -517,7 +517,7 @@ void HTTP2Transport::onAuthStateChange( } void HTTP2Transport::doShutdown() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("doShutdown")); setState(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); disconnect(); m_authDelegate->removeAuthObserver(shared_from_this()); @@ -531,12 +531,12 @@ void HTTP2Transport::doShutdown() { } void HTTP2Transport::onDownchannelConnected() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("onDownchannelConnected")); setState(State::POST_CONNECTING, ConnectionStatusObserverInterface::ChangedReason::SUCCESS); } void HTTP2Transport::onDownchannelFinished() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("onDownchannelFinished")); std::lock_guard lock(m_mutex); @@ -568,17 +568,18 @@ void HTTP2Transport::onMessageRequestSent(const std::shared_ptrsetWaitingForSendAcknowledgement(); } m_countOfUnfinishedMessageHandlers++; - ACSDK_DEBUG7(LX_P(__func__).d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); + ACSDK_DEBUG7( + LX_P("onMessageRequestSent").d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); } void HTTP2Transport::onMessageRequestTimeout() { // If a message request times out, verify our connectivity to AVS. - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("onMessageRequestTimeout")); onWakeVerifyConnectivity(); } void HTTP2Transport::onMessageRequestAcknowledged(const std::shared_ptr& request) { - ACSDK_DEBUG7(LX_P(__func__)); + ACSDK_DEBUG7(LX_P("onMessageRequestAcknowledged")); std::lock_guard lock(m_mutex); if (request->getIsSerialized()) { m_sharedRequestQueue->clearWaitingForSendAcknowledgement(); @@ -589,12 +590,13 @@ void HTTP2Transport::onMessageRequestAcknowledged(const std::shared_ptr lock(m_mutex); --m_countOfUnfinishedMessageHandlers; - ACSDK_DEBUG7(LX_P(__func__).d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); + ACSDK_DEBUG7( + LX_P("onMessageRequestFinished").d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); m_wakeEvent.notifyAll(); } void HTTP2Transport::onPingRequestAcknowledged(bool success) { - ACSDK_DEBUG7(LX_P(__func__).d("success", success)); + ACSDK_DEBUG7(LX_P("onPingRequestAcknowledged").d("success", success)); std::lock_guard lock(m_mutex); m_pingHandler.reset(); if (!success) { @@ -605,7 +607,7 @@ void HTTP2Transport::onPingRequestAcknowledged(bool success) { } void HTTP2Transport::onPingTimeout() { - ACSDK_WARN(LX_P(__func__)); + ACSDK_WARN(LX_P("onPingTimeout")); std::lock_guard lock(m_mutex); m_pingHandler.reset(); setStateLocked(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::PING_TIMEDOUT); @@ -613,18 +615,18 @@ void HTTP2Transport::onPingTimeout() { } void HTTP2Transport::onActivity() { - ACSDK_DEBUG9(LX_P(__func__)); + ACSDK_DEBUG9(LX_P("onActivity")); std::lock_guard lock(m_mutex); m_timeOfLastActivity = std::chrono::steady_clock::now(); } void HTTP2Transport::onForbidden(const std::string& authToken) { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("onForbidden")); m_authDelegate->onAuthFailure(authToken); } std::shared_ptr HTTP2Transport::createAndSendRequest(const HTTP2RequestConfig& cfg) { - ACSDK_DEBUG7(LX_P(__func__).d("type", cfg.getRequestType()).sensitive("url", cfg.getUrl())); + ACSDK_DEBUG7(LX_P("createAndSendRequest").d("type", cfg.getRequestType()).sensitive("url", cfg.getUrl())); return m_http2Connection->createAndSendRequest(cfg); } @@ -633,13 +635,15 @@ std::string HTTP2Transport::getAVSGateway() { } void HTTP2Transport::onGoawayReceived() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("onGoawayReceived")); } void HTTP2Transport::mainLoop() { - ACSDK_DEBUG7(LX_P(__func__)); + ACSDK_DEBUG7(LX_P("mainLoop")); - PowerMonitor::getInstance()->assignThreadPowerResource(m_mainLoopPowerResource); + if (m_mainLoopPowerResource) { + PowerMonitor::getInstance()->assignThreadPowerResource(m_mainLoopPowerResource); + } FinallyGuard removePowerResource([this] { PowerMonitor::getInstance()->removeThreadPowerResource(); @@ -650,13 +654,6 @@ void HTTP2Transport::mainLoop() { m_http2Connection->addObserver(shared_from_this()); - m_postConnect = m_postConnectFactory->createPostConnect(); - if (!m_postConnect || !m_postConnect->doPostConnect(shared_from_this(), shared_from_this())) { - ACSDK_ERROR(LX_P("mainLoopFailed").d("reason", "createPostConnectFailed")); - std::lock_guard lock(m_mutex); - setStateLocked(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); - } - m_timeOfLastActivity = std::chrono::steady_clock::now(); State nextState = getState(); @@ -697,14 +694,14 @@ void HTTP2Transport::mainLoop() { } HTTP2Transport::State HTTP2Transport::handleInit() { - ACSDK_CRITICAL(LX_P(__func__).d("reason", "unexpectedState")); + ACSDK_CRITICAL(LX_P("handleInit").d("reason", "unexpectedState")); std::lock_guard lock(m_mutex); setStateLocked(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); return m_state; } HTTP2Transport::State HTTP2Transport::handleAuthorizing() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("handleAuthorizing")); m_authDelegate->addAuthObserver(shared_from_this()); @@ -712,7 +709,7 @@ HTTP2Transport::State HTTP2Transport::handleAuthorizing() { } HTTP2Transport::State HTTP2Transport::handleConnecting() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("handleConnecting")); auto authToken = m_authDelegate->getAuthToken(); @@ -740,7 +737,9 @@ HTTP2Transport::State HTTP2Transport::handleConnecting() { HTTP2Transport::State HTTP2Transport::handleWaitingToRetryConnecting() { auto timeout = TransportDefines::getRetryTimer().calculateTimeToRetry(m_connectRetryCount); - ACSDK_INFO(LX_P(__func__).d("connectRetryCount", m_connectRetryCount).d("timeout", timeout.count())); + ACSDK_INFO(LX_P("handleWaitingToRetryConnecting") + .d("connectRetryCount", m_connectRetryCount) + .d("timeout", timeout.count())); m_connectRetryCount++; auto wakeTime = std::chrono::steady_clock::now() + timeout; @@ -754,16 +753,30 @@ HTTP2Transport::State HTTP2Transport::handleWaitingToRetryConnecting() { } HTTP2Transport::State HTTP2Transport::handlePostConnecting() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("handlePostConnecting")); if (m_postConnected) { setState(State::CONNECTED, ConnectionStatusObserverInterface::ChangedReason::SUCCESS); return State::CONNECTED; + } else { + m_postConnect = m_postConnectFactory->createPostConnect(); + if (!m_postConnect) { + ACSDK_ERROR(LX_P("handlePostConnectingFailed").d("reason", "createPostConnectFailed")); + setState(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); + return State::SHUTDOWN; + } + + if (!m_postConnect->doPostConnect(shared_from_this(), shared_from_this())) { + ACSDK_ERROR(LX_P("handlePostConnectingFailed").d("reason", "doPostConnectFailed")); + setState(State::SHUTDOWN, ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR); + return State::SHUTDOWN; + } } + return sendMessagesAndPings(State::POST_CONNECTING, m_requestQueue); } HTTP2Transport::State HTTP2Transport::handleConnected() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("handleConnected")); if (m_postConnect) { m_postConnect.reset(); } @@ -772,7 +785,7 @@ HTTP2Transport::State HTTP2Transport::handleConnected() { } HTTP2Transport::State HTTP2Transport::handleServerSideDisconnect() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("handleServerSideDisconnect")); notifyObserversOnServerSideDisconnect(); submitDisconnectReasonMetric( m_metricRecorder, ConnectionStatusObserverInterface::ChangedReason::SERVER_SIDE_DISCONNECT); @@ -780,7 +793,7 @@ HTTP2Transport::State HTTP2Transport::handleServerSideDisconnect() { } HTTP2Transport::State HTTP2Transport::handleDisconnecting() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("handleDisconnecting")); std::unique_lock lock(m_mutex); m_wakeEvent.wait( @@ -790,7 +803,7 @@ HTTP2Transport::State HTTP2Transport::handleDisconnecting() { } HTTP2Transport::State HTTP2Transport::handleShutdown() { - ACSDK_INFO(LX_P(__func__)); + ACSDK_INFO(LX_P("handleShutdown")); { std::lock_guard lock(m_mutex); @@ -857,9 +870,8 @@ HTTP2Transport::State HTTP2Transport::monitorSharedQueueWhileWaiting( HTTP2Transport::State HTTP2Transport::sendMessagesAndPings( alexaClientSDK::acl::HTTP2Transport::State whileState, MessageRequestQueueInterface& requestQueue) { - ACSDK_DEBUG7(LX_P(__func__).d("whileState", whileState)); - std::unique_lock lock(m_mutex); + ACSDK_DEBUG5(LX_P("sendMessagesAndPings").d("whileState", whileState)); auto canSendMessage = [this, &requestQueue] { return ( @@ -917,8 +929,10 @@ HTTP2Transport::State HTTP2Transport::sendMessagesAndPings( if (!m_pingHandler) { lock.unlock(); + ACSDK_DEBUG5(LX_P("sendMessagesAndPings").m("GettingAuthToken")); auto authToken = m_authDelegate->getAuthToken(); if (!authToken.empty()) { + ACSDK_DEBUG5(LX_P("sendMessagesAndPings").m("CreatingPingHandler")); m_pingHandler = PingHandler::create(shared_from_this(), authToken, m_requestActivityPowerResource); } else { ACSDK_ERROR(LX_P("failedToCreatePingHandler").d("reason", "invalidAuth")); @@ -944,7 +958,8 @@ bool HTTP2Transport::setState(State newState, ConnectionStatusObserverInterface: } bool HTTP2Transport::setStateLocked(State newState, ConnectionStatusObserverInterface::ChangedReason changedReason) { - ACSDK_INFO(LX_P(__func__).d("currentState", m_state).d("newState", newState).d("changedReason", changedReason)); + ACSDK_INFO( + LX_P("setStateLocked").d("currentState", m_state).d("newState", newState).d("changedReason", changedReason)); if (newState == m_state) { ACSDK_DEBUG7(LX_P("alreadyInNewState")); @@ -1012,7 +1027,7 @@ bool HTTP2Transport::setStateLocked(State newState, ConnectionStatusObserverInte } void HTTP2Transport::notifyObserversOnConnected() { - ACSDK_DEBUG7(LX_P(__func__)); + ACSDK_DEBUG7(LX_P("notifyObserversOnConnected")); std::unique_lock lock{m_observerMutex}; auto observers = m_observers; @@ -1024,7 +1039,7 @@ void HTTP2Transport::notifyObserversOnConnected() { } void HTTP2Transport::notifyObserversOnDisconnect(ConnectionStatusObserverInterface::ChangedReason reason) { - ACSDK_DEBUG7(LX_P(__func__)); + ACSDK_DEBUG7(LX_P("notifyObserversOnDisconnect")); if (m_postConnect) { m_postConnect->onDisconnect(); @@ -1041,7 +1056,7 @@ void HTTP2Transport::notifyObserversOnDisconnect(ConnectionStatusObserverInterfa } void HTTP2Transport::notifyObserversOnServerSideDisconnect() { - ACSDK_DEBUG7(LX_P(__func__)); + ACSDK_DEBUG7(LX_P("notifyObserversOnServerSideDisconnect")); if (m_postConnect) { m_postConnect->onDisconnect(); diff --git a/ACL/src/Transport/HTTP2TransportFactory.cpp b/ACL/src/Transport/HTTP2TransportFactory.cpp index 7c95cd4904..805536fbdb 100644 --- a/ACL/src/Transport/HTTP2TransportFactory.cpp +++ b/ACL/src/Transport/HTTP2TransportFactory.cpp @@ -32,7 +32,7 @@ static const std::string TAG("HTTP2TransportFactory"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/ACL/src/Transport/MessageRequestHandler.cpp b/ACL/src/Transport/MessageRequestHandler.cpp index 2051bbd975..4a036005e4 100644 --- a/ACL/src/Transport/MessageRequestHandler.cpp +++ b/ACL/src/Transport/MessageRequestHandler.cpp @@ -98,7 +98,7 @@ static const std::string HTTP_KEY_VALUE_SEPARATOR = ": "; /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -232,7 +232,7 @@ std::shared_ptr MessageRequestHandler::create( std::shared_ptr metricRecorder, std::shared_ptr eventTracer, const std::shared_ptr& powerResource) { - ACSDK_DEBUG7(LX(__func__).d("context", context.get()).d("messageRequest", messageRequest.get())); + ACSDK_DEBUG7(LX("create").d("context", context.get()).d("messageRequest", messageRequest.get())); if (!context) { ACSDK_CRITICAL(LX("MessageRequestHandlerCreateFailed").d("reason", "nullHttp2Transport")); @@ -303,7 +303,7 @@ MessageRequestHandler::MessageRequestHandler( m_resultStatus{MessageRequestObserverInterface::Status::PENDING}, m_streamBytesRead{0}, m_recordedStreamMetric{false} { - ACSDK_DEBUG7(LX(__func__).d("context", context.get()).d("messageRequest", messageRequest.get())); + ACSDK_DEBUG7(LX("init").d("context", context.get()).d("messageRequest", messageRequest.get())); if (m_powerResource) { m_powerResource->acquire(); @@ -311,7 +311,7 @@ MessageRequestHandler::MessageRequestHandler( } void MessageRequestHandler::reportMessageRequestAcknowledged() { - ACSDK_DEBUG7(LX(__func__)); + ACSDK_DEBUG7(LX("reportMessageRequestAcknowledged")); if (!m_wasMessageRequestAcknowledgeReported) { m_wasMessageRequestAcknowledgeReported = true; m_context->onMessageRequestAcknowledged(m_messageRequest); @@ -319,7 +319,7 @@ void MessageRequestHandler::reportMessageRequestAcknowledged() { } void MessageRequestHandler::reportMessageRequestFinished() { - ACSDK_DEBUG7(LX(__func__)); + ACSDK_DEBUG7(LX("reportMessageRequestFinished")); if (!m_wasMessageRequestFinishedReported) { m_wasMessageRequestFinishedReported = true; m_context->onMessageRequestFinished(); @@ -327,7 +327,7 @@ void MessageRequestHandler::reportMessageRequestFinished() { } std::vector MessageRequestHandler::getRequestHeaderLines() { - ACSDK_DEBUG9(LX(__func__)); + ACSDK_DEBUG9(LX("getRequestHeaderLines")); m_context->onActivity(); @@ -341,7 +341,7 @@ std::vector MessageRequestHandler::getRequestHeaderLines() { } HTTP2GetMimeHeadersResult MessageRequestHandler::getMimePartHeaderLines() { - ACSDK_DEBUG9(LX(__func__)); + ACSDK_DEBUG9(LX("getMimePartHeaderLines")); m_context->onActivity(); @@ -363,7 +363,7 @@ HTTP2GetMimeHeadersResult MessageRequestHandler::getMimePartHeaderLines() { } HTTP2SendDataResult MessageRequestHandler::onSendMimePartData(char* bytes, size_t size) { - ACSDK_DEBUG9(LX(__func__).d("size", size)); + ACSDK_DEBUG9(LX("onSendMimePartData").d("size", size)); m_context->onActivity(); @@ -426,7 +426,7 @@ void MessageRequestHandler::onActivity() { } bool MessageRequestHandler::onReceiveResponseCode(long responseCode) { - ACSDK_DEBUG7(LX(__func__).d("responseCode", responseCode)); + ACSDK_DEBUG7(LX("onReceiveResponseCode").d("responseCode", responseCode)); reportMessageRequestAcknowledged(); @@ -463,7 +463,7 @@ bool MessageRequestHandler::onReceiveResponseCode(long responseCode) { } void MessageRequestHandler::onResponseFinished(HTTP2ResponseFinishedStatus status, const std::string& nonMimeBody) { - ACSDK_DEBUG7(LX(__func__).d("status", status).d("responseCode", m_responseCode)); + ACSDK_DEBUG7(LX("onResponseFinished").d("status", status).d("responseCode", m_responseCode)); if (HTTP2ResponseFinishedStatus::TIMEOUT == status) { m_context->onMessageRequestTimeout(); diff --git a/ACL/src/Transport/MessageRequestQueue.cpp b/ACL/src/Transport/MessageRequestQueue.cpp index ccaf79f338..64bc623a9a 100644 --- a/ACL/src/Transport/MessageRequestQueue.cpp +++ b/ACL/src/Transport/MessageRequestQueue.cpp @@ -26,12 +26,10 @@ using namespace avsCommon::avs; /// String to identify log entries originating from this file. static const std::string TAG("MessageRequestQueue"); -static const std::string EMPTY_QUEUE_NAME = ""; - /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/ACL/src/Transport/MessageRouter.cpp b/ACL/src/Transport/MessageRouter.cpp index 1786c20a00..752ad20af9 100644 --- a/ACL/src/Transport/MessageRouter.cpp +++ b/ACL/src/Transport/MessageRouter.cpp @@ -41,7 +41,7 @@ const std::chrono::milliseconds MessageRouter::DEFAULT_SERVER_SIDE_DISCONNECT_GR /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -99,11 +99,11 @@ MessageRouterInterface::ConnectionStatus MessageRouter::getConnectionStatus() { } void MessageRouter::enable() { - ACSDK_INFO(LX(__func__)); + ACSDK_INFO(LX("enable")); std::lock_guard lock{m_connectionMutex}; if (m_isEnabled) { - ACSDK_INFO(LX(__func__).m("already enabled")); + ACSDK_INFO(LX("enableFailed").m("already enabled")); return; } @@ -137,7 +137,7 @@ void MessageRouter::doShutdown() { } void MessageRouter::disable() { - ACSDK_INFO(LX(__func__)); + ACSDK_INFO(LX("disable")); std::unique_lock lock{m_connectionMutex}; m_isEnabled = false; disconnectAllTransportsLocked(lock, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); @@ -166,7 +166,7 @@ void MessageRouter::sendMessage(std::shared_ptr request) { } void MessageRouter::setAVSGateway(const std::string& avsGateway) { - ACSDK_INFO(LX(__func__).d("avsGateway", avsGateway)); + ACSDK_INFO(LX("setAVSGateway").d("avsGateway", avsGateway)); std::unique_lock lock{m_connectionMutex}; if (avsGateway != m_avsGateway) { m_avsGateway = avsGateway; @@ -187,26 +187,26 @@ std::string MessageRouter::getAVSGateway() { } void MessageRouter::onWakeConnectionRetry() { - ACSDK_INFO(LX(__func__)); + ACSDK_INFO(LX("onWakeConnectionRetry")); std::lock_guard lock{m_connectionMutex}; if (m_isEnabled && m_activeTransport) { - ACSDK_INFO(LX(__func__).p("m_activeTransport", m_activeTransport)); + ACSDK_INFO(LX("onWakeConnectionRetry").p("m_activeTransport", m_activeTransport)); m_activeTransport->onWakeConnectionRetry(); } } void MessageRouter::onWakeVerifyConnectivity() { - ACSDK_INFO(LX(__func__)); + ACSDK_INFO(LX("onWakeVerifyConnectivity")); std::lock_guard lock{m_connectionMutex}; if (m_isEnabled && m_activeTransport) { - ACSDK_INFO(LX(__func__).p("m_activeTransport", m_activeTransport)); + ACSDK_INFO(LX("onWakeVerifyConnectivity").p("m_activeTransport", m_activeTransport)); m_activeTransport->onWakeVerifyConnectivity(); } } void MessageRouter::onConnected(std::shared_ptr transport) { std::unique_lock lock{m_connectionMutex}; - ACSDK_INFO(LX(__func__).p("transport", transport).p("m_activeTransport", m_activeTransport)); + ACSDK_INFO(LX("onConnected").p("transport", transport).p("m_activeTransport", m_activeTransport)); /* * Transport shutdown might be asynchronous,so the following scenarios are valid, @@ -231,7 +231,7 @@ void MessageRouter::onDisconnected( std::shared_ptr transport, ConnectionStatusObserverInterface::ChangedReason reason) { std::lock_guard lock{m_connectionMutex}; - ACSDK_INFO(LX(__func__) + ACSDK_INFO(LX("onDisconnected") .p("transport", transport) .p("m_activeTransport", m_activeTransport) .d(KEY_SIZEOF_TRANSPORTS, m_transports.size()) @@ -269,8 +269,10 @@ void MessageRouter::onDisconnected( void MessageRouter::onServerSideDisconnect(std::shared_ptr transport) { std::unique_lock lock{m_connectionMutex}; - ACSDK_INFO( - LX(__func__).d("m_isEnabled", m_isEnabled).p("transport", transport).p("m_activeTransport", m_activeTransport)); + ACSDK_INFO(LX("onServerSideDisconnect") + .d("m_isEnabled", m_isEnabled) + .p("transport", transport) + .p("m_activeTransport", m_activeTransport)); if (m_isEnabled && transport == m_activeTransport) { setConnectionStatusLocked( ConnectionStatusObserverInterface::Status::PENDING, @@ -295,7 +297,7 @@ void MessageRouter::setObserver(std::shared_ptr void MessageRouter::setConnectionStatusLocked( const ConnectionStatusObserverInterface::Status status, const ConnectionStatusObserverInterface::ChangedReason reason) { - ACSDK_INFO(LX(__func__).d("status", status).d("reason", reason)); + ACSDK_INFO(LX("setConnectionStatusLocked").d("status", status).d("reason", reason)); if (status != m_connectionStatus) { m_connectionStatus = status; m_connectionReason = reason; @@ -364,7 +366,8 @@ void MessageRouter::notifyObserverOnReceive(const std::string& contextId, const void MessageRouter::createActiveTransportLocked() { auto transport = m_transportFactory->createTransport( m_authDelegate, m_attachmentManager, m_avsGateway, shared_from_this(), shared_from_this(), m_requestQueue); - ACSDK_INFO(LX(__func__).p("transport", transport).d(KEY_SIZEOF_TRANSPORTS, m_transports.size())); + ACSDK_INFO( + LX("createActiveTransportLocked").p("transport", transport).d(KEY_SIZEOF_TRANSPORTS, m_transports.size())); if (transport && transport->connect()) { m_transports.push_back(transport); m_activeTransport = transport; @@ -385,7 +388,7 @@ void MessageRouter::createActiveTransportLocked() { void MessageRouter::disconnectAllTransportsLocked( std::unique_lock& lock, const ConnectionStatusObserverInterface::ChangedReason reason) { - ACSDK_INFO(LX(__func__) + ACSDK_INFO(LX("disconnectAllTransportsLocked") .d("reason", reason) .d(KEY_SIZEOF_TRANSPORTS, m_transports.size()) .p("m_activeTransport", m_activeTransport)); @@ -400,7 +403,7 @@ void MessageRouter::disconnectAllTransportsLocked( lock.unlock(); for (auto transport : movedTransports) { - ACSDK_INFO(LX(__func__).p("transport", transport)); + ACSDK_INFO(LX("disconnectAllTransportsLocked").p("transport", transport)); transport->shutdown(); } lock.lock(); diff --git a/ACL/src/Transport/MimeResponseSink.cpp b/ACL/src/Transport/MimeResponseSink.cpp index 92531a6bd2..a9fac90ff7 100644 --- a/ACL/src/Transport/MimeResponseSink.cpp +++ b/ACL/src/Transport/MimeResponseSink.cpp @@ -52,7 +52,7 @@ 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. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -88,11 +88,11 @@ MimeResponseSink::MimeResponseSink( m_messageConsumer{messageConsumer}, m_attachmentManager{attachmentManager}, m_attachmentContextId{std::move(attachmentContextId)} { - ACSDK_DEBUG9(LX(__func__).d("handler", handler.get())); + ACSDK_DEBUG9(LX("init").d("handler", handler.get())); } bool MimeResponseSink::onReceiveResponseCode(long responseCode) { - ACSDK_DEBUG9(LX(__func__).d("responseCode", responseCode)); + ACSDK_DEBUG9(LX("onReceiveResponseCode").d("responseCode", responseCode)); if (m_handler) { m_handler->onActivity(); @@ -102,7 +102,7 @@ bool MimeResponseSink::onReceiveResponseCode(long responseCode) { } bool MimeResponseSink::onReceiveHeaderLine(const std::string& line) { - ACSDK_DEBUG9(LX(__func__).d("line", line)); + ACSDK_DEBUG9(LX("onReceiveHeaderLine").d("line", line)); if (m_handler) { m_handler->onActivity(); @@ -118,7 +118,7 @@ bool MimeResponseSink::onReceiveHeaderLine(const std::string& line) { } bool MimeResponseSink::onBeginMimePart(const std::multimap& headers) { - ACSDK_DEBUG9(LX(__func__)); + ACSDK_DEBUG9(LX("onBeginMimePart")); if (m_handler) { m_handler->onActivity(); @@ -158,7 +158,7 @@ bool MimeResponseSink::onBeginMimePart(const std::multimaponActivity(); @@ -177,7 +177,7 @@ HTTP2ReceiveDataStatus MimeResponseSink::onReceiveMimeData(const char* bytes, si } bool MimeResponseSink::onEndMimePart() { - ACSDK_DEBUG9(LX(__func__)); + ACSDK_DEBUG9(LX("onEndMimePart")); if (m_handler) { m_handler->onActivity(); @@ -208,7 +208,7 @@ bool MimeResponseSink::onEndMimePart() { } HTTP2ReceiveDataStatus MimeResponseSink::onReceiveNonMimeData(const char* bytes, size_t size) { - ACSDK_DEBUG9(LX(__func__).d("size", size)); + ACSDK_DEBUG9(LX("onReceiveNonMimeData").d("size", size)); if (m_handler) { m_handler->onActivity(); @@ -228,7 +228,7 @@ HTTP2ReceiveDataStatus MimeResponseSink::onReceiveNonMimeData(const char* bytes, } void MimeResponseSink::onResponseFinished(HTTP2ResponseFinishedStatus status) { - ACSDK_DEBUG9(LX(__func__).d("status", status)); + ACSDK_DEBUG9(LX("onResponseFinished").d("status", status)); if (m_handler) { m_handler->onResponseFinished(status, m_nonMimeBody); diff --git a/ACL/src/Transport/PingHandler.cpp b/ACL/src/Transport/PingHandler.cpp index 0a67eae733..f6bf8260a4 100644 --- a/ACL/src/Transport/PingHandler.cpp +++ b/ACL/src/Transport/PingHandler.cpp @@ -45,7 +45,7 @@ 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. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -53,7 +53,7 @@ std::shared_ptr PingHandler::create( std::shared_ptr context, const std::string& authToken, const std::shared_ptr& powerResource) { - ACSDK_DEBUG5(LX(__func__).d("context", context.get())); + ACSDK_DEBUG5(LX("create").d("context", context.get())); if (!context) { ACSDK_CRITICAL(LX("createFailed").d("reason", "nullContext")); @@ -92,7 +92,7 @@ PingHandler::PingHandler( m_wasPingAcknowledgedReported{false}, m_responseCode{0}, m_powerResource{powerResource} { - ACSDK_DEBUG5(LX(__func__).d("context", context.get())); + ACSDK_DEBUG5(LX("init").d("context", context.get())); if (m_powerResource) { m_powerResource->acquire(); @@ -100,13 +100,14 @@ PingHandler::PingHandler( } PingHandler::~PingHandler() { + ACSDK_DEBUG5(LX("destroy")); if (m_powerResource) { m_powerResource->release(); } } void PingHandler::reportPingAcknowledged() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("reportPingAcknowledged")); if (!m_wasPingAcknowledgedReported) { m_wasPingAcknowledgedReported = true; m_context->onPingRequestAcknowledged( @@ -115,17 +116,17 @@ void PingHandler::reportPingAcknowledged() { } std::vector PingHandler::getRequestHeaderLines() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getRequestHeaderLines")); return {m_authHeader}; } HTTP2SendDataResult PingHandler::onSendData(char* bytes, size_t size) { - ACSDK_DEBUG5(LX(__func__).d("size", size)); + ACSDK_DEBUG5(LX("onSendData").d("size", size)); return HTTP2SendDataResult::COMPLETE; } bool PingHandler::onReceiveResponseCode(long responseCode) { - ACSDK_DEBUG5(LX(__func__).d("responseCode", responseCode)); + ACSDK_DEBUG5(LX("onReceiveResponseCode").d("responseCode", responseCode)); if (HTTPResponseCode::CLIENT_ERROR_FORBIDDEN == intToHTTPResponseCode(responseCode)) { m_context->onForbidden(m_authToken); @@ -138,19 +139,19 @@ bool PingHandler::onReceiveResponseCode(long responseCode) { } bool PingHandler::onReceiveHeaderLine(const std::string& line) { - ACSDK_DEBUG5(LX(__func__).d("line", line)); + ACSDK_DEBUG5(LX("onReceiveHeaderLine").d("line", line)); m_context->onActivity(); return true; } HTTP2ReceiveDataStatus PingHandler::onReceiveData(const char* bytes, size_t size) { - ACSDK_DEBUG5(LX(__func__).d("size", size)); + ACSDK_DEBUG5(LX("onReceiveData").d("size", size)); m_context->onActivity(); return HTTP2ReceiveDataStatus::SUCCESS; } void PingHandler::onResponseFinished(HTTP2ResponseFinishedStatus status) { - ACSDK_DEBUG5(LX(__func__).d("status", status)); + ACSDK_DEBUG5(LX("onResponseFinished").d("status", status)); switch (status) { case HTTP2ResponseFinishedStatus::COMPLETE: reportPingAcknowledged(); diff --git a/ACL/src/Transport/PostConnectSequencer.cpp b/ACL/src/Transport/PostConnectSequencer.cpp index 18f5b6a7c3..aed17e2262 100644 --- a/ACL/src/Transport/PostConnectSequencer.cpp +++ b/ACL/src/Transport/PostConnectSequencer.cpp @@ -50,6 +50,8 @@ std::shared_ptr PostConnectSequencer::create( PostConnectSequencer::PostConnectSequencer(const PostConnectOperationsSet& postConnectOperations) : m_isStopping{false}, m_postConnectOperations{postConnectOperations} { + ACSDK_DEBUG5(LX("init")); + m_mainLoopPowerResource = PowerMonitor::getInstance()->createLocalPowerResource(TAG + "_mainLoop"); if (m_mainLoopPowerResource) { @@ -58,14 +60,14 @@ PostConnectSequencer::PostConnectSequencer(const PostConnectOperationsSet& postC } PostConnectSequencer::~PostConnectSequencer() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("destroy")); stop(); } bool PostConnectSequencer::doPostConnect( std::shared_ptr postConnectSender, std::shared_ptr postConnectObserver) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("doPostConnect")); if (!postConnectSender) { ACSDK_ERROR(LX("doPostConnectFailed").d("reason", "nullPostConnectSender")); @@ -92,7 +94,7 @@ bool PostConnectSequencer::doPostConnect( void PostConnectSequencer::mainLoop( std::shared_ptr postConnectSender, std::shared_ptr postConnectObserver) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("mainLoop")); PowerMonitor::getInstance()->assignThreadPowerResource(m_mainLoopPowerResource); @@ -118,7 +120,7 @@ void PostConnectSequencer::mainLoop( /// Set the current post connect operation. std::lock_guard lock{m_mutex}; if (m_isStopping) { - ACSDK_DEBUG5(LX(__func__).m("stop called, exiting mainloop")); + ACSDK_DEBUG5(LX("mainLoop").m("stop called, exiting mainloop")); return; } m_currentPostConnectOperation = postConnectOperation; @@ -130,7 +132,7 @@ void PostConnectSequencer::mainLoop( postConnectObserver->onUnRecoverablePostConnectFailure(); } resetCurrentOperation(); - ACSDK_ERROR(LX(__func__).m("performOperation failed, exiting mainloop")); + ACSDK_ERROR(LX("mainLoop").m("performOperation failed, exiting mainloop")); return; } } @@ -146,7 +148,7 @@ void PostConnectSequencer::mainLoop( } void PostConnectSequencer::onDisconnect() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("onDisconnect")); stop(); } @@ -161,7 +163,7 @@ bool PostConnectSequencer::isStopping() { } void PostConnectSequencer::stop() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("stop")); { std::lock_guard lock{m_mutex}; if (m_isStopping) { diff --git a/ACL/src/Transport/PostConnectSequencerFactory.cpp b/ACL/src/Transport/PostConnectSequencerFactory.cpp index 0a804eaec1..b28d63a251 100644 --- a/ACL/src/Transport/PostConnectSequencerFactory.cpp +++ b/ACL/src/Transport/PostConnectSequencerFactory.cpp @@ -32,7 +32,7 @@ static const std::string TAG("PostConnectSequencerFactory"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp b/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp index a8de4b576b..701147990a 100644 --- a/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp +++ b/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp @@ -29,7 +29,7 @@ static const std::string TAG("SynchronizedMessageRequestQueue"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/ACL/test/CMakeLists.txt b/ACL/test/CMakeLists.txt index dd78faec1b..92db443fa0 100644 --- a/ACL/test/CMakeLists.txt +++ b/ACL/test/CMakeLists.txt @@ -1,6 +1,6 @@ add_subdirectory("Transport") -set(LIBRARIES ACL ${CMAKE_THREAD_LIBS_INIT} ACLTransportCommonTestLib SDKInterfacesTests) +set(LIBRARIES ACL ${CMAKE_THREAD_LIBS_INIT} ACLTransportCommonTestLib SDKInterfacesTests UtilsCommonTestLib) set(INCLUDE_PATH "${AVSCommon_INCLUDE_DIRS}" "${ACL_SOURCE_DIR}/include" diff --git a/ACL/test/Transport/Common/CMakeLists.txt b/ACL/test/Transport/Common/CMakeLists.txt index 8342b2d0de..ab5c96ab7b 100644 --- a/ACL/test/Transport/Common/CMakeLists.txt +++ b/ACL/test/Transport/Common/CMakeLists.txt @@ -11,6 +11,5 @@ if (BUILD_TESTING) "${ACL_SOURCE_DIR}/test/Transport") target_link_libraries(ACLTransportCommonTestLib AVSCommon - gtest_main gmock_main) endif() diff --git a/ACL/test/Transport/HTTP2TransportTest.cpp b/ACL/test/Transport/HTTP2TransportTest.cpp index 30598aadcc..f8be3dcc6c 100644 --- a/ACL/test/Transport/HTTP2TransportTest.cpp +++ b/ACL/test/Transport/HTTP2TransportTest.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "MockAuthDelegate.h" @@ -56,6 +57,7 @@ using namespace avsCommon::utils; using namespace avsCommon::utils::http; using namespace avsCommon::utils::http2; using namespace avsCommon::utils::http2::test; +using namespace avsCommon::utils::logger; using namespace avsCommon::utils::metrics::test; using namespace avsCommon::utils::observer::test; using namespace ::testing; @@ -162,17 +164,6 @@ class HTTP2TransportTest : public Test { 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. @@ -184,6 +175,26 @@ class HTTP2TransportTest : public Test { */ void authorizeAndConnect(); + /** + * Setup expectations that @c HTTP2Transport gets connected. + */ + void expectConnectedNotification(); + + /** + * Setup authentication expectations. + */ + void expectAuthentication(); + + /** + * Setup expectations that @c HTTP2Transport goes through post-connect. + */ + void expectOnPostConnect(); + + /** + * Setup expectations that @c HTTP2Transport enters post connect. + */ + void expectPostConnectStarted(); + /// The HTTP2Transport instance to be tested. std::shared_ptr m_http2Transport; @@ -264,51 +275,55 @@ 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 - +void HTTP2TransportTest::expectAuthentication() { // 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; +void HTTP2TransportTest::expectOnPostConnect() { + // Handle PostConnectFactoryInterface::createPostConnect() when called. + EXPECT_CALL(*m_mockPostConnectFactory, createPostConnect()).WillOnce(InvokeWithoutArgs([this] { + m_createPostConnectCalled.setValue(); + return m_mockPostConnect; + })); - // 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(_, _)) + .WillOnce(Invoke([this]( + std::shared_ptr postConnectSender, + std::shared_ptr postConnectObserver) { + m_doPostConnected.setValue(std::make_pair(postConnectSender, postConnectObserver)); + postConnectObserver->onPostConnected(); + return true; })); +} - // Handle PostConnectInterface::doPostConnect() when called. - EXPECT_CALL(*m_mockPostConnect, doPostConnect(_, _)) - .InSequence(s2) - .WillOnce(Invoke([this, sendOnPostConnected]( - std::shared_ptr postConnectSender, - std::shared_ptr postConnectObserver) { - m_doPostConnected.setValue(std::make_pair(postConnectSender, postConnectObserver)); - if (sendOnPostConnected) { - postConnectObserver->onPostConnected(); - } - return true; - })); - } +void HTTP2TransportTest::expectPostConnectStarted() { + // 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(_, _)) + .WillOnce(Invoke([this]( + std::shared_ptr postConnectSender, + std::shared_ptr postConnectObserver) { + m_doPostConnected.setValue(std::make_pair(postConnectSender, postConnectObserver)); + 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::expectConnectedNotification() { + // Handle TransportObserverInterface::onConnected() when called. + EXPECT_CALL(*m_mockTransportObserver, onConnected(_)) + .WillOnce(Invoke([this](std::shared_ptr transport) { m_transportConnected.setValue(); }) - ); - } + ); } void HTTP2TransportTest::sendAuthStateRefreshed() { @@ -328,7 +343,12 @@ void HTTP2TransportTest::sendAuthStateRefreshed() { } void HTTP2TransportTest::authorizeAndConnect() { - setupHandlers(true, true); + { + InSequence sequence; + expectAuthentication(); + expectOnPostConnect(); + expectConnectedNotification(); + } // Call connect(). m_http2Transport->connect(); @@ -355,7 +375,7 @@ TEST_F(HTTP2TransportTest, testSlow_emptyAuthToken) { // Send an empty Auth token. m_mockAuthDelegate->setAuthToken(""); - setupHandlers(false, false); + expectAuthentication(); m_http2Transport->connect(); @@ -375,7 +395,7 @@ TEST_F(HTTP2TransportTest, testSlow_emptyAuthToken) { * Test waiting for AuthDelegateInterface. */ TEST_F(HTTP2TransportTest, testSlow_waitAuthDelegateInterface) { - setupHandlers(false, false); + expectAuthentication(); m_http2Transport->connect(); @@ -397,7 +417,7 @@ TEST_F(HTTP2TransportTest, testSlow_waitAuthDelegateInterface) { * Test verifying the proper inclusion of bearer token in requests. */ TEST_F(HTTP2TransportTest, test_bearerTokenInRequest) { - setupHandlers(false, false); + expectAuthentication(); m_mockHttp2Connection->setWaitRequestHeader(HTTP_AUTHORIZATION_HEADER_BEARER); @@ -413,7 +433,11 @@ TEST_F(HTTP2TransportTest, test_bearerTokenInRequest) { * Test creation and triggering of post-connect object. */ TEST_F(HTTP2TransportTest, test_triggerPostConnectObject) { - setupHandlers(false, false); + { + InSequence sequence; + expectAuthentication(); + expectPostConnectStarted(); + } // Don't expect TransportObserverInterface::onConnected() will be called. EXPECT_CALL(*m_mockTransportObserver, onConnected(_)).Times(0); @@ -439,7 +463,12 @@ TEST_F(HTTP2TransportTest, test_triggerPostConnectObject) { * Test delay of connection status until post-connect object created / notifies success. */ TEST_F(HTTP2TransportTest, test_connectionStatusOnPostConnect) { - setupHandlers(true, true); + { + InSequence sequence; + expectAuthentication(); + expectOnPostConnect(); + expectConnectedNotification(); + } // Call connect(). m_http2Transport->connect(); @@ -463,7 +492,7 @@ TEST_F(HTTP2TransportTest, test_connectionStatusOnPostConnect) { * Test retry upon failed downchannel connection. */ TEST_F(HTTP2TransportTest, testSlow_retryOnDownchannelConnectionFailure) { - setupHandlers(false, false); + expectAuthentication(); EXPECT_CALL(*m_mockTransportObserver, onConnected(_)).Times(0); @@ -486,13 +515,20 @@ TEST_F(HTTP2TransportTest, testSlow_retryOnDownchannelConnectionFailure) { * Test sending of MessageRequest content. */ TEST_F(HTTP2TransportTest, test_messageRequestContent) { - setupHandlers(false, false); + { + InSequence sequence; + expectAuthentication(); + expectPostConnectStarted(); + } // Call connect(). + TestTrace trace; m_http2Transport->connect(); + trace.log("connect"); // Deliver a 'REFRESHED' status to observers of AuthDelegateInterface. sendAuthStateRefreshed(); + trace.log("authRefreshed"); ASSERT_TRUE(m_mockHttp2Connection->respondToDownchannelRequests( static_cast(HTTPResponseCode::SUCCESS_OK), false, RESPONSE_TIMEOUT)); @@ -506,6 +542,7 @@ TEST_F(HTTP2TransportTest, test_messageRequestContent) { // 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. @@ -529,7 +566,11 @@ TEST_F(HTTP2TransportTest, test_messageRequestWithAttachment) { avsCommon::avs::attachment::AttachmentUtils::createAttachmentReader(attachment); ASSERT_NE(attachmentReader, nullptr); - setupHandlers(false, false); + { + InSequence sequence; + expectAuthentication(); + expectPostConnectStarted(); + } // Call connect(). m_http2Transport->connect(); @@ -570,7 +611,11 @@ TEST_F(HTTP2TransportTest, test_messageRequestWithAttachment) { * Test pause of sending message when attachment buffer (SDS) empty but not closed. */ TEST_F(HTTP2TransportTest, test_pauseSendWhenSDSEmpty) { - setupHandlers(false, false); + { + InSequence sequence; + expectAuthentication(); + expectPostConnectStarted(); + } // Call connect(). m_http2Transport->connect(); @@ -1292,20 +1337,24 @@ TEST_F(HTTP2TransportTest, testSlow_avsStreamsLimit) { * ChangeReason as UNRECOVERABLE_ERROR */ TEST_F(HTTP2TransportTest, test_onPostConnectFailureInitiatesShutdownAndNotifiesObservers) { + TestTrace trace; InSequence dummy; + expectAuthentication(); // Handle PostConnectFactoryInterface::createPostConnect() when called. - EXPECT_CALL(*m_mockPostConnectFactory, createPostConnect()).WillOnce(InvokeWithoutArgs([this] { + EXPECT_CALL(*m_mockPostConnectFactory, createPostConnect()).WillOnce(InvokeWithoutArgs([this, &trace] { + trace.log("createPostConnect"); m_createPostConnectCalled.setValue(); return m_mockPostConnect; })); // Handle PostConnectInterface::doPostConnect() when called. EXPECT_CALL(*m_mockPostConnect, doPostConnect(_, _)) - .WillOnce(Invoke([this]( + .WillOnce(Invoke([this, &trace]( std::shared_ptr postConnectSender, std::shared_ptr postConnectObserver) { m_doPostConnected.setValue(std::make_pair(postConnectSender, postConnectObserver)); + trace.log("doPostConnect"); postConnectObserver->onUnRecoverablePostConnectFailure(); return true; })); @@ -1322,6 +1371,16 @@ TEST_F(HTTP2TransportTest, test_onPostConnectFailureInitiatesShutdownAndNotifies })); m_http2Transport->connect(); + trace.log("connect"); + + // Deliver a 'REFRESHED' status to observers of AuthDelegateInterface. + sendAuthStateRefreshed(); + trace.log("sendAuthStateRefreshed"); + + // The Mock HTTP2Request replies to any downchannel request with 200. + ASSERT_TRUE(m_mockHttp2Connection->respondToDownchannelRequests( + static_cast(HTTPResponseCode::SUCCESS_OK), false, RESPONSE_TIMEOUT)); + trace.log("triggerConnectionAck"); ASSERT_TRUE(m_doPostConnected.waitFor(RESPONSE_TIMEOUT)); ASSERT_TRUE(gotOnDisconnected.waitFor(RESPONSE_TIMEOUT)); diff --git a/ADSL/src/CMakeLists.txt b/ADSL/src/CMakeLists.txt index edd2257ed9..6604e59ac1 100644 --- a/ADSL/src/CMakeLists.txt +++ b/ADSL/src/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) find_package(Threads ${THREADS_PACKAGE_CONFIG}) add_definitions("-DACSDK_LOG_MODULE=adsl") -add_library(ADSL SHARED +add_library(ADSL ADSLComponent.cpp DirectiveProcessor.cpp DirectiveRouter.cpp diff --git a/ADSL/test/common/CMakeLists.txt b/ADSL/test/common/CMakeLists.txt index e6f1802ab4..fd4710d727 100644 --- a/ADSL/test/common/CMakeLists.txt +++ b/ADSL/test/common/CMakeLists.txt @@ -10,6 +10,5 @@ if (BUILD_TESTING) target_link_libraries(ADSLTestCommon ADSL AVSCommon - gtest_main gmock_main) endif() diff --git a/AFML/src/CMakeLists.txt b/AFML/src/CMakeLists.txt index 916797dbe0..5725ef02fa 100644 --- a/AFML/src/CMakeLists.txt +++ b/AFML/src/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(AFML SHARED +add_library(AFML AudioActivityTracker.cpp Channel.cpp FocusManagementComponent.cpp diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifier.h b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifier.h index 744dd24b9e..6e2a526177 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifier.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifier.h @@ -16,7 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CAPABILITYCHANGENOTIFIER_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CAPABILITYCHANGENOTIFIER_H_ -#include +#include #include namespace alexaClientSDK { diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifierInterface.h b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifierInterface.h index 71d82854a0..3af5c99e69 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifierInterface.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifierInterface.h @@ -16,7 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CAPABILITYCHANGENOTIFIERINTERFACE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CAPABILITYCHANGENOTIFIERINTERFACE_H_ -#include +#include #include namespace alexaClientSDK { diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h b/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h index 1076597215..f0927adde4 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/DialogUXStateAggregator.h @@ -142,6 +142,16 @@ class DialogUXStateAggregator void onRequestProcessingCompleted() override; /// @} + /// @name ConnectionStatusObserverInterface Functions + /// @{ + void onConnectionStatusChanged( + const avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::Status status, + const avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override; + + void onConnectionStatusChanged(const Status status, const std::vector& engineStatuses) + override; + /// @} + private: /** * Notifies all observers of the current state. This should only be used within the internal executor. @@ -183,10 +193,6 @@ class DialogUXStateAggregator */ void tryEnterIdleStateOnTimer(); - void onConnectionStatusChanged( - const avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::Status status, - const avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override; - /** * Called internally when some activity starts: Speech is going to be played or voice recognition is about to start. */ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/EventBuilder.h b/AVSCommon/AVS/include/AVSCommon/AVS/EventBuilder.h index 4d2207447f..839c6cd5fa 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/EventBuilder.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/EventBuilder.h @@ -69,6 +69,22 @@ const std::pair buildJsonEventString( const std::string& jsonPayloadValue = "{}", const std::string& jsonContext = ""); +/** + * Builds a JSON event string which includes the header, optional endpoint (if the endpoint is the source + * of the event), the @c payload, and jsonContext. + * + * @param eventHeader The event's @c AVSMessageHeader. + * @param endpoint The optional endpoint which was the source of this event. + * @param jsonPayloadValue The payload value associated with the "payload" key. The value must be a stringified json. + * @param jsonContext The context value associated with the "context" key. The value must be a stringified json. + * @return The event JSON string if successful, else an empty string. + */ +std::string buildJsonEventString( + const AVSMessageHeader& eventHeader, + const utils::Optional& endpoint, + const std::string& jsonPayloadValue, + const std::string& jsonContext); + /** * Builds a JSON event string which includes the header, the @c payload and an optional @c context. * The header includes the namespace, name, message Id and an optional @c dialogRequestId. @@ -76,8 +92,8 @@ const std::pair buildJsonEventString( * header. * * @param eventHeader The event's @c AVSMessageHeader. - * @param endpoint The endpoint which was the source of this event. - * @param payload The payload value associated with the "payload" key. The value must be a stringified json. + * @param endpoint The optional endpoint which was the source of this event. + * @param jsonPayloadValue The payload value associated with the "payload" key. The value must be a stringified json. * @param context Optional @c AVSContext to be sent with the event message. * @return The event JSON string if successful, else an empty string. */ diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h b/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h index 6047280299..03ed426dc4 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/Initialization/AlexaClientSDKInit.h @@ -67,7 +67,7 @@ class AlexaClientSDKInit { */ ~AlexaClientSDKInit(); - /* + /** * Checks whether the Alexa Client SDK has been initialized. * * @return Whether the Alexa Client SDK has been initialized. @@ -121,6 +121,17 @@ class AlexaClientSDKInit { static void uninitialize(); private: + /** + * Cleanup resources activated during the initialization of the Alexa Client SDK. + * + * You should call cleanup() when resources must be released before exiting. + * + * This function must be called when no other threads in the process are running. this function + * is not thread safe. This requirement is present because cleanup() calls functions of other + * libraries that have the same requirements and thread safety. + */ + static void cleanup(); + /// Tracks whether we've initialized the Alexa Client SDK or not static std::atomic_int g_isInitialized; }; diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/SpeakerConstants/SpeakerConstants.h b/AVSCommon/AVS/include/AVSCommon/AVS/SpeakerConstants/SpeakerConstants.h index 03a7a66b51..1b906bbc1e 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/SpeakerConstants/SpeakerConstants.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/SpeakerConstants/SpeakerConstants.h @@ -41,6 +41,12 @@ const int8_t AVS_ADJUST_VOLUME_MAX = 100; /// Default unmute volume level. const int8_t MIN_UNMUTE_VOLUME = 10; +/// Default speaker volume. +const int8_t DEFAULT_SPEAKER_VOLUME = 40; + +/// Default alerts volume. +const int8_t DEFAULT_ALERTS_VOLUME = 40; + } // namespace speakerConstants } // namespace avs } // namespace avsCommon diff --git a/AVSCommon/AVS/src/AVSContext.cpp b/AVSCommon/AVS/src/AVSContext.cpp index 446f107d92..d3a0a275d8 100644 --- a/AVSCommon/AVS/src/AVSContext.cpp +++ b/AVSCommon/AVS/src/AVSContext.cpp @@ -44,7 +44,7 @@ static const std::string TAG("AVSContext"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) utils::logger::LogEntry(TAG, event) @@ -84,11 +84,11 @@ std::string AVSContext::toJson() const { jsonGenerator.addMember(UNCERTAINTY_KEY_STRING, state.uncertaintyInMilliseconds); jsonGenerator.finishArrayElement(); } else { - ACSDK_DEBUG0(LX(__func__).d("stateIgnored", identifier.nameSpace + "::" + identifier.name)); + ACSDK_DEBUG0(LX("toJson").d("stateIgnored", identifier.nameSpace + "::" + identifier.name)); } } jsonGenerator.finishArray(); - ACSDK_DEBUG5(LX(__func__).sensitive("context", jsonGenerator.toString())); + ACSDK_DEBUG5(LX("toJson").sensitive("context", jsonGenerator.toString())); return jsonGenerator.toString(); } } // namespace avs diff --git a/AVSCommon/AVS/src/AVSDirective.cpp b/AVSCommon/AVS/src/AVSDirective.cpp index 6704948063..d9e8c65274 100644 --- a/AVSCommon/AVS/src/AVSDirective.cpp +++ b/AVSCommon/AVS/src/AVSDirective.cpp @@ -64,7 +64,7 @@ static const std::string TAG("AvsDirective"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -146,22 +146,22 @@ static std::shared_ptr parseHeader(const Document& document, A std::string instance; if (retrieveValue(headerIt->value, JSON_MESSAGE_INSTANCE_KEY, &instance)) { - ACSDK_DEBUG5(LX(__func__).d(JSON_MESSAGE_INSTANCE_KEY, instance)); + ACSDK_DEBUG5(LX("parseHeader").d(JSON_MESSAGE_INSTANCE_KEY, instance)); } std::string payloadVersion; if (retrieveValue(headerIt->value, JSON_MESSAGE_PAYLOAD_VERSION_KEY, &payloadVersion)) { - ACSDK_DEBUG5(LX(__func__).d(JSON_MESSAGE_PAYLOAD_VERSION_KEY, payloadVersion)); + ACSDK_DEBUG5(LX("parseHeader").d(JSON_MESSAGE_PAYLOAD_VERSION_KEY, payloadVersion)); } std::string correlationToken; if (retrieveValue(headerIt->value, JSON_CORRELATION_TOKEN_KEY, &correlationToken)) { - ACSDK_DEBUG5(LX(__func__).d(JSON_CORRELATION_TOKEN_KEY, correlationToken)); + ACSDK_DEBUG5(LX("parseHeader").d(JSON_CORRELATION_TOKEN_KEY, correlationToken)); } std::string eventCorrelationToken; if (retrieveValue(headerIt->value, JSON_EVENT_CORRELATION_TOKEN_KEY, &eventCorrelationToken)) { - ACSDK_DEBUG5(LX(__func__).d(JSON_EVENT_CORRELATION_TOKEN_KEY, eventCorrelationToken)); + ACSDK_DEBUG5(LX("parseHeader").d(JSON_EVENT_CORRELATION_TOKEN_KEY, eventCorrelationToken)); } return std::make_shared( @@ -219,20 +219,20 @@ static utils::Optional parseEndpoint(const Document& documen Value::ConstMemberIterator endpointIt; if (!findNode(directiveIt->value, JSON_ENDPOINT_KEY, &endpointIt)) { - ACSDK_DEBUG0(LX(__func__).m("noEndpoint")); + ACSDK_DEBUG0(LX("parseEndpoint").m("noEndpoint")); return utils::Optional(); } std::string endpointId; if (!retrieveValue(endpointIt->value, JSON_ENDPOINT_ID_KEY, &endpointId)) { - ACSDK_ERROR(LX(__func__).m("noEndpointId")); + ACSDK_ERROR(LX("parseEndpoint").m("noEndpointId")); return utils::Optional(); } AVSMessageEndpoint messageEndpoint{endpointId}; messageEndpoint.cookies = retrieveStringMap(endpointIt->value, JSON_ENDPOINT_COOKIE_KEY); - ACSDK_DEBUG5(LX(__func__).sensitive("endpointId", endpointId)); + ACSDK_DEBUG5(LX("parseEndpoint").sensitive("endpointId", endpointId)); return utils::Optional(messageEndpoint); } diff --git a/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp b/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp index 40f3abddee..4a4ba73e83 100644 --- a/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp +++ b/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp @@ -28,10 +28,17 @@ static const std::string TAG("AbstractAVSConnectionManager"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) +/** + * Create a LogEntry using this file's TAG, the specified event string, and pointer to disambiguate the instance. + * + * @param event The event string for this @c LogEntry. + */ +#define LX_P(event) LX(event).p("this", this) + AbstractAVSConnectionManager::AbstractAVSConnectionManager( std::unordered_set> observers) : m_connectionStatus{ConnectionStatusObserverInterface::Status::DISCONNECTED}, @@ -46,7 +53,7 @@ AbstractAVSConnectionManager::AbstractAVSConnectionManager( void AbstractAVSConnectionManager::addConnectionStatusObserver( std::shared_ptr observer) { if (!observer) { - ACSDK_ERROR(LX("addConnectionStatusObserverFailed").d("reason", "nullObserver")); + ACSDK_ERROR(LX_P("addConnectionStatusObserverFailed").d("reason", "nullObserver")); return; } @@ -60,7 +67,7 @@ void AbstractAVSConnectionManager::addConnectionStatusObserver( // call new onConnectionStatusChanged API for (auto engineStatus : localEngineConnectionStatuses) { (void)engineStatus; - ACSDK_DEBUG9(LX(__func__) + ACSDK_DEBUG9(LX_P("addConnectionStatusObserver") .d("engineType", engineStatus.engineType) .d("status", engineStatus.status) .d("reason", engineStatus.reason)); @@ -79,7 +86,7 @@ void AbstractAVSConnectionManager::addConnectionStatusObserver( void AbstractAVSConnectionManager::removeConnectionStatusObserver( std::shared_ptr observer) { if (!observer) { - ACSDK_ERROR(LX("removeConnectionStatusObserverFailed").d("reason", "nullObserver")); + ACSDK_ERROR(LX_P("removeConnectionStatusObserverFailed").d("reason", "nullObserver")); return; } @@ -116,7 +123,7 @@ void AbstractAVSConnectionManager::notifyObservers(bool avsConnectionStatusChang for (auto engineStatus : localEngineConnectionStatuses) { (void)engineStatus; - ACSDK_DEBUG5(LX(__func__) + ACSDK_DEBUG5(LX_P("notifyObservers") .m("EngineConnectionStatusDetail") .d("engineType", engineStatus.engineType) .d("status", engineStatus.status) diff --git a/AVSCommon/AVS/src/AlexaClientSDKInit.cpp b/AVSCommon/AVS/src/AlexaClientSDKInit.cpp index 1c187203d1..2af6d8c173 100644 --- a/AVSCommon/AVS/src/AlexaClientSDKInit.cpp +++ b/AVSCommon/AVS/src/AlexaClientSDKInit.cpp @@ -35,7 +35,7 @@ static const std::string TAG("AlexaClientSdkInit"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -99,7 +99,7 @@ bool AlexaClientSDKInit::initialize(const std::vector& initParams) { - ACSDK_INFO(LX(__func__).d("sdkversion", avsCommon::utils::sdkVersion::getCurrentVersion())); + ACSDK_INFO(LX("initialize").d("sdkversion", avsCommon::utils::sdkVersion::getCurrentVersion())); if (!initParams) { ACSDK_ERROR(LX("initializeFailed").d("reason", "nullInitParams")); @@ -113,7 +113,7 @@ bool AlexaClientSDKInit::initialize(const std::shared_ptrversion)); + ACSDK_INFO(LX("initialize").d("curlVersion", curlVersion->version)); if (!(curlVersion->features & CURL_VERSION_HTTP2)) { ACSDK_ERROR(LX("initializeFailed").d("reason", "curlDoesNotSupportHTTP2")); @@ -134,6 +134,7 @@ bool AlexaClientSDKInit::initialize(const std::shared_ptrtimerDelegateFactory; if (!timerDelegateFactory) { ACSDK_ERROR(LX("initializeFailed").d("reason", "nullTimerDelegateFactory")); + cleanup(); return false; } @@ -146,23 +147,27 @@ bool AlexaClientSDKInit::initialize(const std::shared_ptrwithTimerDelegateFactory(timerDelegateFactory); if (!primitivesProvider->initialize()) { + cleanup(); return false; } @@ -170,12 +175,7 @@ bool AlexaClientSDKInit::initialize(const std::shared_ptrdeactivate(); @@ -185,6 +185,15 @@ void AlexaClientSDKInit::uninitialize() { } } +void AlexaClientSDKInit::uninitialize() { + if (0 == g_isInitialized) { + ACSDK_ERROR(LX("initializeError").d("reason", "notInitialized")); + return; + } + g_isInitialized--; + cleanup(); +} + } // namespace initialization } // namespace avs } // namespace avsCommon diff --git a/AVSCommon/AVS/src/Attachment/AttachmentManager.cpp b/AVSCommon/AVS/src/Attachment/AttachmentManager.cpp index d61974fc4b..025b13fb51 100644 --- a/AVSCommon/AVS/src/Attachment/AttachmentManager.cpp +++ b/AVSCommon/AVS/src/Attachment/AttachmentManager.cpp @@ -35,7 +35,7 @@ static const std::string TAG("AttachmentManager"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp b/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp index 109ce14727..3d837b289f 100644 --- a/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp +++ b/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp @@ -36,7 +36,7 @@ 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. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/Attachment/InProcessAttachmentWriter.cpp b/AVSCommon/AVS/src/Attachment/InProcessAttachmentWriter.cpp index 7d13fd9118..58910e700b 100644 --- a/AVSCommon/AVS/src/Attachment/InProcessAttachmentWriter.cpp +++ b/AVSCommon/AVS/src/Attachment/InProcessAttachmentWriter.cpp @@ -29,7 +29,7 @@ static const std::string TAG("InProcessAttachmentWriter"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/CapabilityAgent.cpp b/AVSCommon/AVS/src/CapabilityAgent.cpp index 0e5204dc9f..bb5b329aaa 100644 --- a/AVSCommon/AVS/src/CapabilityAgent.cpp +++ b/AVSCommon/AVS/src/CapabilityAgent.cpp @@ -39,7 +39,7 @@ static const int CAPABILITY_QUEUE_WARN_SIZE = 10; /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/CapabilityResources.cpp b/AVSCommon/AVS/src/CapabilityResources.cpp index c81f56212b..d5247142d2 100644 --- a/AVSCommon/AVS/src/CapabilityResources.cpp +++ b/AVSCommon/AVS/src/CapabilityResources.cpp @@ -29,7 +29,7 @@ static const std::string TAG("CapabilityResources"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/CapabilitySemantics/ActionsToDirectiveMapping.cpp b/AVSCommon/AVS/src/CapabilitySemantics/ActionsToDirectiveMapping.cpp index ba9efafac4..80fcdb5de6 100644 --- a/AVSCommon/AVS/src/CapabilitySemantics/ActionsToDirectiveMapping.cpp +++ b/AVSCommon/AVS/src/CapabilitySemantics/ActionsToDirectiveMapping.cpp @@ -51,7 +51,7 @@ static const std::string TAG("ActionsToDirectiveMapping"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/CapabilitySemantics/CapabilitySemantics.cpp b/AVSCommon/AVS/src/CapabilitySemantics/CapabilitySemantics.cpp index 58d4fa356b..727b7bd5f7 100644 --- a/AVSCommon/AVS/src/CapabilitySemantics/CapabilitySemantics.cpp +++ b/AVSCommon/AVS/src/CapabilitySemantics/CapabilitySemantics.cpp @@ -39,7 +39,7 @@ static const std::string TAG("CapabilitySemantics"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/CapabilitySemantics/StatesToRangeMapping.cpp b/AVSCommon/AVS/src/CapabilitySemantics/StatesToRangeMapping.cpp index 908c7732e2..8b43bfe5f2 100644 --- a/AVSCommon/AVS/src/CapabilitySemantics/StatesToRangeMapping.cpp +++ b/AVSCommon/AVS/src/CapabilitySemantics/StatesToRangeMapping.cpp @@ -55,7 +55,7 @@ static const std::string TAG("StatesToRangeMapping"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/CapabilitySemantics/StatesToValueMapping.cpp b/AVSCommon/AVS/src/CapabilitySemantics/StatesToValueMapping.cpp index 05d1ca6b12..e48cb54111 100644 --- a/AVSCommon/AVS/src/CapabilitySemantics/StatesToValueMapping.cpp +++ b/AVSCommon/AVS/src/CapabilitySemantics/StatesToValueMapping.cpp @@ -52,7 +52,7 @@ static const std::string TAG("StatesToValueMapping"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/ComponentConfiguration.cpp b/AVSCommon/AVS/src/ComponentConfiguration.cpp index d241f5f5da..1429ffaf6d 100644 --- a/AVSCommon/AVS/src/ComponentConfiguration.cpp +++ b/AVSCommon/AVS/src/ComponentConfiguration.cpp @@ -28,7 +28,7 @@ static const std::string TAG("ComponentConfiguration"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -46,19 +46,21 @@ static bool isValidConfiguration(std::string name, std::string version) { const unsigned char& c = *it; if (!std::isalnum(c) && c != '.') { - ACSDK_ERROR(LX(__func__).m("invalid component version").d("name", name).d("version", version)); + ACSDK_ERROR( + LX("isValidConfiguration").m("invalid component version").d("name", name).d("version", version)); return false; } // Version must have characters between dots if (c == '.' && it + 1 != version.end() && *(it + 1) == '.') { - ACSDK_ERROR(LX(__func__).m("invalid component version").d("name", name).d("version", version)); + ACSDK_ERROR( + LX("isValidConfiguration").m("invalid component version").d("name", name).d("version", version)); return false; } } // Valid if configuration is not empty if (name.length() == 0 || version.length() == 0) { - ACSDK_ERROR(LX(__func__).m("component can not be empty").d("name", name).d("version", version)); + ACSDK_ERROR(LX("isValidConfiguration").m("component can not be empty").d("name", name).d("version", version)); return false; } diff --git a/AVSCommon/AVS/src/DialogUXStateAggregator.cpp b/AVSCommon/AVS/src/DialogUXStateAggregator.cpp index e4ad465074..f581277b9f 100644 --- a/AVSCommon/AVS/src/DialogUXStateAggregator.cpp +++ b/AVSCommon/AVS/src/DialogUXStateAggregator.cpp @@ -23,6 +23,7 @@ namespace avs { using namespace sdkInterfaces; using namespace avsCommon::utils::metrics; +using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. static const std::string TAG("DialogUXStateAggregator"); @@ -30,7 +31,7 @@ static const std::string TAG("DialogUXStateAggregator"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -74,7 +75,7 @@ std::shared_ptr DialogUXStateAggregator::createDialogUX const std::shared_ptr& connectionManager, const std::shared_ptr& interactionModelNotifier) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("createDialogUXStateAggregator")); if (!connectionManager || !interactionModelNotifier) { ACSDK_ERROR(LX("createDialogUXStateAggregatorFailed") .d("isConnectionManagerNull", !connectionManager) @@ -128,7 +129,7 @@ void DialogUXStateAggregator::removeObserver(std::shared_ptr& mediaPlayerState, const std::vector& audioAnalyzerState) { - ACSDK_DEBUG0(LX(__func__).d("SpeechSynthesizerState", state)); + ACSDK_DEBUG0(LX("onStateChanged").d("SpeechSynthesizerState", state)); m_speechSynthesizerState = state; m_executor.submit([this, state]() { ACSDK_DEBUG0(LX("onStateChangedLambda").d("SpeechSynthesizerState", state)); @@ -190,7 +190,7 @@ void DialogUXStateAggregator::onStateChanged( } void DialogUXStateAggregator::executeTryExitThinkingState() { - ACSDK_DEBUG0(LX(__func__)); + ACSDK_DEBUG0(LX("executeTryExitThinkingState")); if (DialogUXStateObserverInterface::DialogUXState::THINKING == m_currentState && SpeechSynthesizerObserverInterface::SpeechSynthesizerState::GAINING_FOCUS != m_speechSynthesizerState) { ACSDK_DEBUG5(LX("Kicking off short timer").d("shortTimeout in ms", m_shortTimeoutForThinkingToIdle.count())); @@ -209,8 +209,34 @@ void DialogUXStateAggregator::executeTryExitThinkingState() { void DialogUXStateAggregator::onConnectionStatusChanged( const ConnectionStatusObserverInterface::Status status, const ConnectionStatusObserverInterface::ChangedReason reason) { - m_executor.submit([this, status]() { - if (status != avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::Status::CONNECTED) { + /** + * Empty. Either this method should be implemented or the one which takes a vector, never both at the same time + * since calling both methods at the same time could cause issues. Keeping this method empty + * implementation since this is a pure virtual function which requires to be implemented. This should be remove + * once the virtual function can be removed + */ + ACSDK_DEBUG(LX("onConnectionStatusChanged").d("status", status).m("deprecated method. Nothing done.")); +} + +void DialogUXStateAggregator::onConnectionStatusChanged( + const DialogUXStateAggregator::Status status, + const std::vector& engineStatuses) { + ACSDK_DEBUG(LX("onConnectionStatusChanged").d("engineAggregatedStatus", status)); + m_executor.submit([this, engineStatuses]() { + bool isDisconnected = true; + for (const auto& engineStatus : engineStatuses) { + ACSDK_DEBUG(LX("onConnectionStatusChangedLambda") + .d("engineType", engineStatus.engineType) + .d("engineStatus", engineStatus.status)); + if (Status::CONNECTED == engineStatus.status) { + isDisconnected = false; + break; + } + } + + ACSDK_DEBUG(LX("onConnectionStatusChangedLambda").d("isConnected", !isDisconnected)); + if (isDisconnected) { + ACSDK_DEBUG(LX("onConnectionStatusChangedLambda").m("Setting state to idle")); executeSetState(DialogUXStateObserverInterface::DialogUXState::IDLE); } }); @@ -310,43 +336,26 @@ void DialogUXStateAggregator::tryEnterIdleStateOnTimer() { } bool DialogUXStateAggregator::executeSetState(sdkInterfaces::DialogUXStateObserverInterface::DialogUXState newState) { - bool validTransition = true; + bool validTransition = newState != m_currentState; - if (newState == m_currentState) { - validTransition = false; - } else { - switch (m_currentState) { - case DialogUXStateObserverInterface::DialogUXState::THINKING: - if (DialogUXStateObserverInterface::DialogUXState::LISTENING == newState) { - validTransition = false; - } - - break; - default: - break; - } - } - - ACSDK_DEBUG0(LX(__func__) + ACSDK_DEBUG0(LX("executeSetState") .d("from", m_currentState) .d("to", newState) .d("validTransition", validTransition ? "true" : "false")); - if (!validTransition) { - return false; + if (validTransition) { + m_listeningTimeoutTimer.stop(); + m_thinkingTimeoutTimer.stop(); + m_multiturnSpeakingToListeningTimer.stop(); + m_currentState = newState; + notifyObserversOfState(); } - m_listeningTimeoutTimer.stop(); - m_thinkingTimeoutTimer.stop(); - m_multiturnSpeakingToListeningTimer.stop(); - m_currentState = newState; - notifyObserversOfState(); - - return true; + return validTransition; } void DialogUXStateAggregator::executeTryEnterIdleState() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("executeTryEnterIdleState")); m_thinkingTimeoutTimer.stop(); m_multiturnSpeakingToListeningTimer.stop(); if (!m_multiturnSpeakingToListeningTimer diff --git a/AVSCommon/AVS/src/DirectiveRoutingRule.cpp b/AVSCommon/AVS/src/DirectiveRoutingRule.cpp index 825709f3fc..0b104c8ab1 100644 --- a/AVSCommon/AVS/src/DirectiveRoutingRule.cpp +++ b/AVSCommon/AVS/src/DirectiveRoutingRule.cpp @@ -27,7 +27,7 @@ static const std::string TAG("DirectiveRoutingRule"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/EditableMessageRequest.cpp b/AVSCommon/AVS/src/EditableMessageRequest.cpp index 82d2e3abcf..de1fb00b3f 100644 --- a/AVSCommon/AVS/src/EditableMessageRequest.cpp +++ b/AVSCommon/AVS/src/EditableMessageRequest.cpp @@ -28,7 +28,7 @@ static const std::string TAG("EditableMessageRequest"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/EventBuilder.cpp b/AVSCommon/AVS/src/EventBuilder.cpp index 96c7f07e4e..dd5aaed250 100644 --- a/AVSCommon/AVS/src/EventBuilder.cpp +++ b/AVSCommon/AVS/src/EventBuilder.cpp @@ -36,7 +36,7 @@ static const std::string TAG("EventBuilder"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -150,7 +150,7 @@ const std::pair buildJsonEventString( ACSDK_DEBUG(LX("buildJsonEventString").d("messageId", messageId).d("namespace", nameSpace).d("name", eventName)); auto eventJson = jsonGenerator.toString(); - ACSDK_DEBUG0(LX(__func__).d("event", eventJson)); + ACSDK_DEBUG0(LX("buildJsonEventString").d("event", eventJson)); return std::make_pair(messageId, eventJson); } @@ -190,7 +190,7 @@ std::string buildJsonEventString( const AVSMessageHeader& eventHeader, const Optional& endpoint, const std::string& jsonPayloadValue, - const Optional& context) { + const std::string& jsonContext) { json::JsonGenerator jsonGenerator; jsonGenerator.startObject(EVENT_KEY_STRING); { @@ -202,12 +202,26 @@ std::string buildJsonEventString( } jsonGenerator.finishObject(); - if (context.hasValue()) { - jsonGenerator.addRawJsonMember(CONTEXT_KEY_STRING, context.value().toJson()); + if (!jsonContext.empty()) { + if (!jsonGenerator.addRawJsonMember(CONTEXT_KEY_STRING, jsonContext)) { + ACSDK_ERROR(LX("buildJsonEventStringFailed") + .d("reason", "addRawJsonMemberFailed") + .sensitive("context", jsonContext)); + return ""; + } } return jsonGenerator.toString(); } +std::string buildJsonEventString( + const AVSMessageHeader& eventHeader, + const Optional& endpoint, + const std::string& jsonPayloadValue, + const Optional& context) { + return buildJsonEventString( + eventHeader, endpoint, jsonPayloadValue, (context.hasValue() ? context.value().toJson() : "")); +} + } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/src/ExceptionEncounteredSender.cpp b/AVSCommon/AVS/src/ExceptionEncounteredSender.cpp index 1345122796..0806bdafa1 100644 --- a/AVSCommon/AVS/src/ExceptionEncounteredSender.cpp +++ b/AVSCommon/AVS/src/ExceptionEncounteredSender.cpp @@ -37,7 +37,7 @@ static const std::string TAG("ExceptionEncountered"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/Initialization/SDKPrimitivesProvider.cpp b/AVSCommon/AVS/src/Initialization/SDKPrimitivesProvider.cpp index 9fb1a07437..bf0bc33db0 100644 --- a/AVSCommon/AVS/src/Initialization/SDKPrimitivesProvider.cpp +++ b/AVSCommon/AVS/src/Initialization/SDKPrimitivesProvider.cpp @@ -31,12 +31,12 @@ std::mutex SDKPrimitivesProvider::m_mutex; /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) std::shared_ptr SDKPrimitivesProvider::getInstance() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getInstance")); std::lock_guard lock(m_mutex); if (!m_provider) { @@ -53,15 +53,15 @@ SDKPrimitivesProvider::SDKPrimitivesProvider() : bool SDKPrimitivesProvider::withTimerDelegateFactory( std::shared_ptr timerDelegateFactory) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("withTimerDelegateFactory")); if (!timerDelegateFactory) { - ACSDK_ERROR(LX(__func__).d("reason", "nullTimerDelegateFactory")); + ACSDK_ERROR(LX("withTimerDelegateFactoryFailed").d("reason", "nullTimerDelegateFactory")); return false; } if (isInitialized()) { - ACSDK_ERROR(LX(__func__).d("reason", "alreadyInitialized")); + ACSDK_ERROR(LX("withTimerDelegateFactoryFailed").d("reason", "alreadyInitialized")); return false; } @@ -72,10 +72,10 @@ bool SDKPrimitivesProvider::withTimerDelegateFactory( } std::shared_ptr SDKPrimitivesProvider::getTimerDelegateFactory() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getTimerDelegateFactory")); if (!isInitialized()) { - ACSDK_ERROR(LX(__func__).d("reason", "notInitialized")); + ACSDK_ERROR(LX("getTimerDelegateFactoryFailed").d("reason", "notInitialized")); return nullptr; } @@ -84,10 +84,10 @@ std::shared_ptr SDKPrimiti } bool SDKPrimitivesProvider::initialize() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("initialize")); if (isInitialized()) { - ACSDK_ERROR(LX(__func__).d("reason", "alreadyInitialized")); + ACSDK_ERROR(LX("initializeFailed").d("reason", "alreadyInitialized")); return false; } @@ -98,14 +98,14 @@ bool SDKPrimitivesProvider::initialize() { } bool SDKPrimitivesProvider::isInitialized() const { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("isInitialized")); std::lock_guard lock(m_mutex); return m_initialized; } void SDKPrimitivesProvider::terminate() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("terminate")); reset(); @@ -114,7 +114,7 @@ void SDKPrimitivesProvider::terminate() { } void SDKPrimitivesProvider::reset() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("reset")); std::lock_guard lock(m_mutex); m_timerDelegateFactory.reset(); diff --git a/AVSCommon/AVS/src/MessageRequest.cpp b/AVSCommon/AVS/src/MessageRequest.cpp index 0e06a811ea..f1e89005eb 100644 --- a/AVSCommon/AVS/src/MessageRequest.cpp +++ b/AVSCommon/AVS/src/MessageRequest.cpp @@ -29,7 +29,7 @@ static const std::string TAG("MessageRequest"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/AVS/src/WaitableMessageRequest.cpp b/AVSCommon/AVS/src/WaitableMessageRequest.cpp index cb8c3cf5fd..73ab9ab482 100644 --- a/AVSCommon/AVS/src/WaitableMessageRequest.cpp +++ b/AVSCommon/AVS/src/WaitableMessageRequest.cpp @@ -29,7 +29,7 @@ static const std::string TAG("WaitableMessageRequest"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -51,7 +51,7 @@ void WaitableMessageRequest::sendCompleted( m_sendMessageStatus = sendMessageStatus; m_responseReceived = true; } else { - ACSDK_WARN(LX(__func__).d("reason", "sendCompletedCalled when m_responseReceived")); + ACSDK_WARN(LX("sendCompleted").d("reason", "sendCompletedCalled when m_responseReceived")); } m_requestCv.notify_one(); } diff --git a/AVSCommon/AVS/test/AlexaClientSDKInitTest.cpp b/AVSCommon/AVS/test/AlexaClientSDKInitTest.cpp index fdd80a402e..343cdeaa12 100644 --- a/AVSCommon/AVS/test/AlexaClientSDKInitTest.cpp +++ b/AVSCommon/AVS/test/AlexaClientSDKInitTest.cpp @@ -16,9 +16,16 @@ /// @file AlexaClientSDKInitTest.cpp #include + +#include #include -#include "AVSCommon/AVS/Initialization/AlexaClientSDKInit.h" +#include +#include +#include +#ifdef ENABLE_LPM +#include +#endif namespace alexaClientSDK { namespace avsCommon { @@ -28,65 +35,188 @@ namespace test { static std::vector> EMPTY_JSON_STREAMS; -using namespace std; +using namespace sdkInterfaces::test; +using namespace ::testing; + +/// Test harness for @c AlexaClientSDKInit class. +class AlexaClientSDKInitTest : public ::testing::Test { +public: + /// Setup the test harness for running the test. + void SetUp() override; + + /// Clean up the test harness after running the test. + void TearDown() override; + + /// Test initialization parameters builder. + std::unique_ptr m_builder; + /// Test json streams. + std::shared_ptr>> m_jsonStreamsPtr; + /// Test logger. + std::shared_ptr m_logger; +}; + +void AlexaClientSDKInitTest::SetUp() { + m_builder = InitializationParametersBuilder::create(); + m_jsonStreamsPtr = std::make_shared>>(EMPTY_JSON_STREAMS); + m_builder->withJsonStreams(m_jsonStreamsPtr); +} + +void AlexaClientSDKInitTest::TearDown() { + AlexaClientSDKInit::uninitialize(); +} + +/** + * Tests @c initialize without any initialization parameters, expecting to return @c false. + */ +TEST_F(AlexaClientSDKInitTest, test_initializeNoInitParams) { + ASSERT_FALSE(AlexaClientSDKInit::initialize(nullptr)); +} + +/** + * Tests @c initialize with a null timerDelegateFactory, expecting to return @c false. + */ +TEST_F(AlexaClientSDKInitTest, test_initializeWithNullTimerDelegateFactory) { + auto initParams = m_builder->build(); + initParams->timerDelegateFactory = nullptr; + ASSERT_FALSE(AlexaClientSDKInit::initialize(std::move(initParams))); +} /** - * Tests @c initialize without any JSON configuration, expecting it to return @c true. + * Tests @c initialize without any JSON configuration, expecting to return @c true. * * @note This test also validates whether libcurl supports HTTP2. */ -TEST(AlexaClientSDKInitTest, test_initializeNoJSONConfig) { +TEST_F(AlexaClientSDKInitTest, test_initializeNoJSONConfig) { ASSERT_TRUE(AlexaClientSDKInit::initialize(EMPTY_JSON_STREAMS)); - AlexaClientSDKInit::uninitialize(); } +#ifdef ENABLE_LPM +/* + * Tests @c initialize with null Low Power Mode, expecting to return @c true. + */ +TEST_F(AlexaClientSDKInitTest, test_initializeNullLPM) { + auto initParams = m_builder->build(); + initParams->powerResourceManager = nullptr; + ASSERT_TRUE(AlexaClientSDKInit::initialize(std::move(initParams))); +} + +/* + * Tests @c initialize with Low Power Mode enabled and unsupported TimerDelegateFactory, expecting to return @c false. + */ +TEST_F(AlexaClientSDKInitTest, test_initializeLPMUnsupportedTimerDelegateFactory) { + auto powerResourceManager = std::make_shared(); + auto mockTimerDelegateFactory = std::make_shared(); + m_builder->withTimerDelegateFactory(mockTimerDelegateFactory); + EXPECT_CALL(*mockTimerDelegateFactory, supportsLowPowerMode()).WillOnce(Return(false)); + m_builder->withPowerResourceManager(powerResourceManager); + auto initParams = m_builder->build(); + ASSERT_FALSE(AlexaClientSDKInit::initialize(std::move(initParams))); +} +#endif + /** - * Tests @c initialize with an invalid JSON configuration, expecting it to return @c false. + * Tests @c initialize with an invalid JSON configuration, expecting to return @c false. * * @note This test also validates whether libcurl supports HTTP2. */ -TEST(AlexaClientSDKInitTest, test_initializeInvalidJSONConfig) { +TEST_F(AlexaClientSDKInitTest, test_initializeInvalidJSONConfig) { auto invalidJSON = std::shared_ptr(new std::stringstream()); (*invalidJSON) << "{"; ASSERT_FALSE(AlexaClientSDKInit::initialize({invalidJSON})); } /** - * Tests @c initialize with a valid JSON configuration, expecting it to return @c true. + * Tests @c initialize with a valid JSON configuration, expecting to return @c true. * * @note This test also validates whether libcurl supports HTTP2. */ -TEST(AlexaClientSDKInitTest, test_initializeValidJSONConfig) { +TEST_F(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. + * Tests @c isInitialized when the SDK has not been initialized yet, expecting to return @c false. */ -TEST(AlexaClientSDKInitTest, test_uninitializedIsInitialized) { +TEST_F(AlexaClientSDKInitTest, test_uninitializedIsInitialized) { ASSERT_FALSE(AlexaClientSDKInit::isInitialized()); } /** - * Tests @c isInitialized when the SDK is initialized, expecting it to return @c true. + * Tests @c isInitialized when the SDK is initialized, expecting to return @c true. */ -TEST(AlexaClientSDKInitTest, test_isInitialized) { +TEST_F(AlexaClientSDKInitTest, test_isInitialized) { ASSERT_TRUE(AlexaClientSDKInit::initialize(EMPTY_JSON_STREAMS)); - // Expect used to ensure we uninitialize. EXPECT_TRUE(AlexaClientSDKInit::isInitialized()); - AlexaClientSDKInit::uninitialize(); } /** * Tests @c uninitialize when the SDK has not been initialized yet, expecting no crashes or exceptions. */ -TEST(AlexaClientSDKInitTest, test_uninitialize) { +TEST_F(AlexaClientSDKInitTest, test_uninitialize) { AlexaClientSDKInit::uninitialize(); } +/** + * Tests @c getCreateAlexaClientSDKInit using JSON Stream with a null logger, expecting to return @c nullptr. + */ +TEST_F(AlexaClientSDKInitTest, test_getCreateAlexaClientSDKInitNullLoggerUsingJSON) { + auto constructor = AlexaClientSDKInit::getCreateAlexaClientSDKInit(EMPTY_JSON_STREAMS); + ASSERT_EQ(constructor(nullptr), nullptr); +} + +/** + * Tests @c getCreateAlexaClientSDKInit using Init Params with a null logger, expecting to return @c nullptr. + */ +TEST_F(AlexaClientSDKInitTest, test_getCreateAlexaClientSDKInitNullLoggerUsingInitParams) { + auto initParams = m_builder->build(); + auto constructor = AlexaClientSDKInit::getCreateAlexaClientSDKInit(std::move(initParams)); + ASSERT_EQ(constructor(nullptr), nullptr); +} + +/** + * Tests @c getCreateAlexaClientSDKInit using invalid JSON Stream, expecting to return @c nullptr. + */ +TEST_F(AlexaClientSDKInitTest, test_getCreateAlexaClientSDKInitInvalidJSONStream) { + auto invalidJSON = std::shared_ptr(new std::stringstream()); + (*invalidJSON) << "{"; + auto constructor = AlexaClientSDKInit::getCreateAlexaClientSDKInit({invalidJSON}); + ASSERT_EQ(constructor(m_logger), nullptr); +} + +/** + * Tests @c getCreateAlexaClientSDKInit using valid JSON Stream, expecting to return @c true. + */ +TEST_F(AlexaClientSDKInitTest, test_getCreateAlexaClientSDKInitValidJSONStream) { + auto constructor = AlexaClientSDKInit::getCreateAlexaClientSDKInit(EMPTY_JSON_STREAMS); + auto alexaClientSDKInitInstance = constructor(m_logger); + ASSERT_FALSE(AlexaClientSDKInit::isInitialized()); + alexaClientSDKInitInstance->initialize(EMPTY_JSON_STREAMS); + ASSERT_TRUE(AlexaClientSDKInit::isInitialized()); +} + +/** + * Tests @c getCreateAlexaClientSDKInit using valid Init Params, expecting to return @c true. + */ +TEST_F(AlexaClientSDKInitTest, test_getCreateAlexaClientSDKInitValidInitParams) { + auto initParams = m_builder->build(); + auto constructor = AlexaClientSDKInit::getCreateAlexaClientSDKInit(std::move(initParams)); + auto alexaClientSDKInitInstance = constructor(m_logger); + ASSERT_FALSE(AlexaClientSDKInit::isInitialized()); + alexaClientSDKInitInstance->initialize(EMPTY_JSON_STREAMS); + ASSERT_TRUE(AlexaClientSDKInit::isInitialized()); +} + +/** + * Tests @c getCreateAlexaClientSDKInit using null Init Params, expecting to return @c nullptr. + */ +TEST_F(AlexaClientSDKInitTest, test_getCreateAlexaClientSDKInitNullInitParams) { + auto initParams = m_builder->build(); + auto constructor = AlexaClientSDKInit::getCreateAlexaClientSDKInit(std::move(initParams)); + ASSERT_EQ(constructor(m_logger), nullptr); +} + } // namespace test } // namespace initialization } // namespace avs diff --git a/AVSCommon/AVS/test/Attachment/Common/CMakeLists.txt b/AVSCommon/AVS/test/Attachment/Common/CMakeLists.txt index bbcc2b2715..e6c83c76e1 100644 --- a/AVSCommon/AVS/test/Attachment/Common/CMakeLists.txt +++ b/AVSCommon/AVS/test/Attachment/Common/CMakeLists.txt @@ -6,6 +6,5 @@ if(BUILD_TESTING) "${AVSCommon_SOURCE_DIR}/AVS/test") target_link_libraries(AttachmentCommonTestLib AVSCommon - gtest_main gmock_main) endif() \ No newline at end of file diff --git a/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp b/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp index c85cbcb822..267c2ba555 100644 --- a/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp +++ b/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp @@ -25,10 +25,13 @@ namespace test { using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; +using namespace std; /// Long time out for observers to wait for the state change callback (we should not reach this). static const auto DEFAULT_TIMEOUT = std::chrono::seconds(5); +static const auto OTHER_ENGINE_TYPE = 2; + /// Short time out for when callbacks are expected not to occur. static const auto SHORT_TIMEOUT = std::chrono::milliseconds(50); @@ -227,21 +230,10 @@ TEST_F(DialogUXAggregatorTest, test_aipExpectingSpeechLeadsToListeningState) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::EXPECTING); } -/// Tests that the AIP busy state leads to the LISTENING state. -TEST_F(DialogUXAggregatorTest, test_aipBusyLeadsToListeningState) { - assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); - assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); -} - /// Tests that the RequestProcessingStarted leads to the THINKING state. TEST_F(DialogUXAggregatorTest, test_requestProcessingStartedLeadsToThinkingState) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); - assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); - m_aggregator->onRequestProcessingStarted(); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::THINKING); } @@ -255,6 +247,7 @@ TEST_F(DialogUXAggregatorTest, test_listeningGoesToIdleAfterTimeout) { assertStateChange(m_anotherTestObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); + anotherAggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); anotherAggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); assertStateChange(m_anotherTestObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); @@ -269,7 +262,7 @@ TEST_F(DialogUXAggregatorTest, test_thinkingGoesToIdleAfterTimeout) { anotherAggregator->addObserver(m_anotherTestObserver); assertStateChange(m_anotherTestObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - anotherAggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + anotherAggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); assertStateChange(m_anotherTestObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); anotherAggregator->onRequestProcessingStarted(); @@ -278,11 +271,11 @@ TEST_F(DialogUXAggregatorTest, test_thinkingGoesToIdleAfterTimeout) { assertStateChange(m_anotherTestObserver, DialogUXStateObserverInterface::DialogUXState::IDLE, TRANSITION_TIMEOUT); } -/// Tests that the THINKING state transitions to IDLE after recieving a message and a long timeout. +/// Tests that the THINKING state transitions to IDLE after receiving a message and a long timeout. TEST_F(DialogUXAggregatorTest, test_thinkingThenReceiveGoesToIdleAfterLongTimeout) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); m_aggregator->onRequestProcessingStarted(); @@ -299,7 +292,7 @@ TEST_F(DialogUXAggregatorTest, test_thinkingThenReceiveGoesToIdleAfterLongTimeou TEST_F(DialogUXAggregatorTest, test_listeningThenRequestProcessingCompletedThenSpeakGoesToSpeakButNotIdle) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); m_aggregator->onRequestProcessingStarted(); @@ -322,7 +315,7 @@ TEST_F(DialogUXAggregatorTest, test_listeningThenRequestProcessingCompletedThenS TEST_F(DialogUXAggregatorTest, test_speakingAndRecognizingFinishedGoesToIdle) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); m_aggregator->onRequestProcessingStarted(); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::THINKING); @@ -351,7 +344,7 @@ TEST_F(DialogUXAggregatorTest, test_nonIdleObservantsPreventsIdle) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); // AIP is active, SS is not. Expected: non idle - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); m_aggregator->onStateChanged( SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED, TEST_SOURCE_ID, @@ -389,7 +382,7 @@ TEST_F(DialogUXAggregatorTest, test_nonIdleObservantsPreventsIdle) { TEST_F(DialogUXAggregatorTest, test_speakingFinishedDoesNotGoesToIdleImmediately) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); m_aggregator->onRequestProcessingStarted(); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::THINKING); @@ -439,7 +432,7 @@ TEST_F(DialogUXAggregatorTest, test_simpleReceiveDoesNothing) { TEST_F(DialogUXAggregatorTest, test_thinkingThenReceiveRemainsInThinkingIfSpeechSynthesizerReportsGainingFocus) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); m_aggregator->onRequestProcessingStarted(); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::THINKING); @@ -464,7 +457,7 @@ TEST_F(DialogUXAggregatorTest, test_validStatesForRPSToThinking) { m_aggregator->onRequestProcessingCompleted(); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); m_aggregator->onRequestProcessingStarted(); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::THINKING); @@ -498,7 +491,7 @@ TEST_F(DialogUXAggregatorTest, test_validStatesForRPSToThinking) { TEST_F(DialogUXAggregatorTest, test_receiveThenRPCTransitionsOutOfThinking) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); - m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::BUSY); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); m_aggregator->onRequestProcessingStarted(); @@ -539,6 +532,74 @@ TEST_F(DialogUXAggregatorTest, test_receiveRPCwithoutRPS) { assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); } +/// Test that the state is set to idle when there is NOT an active connection +TEST_F(DialogUXAggregatorTest, test_setToIdleIfNoConnectionAvailable) { + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); + + DialogUXStateAggregator::EngineConnectionStatus pending{ENGINE_TYPE_ALEXA_VOICE_SERVICES, + ConnectionStatusObserverInterface::ChangedReason::NONE, + ConnectionStatusObserverInterface::Status::PENDING}; + DialogUXStateAggregator::EngineConnectionStatus disconnected{ + ENGINE_TYPE_ALEXA_VOICE_SERVICES, + ConnectionStatusObserverInterface::ChangedReason::NONE, + ConnectionStatusObserverInterface::Status::DISCONNECTED}; + vector engineStatuses; + engineStatuses.push_back(pending); + engineStatuses.push_back(disconnected); + m_aggregator->onConnectionStatusChanged(ConnectionStatusObserverInterface::Status::DISCONNECTED, engineStatuses); + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); +} + +/// Test that don't change the state is if THERE IS an active connection +TEST_F(DialogUXAggregatorTest, test_doNotSetToIdleIfConnectionIsAvailable) { + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); + + DialogUXStateAggregator::EngineConnectionStatus connected{ENGINE_TYPE_ALEXA_VOICE_SERVICES, + ConnectionStatusObserverInterface::ChangedReason::NONE, + ConnectionStatusObserverInterface::Status::CONNECTED}; + DialogUXStateAggregator::EngineConnectionStatus pending{ENGINE_TYPE_ALEXA_VOICE_SERVICES, + ConnectionStatusObserverInterface::ChangedReason::NONE, + ConnectionStatusObserverInterface::Status::PENDING}; + DialogUXStateAggregator::EngineConnectionStatus disconnected{ + ENGINE_TYPE_ALEXA_VOICE_SERVICES, + ConnectionStatusObserverInterface::ChangedReason::NONE, + ConnectionStatusObserverInterface::Status::DISCONNECTED}; + vector engineStatuses; + engineStatuses.push_back(connected); + engineStatuses.push_back(pending); + engineStatuses.push_back(disconnected); + m_aggregator->onConnectionStatusChanged(ConnectionStatusObserverInterface::Status::DISCONNECTED, engineStatuses); + assertNoStateChange(m_testObserver); +} + +/// Test that don't change the state is if THERE IS an active connection for a different engine typ +TEST_F(DialogUXAggregatorTest, test_doNotSetToIdleIfConnectionIsAvailableForDifferentEngine) { + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::IDLE); + m_aggregator->onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING); + assertStateChange(m_testObserver, DialogUXStateObserverInterface::DialogUXState::LISTENING); + + DialogUXStateAggregator::EngineConnectionStatus connected{OTHER_ENGINE_TYPE, + ConnectionStatusObserverInterface::ChangedReason::NONE, + ConnectionStatusObserverInterface::Status::CONNECTED}; + DialogUXStateAggregator::EngineConnectionStatus pending{ENGINE_TYPE_ALEXA_VOICE_SERVICES, + ConnectionStatusObserverInterface::ChangedReason::NONE, + ConnectionStatusObserverInterface::Status::PENDING}; + DialogUXStateAggregator::EngineConnectionStatus disconnected{ + ENGINE_TYPE_ALEXA_VOICE_SERVICES, + ConnectionStatusObserverInterface::ChangedReason::NONE, + ConnectionStatusObserverInterface::Status::DISCONNECTED}; + vector engineStatuses; + engineStatuses.push_back(connected); + engineStatuses.push_back(pending); + engineStatuses.push_back(disconnected); + m_aggregator->onConnectionStatusChanged(ConnectionStatusObserverInterface::Status::DISCONNECTED, engineStatuses); + assertNoStateChange(m_testObserver); +} + } // namespace test } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/test/Initialization/SDKPrimitivesProviderTest.cpp b/AVSCommon/AVS/test/Initialization/SDKPrimitivesProviderTest.cpp new file mode 100644 index 0000000000..ba7309d63c --- /dev/null +++ b/AVSCommon/AVS/test/Initialization/SDKPrimitivesProviderTest.cpp @@ -0,0 +1,271 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 SDKPrimitivesProviderTest.cpp + +#include +#include + +#include "AVSCommon/AVS/Initialization/SDKPrimitivesProvider.h" +#include "AVSCommon/Utils/Timing/TimerDelegateFactory.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace avs { +namespace initialization { +namespace test { + +using namespace std; + +/// Test harness for @c AlexaClientSDKInit class. +class SDKPrimitivesProviderTest : public ::testing::Test { +public: + /// Setup the test harness for running the test. + void SetUp() override; + + /// Clean up the test harness after running the test. + void TearDown() override; + + /// The SDKPrimitivesProvider instance to be tested. + std::shared_ptr m_primitivesProvider; + + /// The SDKPrimitivesProviderCopy instance to be tested. + std::shared_ptr m_primitivesProviderCopy; +}; + +void SDKPrimitivesProviderTest::SetUp() { + m_primitivesProvider = SDKPrimitivesProvider::getInstance(); + m_primitivesProviderCopy = SDKPrimitivesProvider::getInstance(); + ASSERT_NE(m_primitivesProvider, nullptr); + ASSERT_NE(m_primitivesProviderCopy, nullptr); +} + +void SDKPrimitivesProviderTest::TearDown() { + m_primitivesProvider->terminate(); + m_primitivesProviderCopy->terminate(); +} + +/** + * Tests @c getInstance and verifies that it is not created initialized, expecting to return @c false. + */ +TEST_F(SDKPrimitivesProviderTest, test_getInstanceNotInitialized) { + ASSERT_FALSE(m_primitivesProvider->isInitialized()); +} + +/** + * Tests @c getInstance and verifies that it is not created initialized using multiple references, expecting to return + * @c false from all references. + */ +TEST_F(SDKPrimitivesProviderTest, test_getInstanceMultipleNotInitialized) { + ASSERT_FALSE(m_primitivesProvider->isInitialized()); + ASSERT_FALSE(m_primitivesProviderCopy->isInitialized()); +} + +/** + * Tests @c getInstance and verifies that only a singleton is created, expecting both pointers to point to same object. + */ +TEST_F(SDKPrimitivesProviderTest, test_getInstanceSingleton) { + ASSERT_EQ(m_primitivesProvider, m_primitivesProviderCopy); +} + +/** + * Tests @c initialize and verifies that it does not initialize twice, expecting to return @c false. + */ +TEST_F(SDKPrimitivesProviderTest, test_initializeOnlyOnce) { + ASSERT_TRUE(m_primitivesProvider->initialize()); + ASSERT_FALSE(m_primitivesProvider->initialize()); +} + +/** + * Tests @c initialize and verifies that it does not initialize twice using multiple references, expecting to return @c + * false from all references. + */ +TEST_F(SDKPrimitivesProviderTest, test_initializeOnlyOnceUsingMultipleReferences) { + ASSERT_TRUE(m_primitivesProvider->initialize()); + ASSERT_FALSE(m_primitivesProvider->initialize()); + ASSERT_FALSE(m_primitivesProviderCopy->initialize()); +} + +/** + * Tests @c withTimerDelegateFactory, expecting to return @c true. + */ +TEST_F(SDKPrimitivesProviderTest, test_withTimerDelegateFactory) { + ASSERT_FALSE(m_primitivesProvider->isInitialized()); + auto timerDelegateFactory = std::make_shared(); + ASSERT_TRUE(m_primitivesProvider->withTimerDelegateFactory(timerDelegateFactory)); +} + +/** + * Tests @c withTimerDelegateFactory using multiple references, expecting to return @c true from all references. + */ +TEST_F(SDKPrimitivesProviderTest, test_withTimerDelegateFactoryUsingMultipleReferences) { + ASSERT_FALSE(m_primitivesProvider->isInitialized()); + auto timerDelegateFactory = std::make_shared(); + ASSERT_TRUE(m_primitivesProvider->withTimerDelegateFactory(timerDelegateFactory)); + ASSERT_TRUE(m_primitivesProviderCopy->withTimerDelegateFactory(timerDelegateFactory)); +} + +/** + * Tests @c withTimerDelegateFactory with null TimerDelegateFactory, expecting to return @c false. + */ +TEST_F(SDKPrimitivesProviderTest, test_withTimerDelegateFactoryNull) { + ASSERT_FALSE(m_primitivesProvider->withTimerDelegateFactory(nullptr)); +} + +/** + * Tests @c withTimerDelegateFactory with null TimerDelegateFactory using multiple references, expecting to return @c + * false from all references. + */ +TEST_F(SDKPrimitivesProviderTest, test_withTimerDelegateFactoryNullUsingMultipleReferences) { + ASSERT_FALSE(m_primitivesProvider->withTimerDelegateFactory(nullptr)); + ASSERT_FALSE(m_primitivesProviderCopy->withTimerDelegateFactory(nullptr)); +} + +/** + * Tests @c withTimerDelegateFactory already initialized, expecting to return @c false. + */ +TEST_F(SDKPrimitivesProviderTest, test_withTimerDelegateFactoryInitialized) { + ASSERT_TRUE(m_primitivesProvider->initialize()); + auto timerDelegateFactory = std::make_shared(); + ASSERT_FALSE(m_primitivesProvider->withTimerDelegateFactory(timerDelegateFactory)); +} + +/** + * Tests @c withTimerDelegateFactory already initialized using multiple references, expecting to return @c false from + * all references. + */ +TEST_F(SDKPrimitivesProviderTest, test_withTimerDelegateFactoryInitializedMultipleReferences) { + ASSERT_TRUE(m_primitivesProvider->initialize()); + auto timerDelegateFactory = std::make_shared(); + ASSERT_FALSE(m_primitivesProvider->withTimerDelegateFactory(timerDelegateFactory)); + ASSERT_FALSE(m_primitivesProviderCopy->withTimerDelegateFactory(timerDelegateFactory)); +} + +/** + * Tests @c getTimerDelegateFactory, expecting no errors. + */ +TEST_F(SDKPrimitivesProviderTest, test_getTimerDelegateFactory) { + m_primitivesProvider->initialize(); + m_primitivesProvider->getTimerDelegateFactory(); +} + +/** + * Tests @c getTimerDelegateFactory, expecting to return the same object. + */ +TEST_F(SDKPrimitivesProviderTest, test_getTimerDelegateFactoryManual) { + auto timerDelegateFactory = std::make_shared(); + ASSERT_TRUE(m_primitivesProvider->withTimerDelegateFactory(timerDelegateFactory)); + ASSERT_NE(m_primitivesProvider->getTimerDelegateFactory(), timerDelegateFactory); + m_primitivesProvider->initialize(); + ASSERT_EQ(m_primitivesProvider->getTimerDelegateFactory(), timerDelegateFactory); +} + +/** + * Tests @c getTimerDelegateFactory using multiple references, expecting to return the same object from all references. + */ +TEST_F(SDKPrimitivesProviderTest, test_getTimerDelegateFactoryManualMultipleReferences) { + auto timerDelegateFactory = std::make_shared(); + ASSERT_TRUE(m_primitivesProvider->withTimerDelegateFactory(timerDelegateFactory)); + ASSERT_NE(m_primitivesProvider->getTimerDelegateFactory(), timerDelegateFactory); + m_primitivesProvider->initialize(); + ASSERT_EQ(m_primitivesProvider->getTimerDelegateFactory(), timerDelegateFactory); + ASSERT_EQ(m_primitivesProviderCopy->getTimerDelegateFactory(), timerDelegateFactory); +} + +/** + * Tests @c getTimerDelegateFactory without initialization, expecting to return @c nullptr. + */ +TEST_F(SDKPrimitivesProviderTest, test_getTimerDelegateFactoryWithoutInitialzation) { + ASSERT_EQ(m_primitivesProvider->getTimerDelegateFactory(), nullptr); +} + +/** + * Tests @c getTimerDelegateFactory without initialization using multiple references, expecting to return @c nullptr + * from all references. + */ +TEST_F(SDKPrimitivesProviderTest, test_getTimerDelegateFactoryWithoutInitialzationMultipleReferences) { + ASSERT_EQ(m_primitivesProvider->getTimerDelegateFactory(), nullptr); + ASSERT_EQ(m_primitivesProviderCopy->getTimerDelegateFactory(), nullptr); +} + +/** + * Tests @c isInitialized and verifies that it behaves correctly after initialization. + */ +TEST_F(SDKPrimitivesProviderTest, test_isInitialized) { + ASSERT_FALSE(m_primitivesProvider->isInitialized()); + ASSERT_TRUE(m_primitivesProvider->initialize()); + ASSERT_TRUE(m_primitivesProvider->isInitialized()); + m_primitivesProvider->terminate(); + ASSERT_FALSE(m_primitivesProvider->isInitialized()); +} + +/** + * Tests @c reset and verify that it correctly resets the timerDelegateFactory. + */ +TEST_F(SDKPrimitivesProviderTest, test_resetTimerDelegateFactory) { + ASSERT_FALSE(m_primitivesProvider->isInitialized()); + ASSERT_TRUE(m_primitivesProvider->initialize()); + ASSERT_TRUE(m_primitivesProvider->isInitialized()); + m_primitivesProvider->reset(); + ASSERT_EQ(m_primitivesProvider->getTimerDelegateFactory(), nullptr); +} + +/** + * Tests @c reset and verify that it correctly uninititalizes. + */ +TEST_F(SDKPrimitivesProviderTest, test_resetUninitializes) { + ASSERT_FALSE(m_primitivesProvider->isInitialized()); + ASSERT_TRUE(m_primitivesProvider->initialize()); + ASSERT_TRUE(m_primitivesProvider->isInitialized()); + m_primitivesProvider->reset(); + ASSERT_FALSE(m_primitivesProvider->isInitialized()); +} + +/** + * Tests @c reset using multiple references and verify that it correctly uninititalizes. + */ +TEST_F(SDKPrimitivesProviderTest, test_resetUninitializesMultipleReferences) { + ASSERT_FALSE(m_primitivesProvider->isInitialized()); + ASSERT_TRUE(m_primitivesProvider->initialize()); + ASSERT_TRUE(m_primitivesProvider->isInitialized()); + m_primitivesProvider->reset(); + ASSERT_FALSE(m_primitivesProvider->isInitialized()); + ASSERT_FALSE(m_primitivesProviderCopy->isInitialized()); +} + +/** + * Tests @c terminate without initialization and makes sure that multiple calls do not cause errors. + */ +TEST_F(SDKPrimitivesProviderTest, test_terminateMultipleTimesWithoutInitialization) { + ASSERT_EQ(m_primitivesProvider, m_primitivesProviderCopy); + m_primitivesProvider->terminate(); + m_primitivesProviderCopy->terminate(); +} + +/** + * Tests @c terminate with initialization and makes sure that multiple calls do not cause errors. + */ +TEST_F(SDKPrimitivesProviderTest, test_terminateMultipleTimesWithInitialization) { + m_primitivesProvider->initialize(); + ASSERT_EQ(m_primitivesProvider, m_primitivesProviderCopy); + m_primitivesProvider->terminate(); + m_primitivesProviderCopy->terminate(); +} + +} // namespace test +} // namespace initialization +} // namespace avs +} // namespace avsCommon +} // namespace alexaClientSDK \ No newline at end of file diff --git a/AVSCommon/CMakeLists.txt b/AVSCommon/CMakeLists.txt index 981fdb7248..0ff9cb433b 100644 --- a/AVSCommon/CMakeLists.txt +++ b/AVSCommon/CMakeLists.txt @@ -6,7 +6,7 @@ add_subdirectory("AVS") add_subdirectory("SDKInterfaces") add_subdirectory("Utils") -add_library(AVSCommon SHARED +add_library(AVSCommon AVS/src/AVSContext.cpp AVS/src/AVSDirective.cpp AVS/src/AVSMessage.cpp @@ -114,12 +114,15 @@ add_library(AVSCommon SHARED Utils/src/UUIDGeneration.cpp Utils/src/WaitEvent.cpp Utils/src/WavUtils.cpp - Utils/src/WorkerThread.cpp) + Utils/src/WorkerThread.cpp + ${FileSystemUtils_SOURCE}) target_include_directories(AVSCommon PUBLIC "${AVSCommon_SOURCE_DIR}/AVS/include" "${AVSCommon_SOURCE_DIR}/SDKInterfaces/include" "${AVSCommon_SOURCE_DIR}/Utils/include" + "${AVSCommon_BINARY_DIR}/Utils/include" + "${AVSCommon_SOURCE_DIR}/../Captions/Interface/include" "${RAPIDJSON_INCLUDE_DIR}" "${MultipartParser_SOURCE_DIR}" ${CURL_INCLUDE_DIRS}) @@ -149,4 +152,5 @@ target_link_libraries(AVSCommon LIST(APPEND PATHS "${PROJECT_SOURCE_DIR}/AVS/include") LIST(APPEND PATHS "${PROJECT_SOURCE_DIR}/SDKInterfaces/include") LIST(APPEND PATHS "${PROJECT_SOURCE_DIR}/Utils/include") +LIST(APPEND PATHS "${PROJECT_BINARY_DIR}/Utils/include") asdk_install_multiple("${PATHS}") diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Endpoints/EndpointBuilderInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Endpoints/EndpointBuilderInterface.h index d22977fa96..a995b25615 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Endpoints/EndpointBuilderInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Endpoints/EndpointBuilderInterface.h @@ -91,6 +91,21 @@ class EndpointBuilderInterface : public EndpointCapabilitiesRegistrarInterface { */ virtual EndpointBuilderInterface& withDerivedEndpointId(const std::string& suffix) = 0; + /** + * Includes default Registration information to the endpoint. + * + * @note This will include default endpoint's registration information into the endpoint. + * When this payload is included into the endpoint's discovery, cloud will be able to tell this endpoint + * represents the same device that the client is running on. + * + * @warning Do not use this function unless you want the new endpoint to be treated as same as the AVS endpoint + * that maintains HTTP2 connection. This function is added to support a legacy case and might be deprecated later. + * Please be aware of the risk while using this function. + * + * @return This builder which can be used to nest configuration function calls. + */ + virtual EndpointBuilderInterface& withDeviceRegistration() = 0; + /** * Configures builder to use the given identifier for the new endpoint. * diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/LocalPlaybackHandlerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/LocalPlaybackHandlerInterface.h index 7488ea1a13..cee7d4369b 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/LocalPlaybackHandlerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/LocalPlaybackHandlerInterface.h @@ -34,8 +34,19 @@ class LocalPlaybackHandlerInterface { */ virtual ~LocalPlaybackHandlerInterface() = default; - /// Available local operations. - enum PlaybackOperation { STOP_PLAYBACK, PAUSE_PLAYBACK, RESUME_PLAYBACK }; + /* + * Enumeration of the available local operations. + */ + enum PlaybackOperation { + /// Stop playback, close pipeline + STOP_PLAYBACK, + /// Stop playback, keep pipeline open (for a time), to enable resume + RESUMABLE_STOP, + /// Resume playing after RESUMABLE_STOP, or TRANSIENT_PAUSE. + RESUME_PLAYBACK, + /// Transiently pause playback - this is intended to be for a very short period. Not resumable from cloud + TRANSIENT_PAUSE + }; /** * Request the handler to perform a local playback operation. diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/LocaleAssetsManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/LocaleAssetsManagerInterface.h index 18b1536da1..cf34a932cc 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/LocaleAssetsManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/LocaleAssetsManagerInterface.h @@ -109,10 +109,19 @@ class LocaleAssetsManagerInterface : public CapabilityConfigurationChangeObserve /** * Get the default locale. * + * @deprecated Use getDefaultLocales + * * @return The default locale. */ virtual Locale getDefaultLocale() const = 0; + /** + * Get the default multilingual locales. + * + * @return The default multilingual locales. + */ + virtual Locales getDefaultLocales() const; + /** * Get the default valid concurrent wake words sets. * @@ -176,6 +185,10 @@ class LocaleAssetsManagerInterface : public CapabilityConfigurationChangeObserve virtual ~LocaleAssetsManagerInterface() = default; }; +inline LocaleAssetsManagerInterface::Locales LocaleAssetsManagerInterface::getDefaultLocales() const { + return {getDefaultLocale()}; +} + } // namespace sdkInterfaces } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PowerResourceManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PowerResourceManagerInterface.h index 1c25a0d199..ae5f9bc2e9 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PowerResourceManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PowerResourceManagerInterface.h @@ -155,6 +155,9 @@ class PowerResourceManagerInterface { * Acquires the time since latest partial low power mode state change. * This API should only be called after a power resource has been acquired. * @param component component name. + * @param resourceFlags in/out parameter to get resource flags which exit lpm state last. + * This parameter is passed by the caller of this API and it gets updated with the bit + * pattern which represents one bit position for each resource type.Ref:enum PowerResourceTypeIndex * @param partialState the partial low power mode state (PowerResourceTypeFlags) to check. * The state type is determined based on the bits that are passed in. For example, if * TYPE_CPU is passed in then the time since the most recent CPU low power mode state will @@ -166,6 +169,7 @@ class PowerResourceManagerInterface { */ virtual std::chrono::milliseconds getTimeSinceLastPartialMS( const std::string& component, + PartialStateBitSet& resourceFlags, PartialStateBitSet partialState = PowerResourceTypeFlag::TYPE_ALL_FLAG); /** @@ -244,11 +248,13 @@ inline std::chrono::milliseconds PowerResourceManagerInterface::getTimeSinceLast /** * Provides the default @c PowerResourceManagerInterface time since last partial in MS. * @param component component name. + * @param resourceFlags in/out parameter to get resource flags which exit lpm state last. * @param partialState the partial low power mode state (PowerResourceTypeFlags) to check. * @return Return default value of 0 milliseconds in the form of std::chrono::milliseconds. */ inline std::chrono::milliseconds PowerResourceManagerInterface::getTimeSinceLastPartialMS( const std::string& component, + PartialStateBitSet& resourceFlags, PartialStateBitSet partialState) { return std::chrono::milliseconds::zero(); } diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/StateProviderInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/StateProviderInterface.h index c2603f0fd3..563b1244bf 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/StateProviderInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/StateProviderInterface.h @@ -72,9 +72,10 @@ class StateProviderInterface { virtual void provideState(const avs::CapabilityTag& stateProviderName, const ContextRequestToken stateRequestToken); /** - * Returns whether the provider should be queried for its state / properties. + * Returns whether the provider can be queried for its state / properties. + * If not, the provider is omitted from the context altogether. ContextManager will not query or report its state. * - * @return Whether this provider should be queried about its state when a new context request arrives. + * @return Whether this provider can be queried about its state when a new context request arrives. * @note In future versions, this method will be made pure virtual. */ virtual bool canStateBeRetrieved(); @@ -86,6 +87,14 @@ class StateProviderInterface { * @return Whether this provider has reportable state properties. */ virtual bool hasReportableStateProperties(); + + /** + * Returns whether the provider should be queried for its state / properties. + * If this returns false the last cached state will be reported to the context requester. + * + * @return whether the provider should be queried for its state / properties. + */ + virtual bool shouldQueryState(); }; inline void StateProviderInterface::provideState( @@ -109,6 +118,10 @@ inline bool StateProviderInterface::hasReportableStateProperties() { return false; } +inline bool StateProviderInterface::shouldQueryState() { + return true; +} + } // namespace sdkInterfaces } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAuthObserver.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAuthObserver.h new file mode 100644 index 0000000000..6ff43253ad --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockAuthObserver.h @@ -0,0 +1,39 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_MOCKAUTHOBSERVER_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKAUTHOBSERVER_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace test { + +/// A mock object that implements the @c AuthObserverInterface. +class MockAuthObserver : public AuthObserverInterface { +public: + MOCK_METHOD2(onAuthStateChange, void(State newState, Error error)); +}; + +} // namespace test +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKAUTHOBSERVER_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockCapabilityConfigurationInterface.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockCapabilityConfigurationInterface.h new file mode 100644 index 0000000000..e856d1c78e --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockCapabilityConfigurationInterface.h @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_MOCKCAPABILITYCONFIGURATIONINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKCAPABILITYCONFIGURATIONINTERFACE_H_ + +#include +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace test { + +/// Mock class that implements the CapabilityConfigurationInterface. +class MockCapabilityConfigurationInterface : public CapabilityConfigurationInterface { +public: + MOCK_METHOD0( + getCapabilityConfigurations, + std::unordered_set>()); +}; + +} // namespace test +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_MOCKCAPABILITYCONFIGURATIONINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockDirectiveHandler.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockDirectiveHandler.h index aa08aa0653..93c2e7d93c 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockDirectiveHandler.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockDirectiveHandler.h @@ -67,7 +67,7 @@ class MockDirectiveHandler : public DirectiveHandlerMockAdapter { const std::shared_ptr&)); }; -void DirectiveHandlerMockAdapter::preHandleDirective( +inline void DirectiveHandlerMockAdapter::preHandleDirective( std::shared_ptr avsDirective, std::unique_ptr handler) { if (handler) { diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockLocaleAssetsManager.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockLocaleAssetsManager.h index 184a6a0496..fc5e4fe278 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockLocaleAssetsManager.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockLocaleAssetsManager.h @@ -45,6 +45,7 @@ class MockLocaleAssetsManager : public LocaleAssetsManagerInterface { MOCK_CONST_METHOD0(getSupportedLocales, std::set()); MOCK_CONST_METHOD0(getSupportedLocaleCombinations, LocaleCombinations()); MOCK_CONST_METHOD0(getDefaultLocale, Locale()); + MOCK_CONST_METHOD0(getDefaultLocales, Locales()); }; } // namespace test diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/MockMiscStorage.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/MockMiscStorage.h new file mode 100644 index 0000000000..aa4695f87c --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/MockMiscStorage.h @@ -0,0 +1,103 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_MOCKMISCSTORAGE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_STORAGE_MOCKMISCSTORAGE_H_ + +#include + +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace storage { +namespace test { + +/** + * Mock for @c MiscStorageInterface. + */ +class MockMiscStorage : public avsCommon::sdkInterfaces::storage::MiscStorageInterface { +public: + /// @name MiscStorageInterface functions + /// @{ + MOCK_METHOD0(createDatabase, bool()); + MOCK_METHOD0(open, bool()); + MOCK_METHOD0(isOpened, bool()); + MOCK_METHOD0(close, void()); + MOCK_METHOD4( + createTable, + bool(const std::string& componentName, const std::string& tableName, KeyType keyType, ValueType valueType)); + MOCK_METHOD2(clearTable, bool(const std::string& componentName, const std::string& tableName)); + MOCK_METHOD2(deleteTable, bool(const std::string& componentName, const std::string& tableName)); + MOCK_METHOD4( + get, + bool( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + std::string* value)); + MOCK_METHOD4( + add, + bool( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value)); + MOCK_METHOD4( + update, + bool( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value)); + MOCK_METHOD4( + put, + bool( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + const std::string& value)); + MOCK_METHOD3(remove, bool(const std::string& componentName, const std::string& tableName, const std::string& key)); + MOCK_METHOD4( + tableEntryExists, + bool( + const std::string& componentName, + const std::string& tableName, + const std::string& key, + bool* tableEntryExistsValue)); + MOCK_METHOD3( + tableExists, + bool(const std::string& componentName, const std::string& tableName, bool* tableExistsValue)); + MOCK_METHOD3( + load, + bool( + const std::string& componentName, + const std::string& tableName, + std::unordered_map* valueContainer)); + ///@} +}; + +} // namespace test +} // namespace storage +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_STORAGE_MOCKMISCSTORAGE_H_ diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/StubMiscStorage.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/StubMiscStorage.h index 20309c3fa6..d1e99c52bc 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/StubMiscStorage.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Storage/StubMiscStorage.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -96,12 +97,29 @@ class StubMiscStorage : public avsCommon::sdkInterfaces::storage::MiscStorageInt ///@} private: + /// Constructor. StubMiscStorage(); + /** + * A function to clear the table identified by componentName and tableName. + * + * m_mutex must be held before calling this. + * + * @param componentName The component name. + * @param tableName The table name. + * @return Whether the operation was succesful. + */ + bool clearTableLocked(const std::string& componentName, const std::string& tableName); + + // Mutex. + std::mutex m_mutex; + /// 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; }; diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Timing/MockTimerDelegateFactory.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Timing/MockTimerDelegateFactory.h new file mode 100644 index 0000000000..0f9c9b0074 --- /dev/null +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Timing/MockTimerDelegateFactory.h @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_TIMING_MOCKTIMERDELEGATEFACTORY_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_TIMING_MOCKTIMERDELEGATEFACTORY_H_ + +#include +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { +namespace test { + +/// A mock of @c TimerDelegateFactoryInterface. +class MockTimerDelegateFactory : public timing::TimerDelegateFactoryInterface { +public: + MOCK_METHOD0(supportsLowPowerMode, bool()); + MOCK_METHOD0(getTimerDelegate, std::unique_ptr()); +}; + +} // namespace test +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_TEST_AVSCOMMON_SDKINTERFACES_TIMING_MOCKTIMERDELEGATEFACTORY_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/test/src/StubMiscStorage.cpp b/AVSCommon/SDKInterfaces/test/src/StubMiscStorage.cpp index 45fc962e1d..c060d9ad87 100644 --- a/AVSCommon/SDKInterfaces/test/src/StubMiscStorage.cpp +++ b/AVSCommon/SDKInterfaces/test/src/StubMiscStorage.cpp @@ -24,15 +24,18 @@ namespace storage { namespace test { bool StubMiscStorage::createDatabase() { + std::lock_guard lock(m_mutex); return true; } bool StubMiscStorage::open() { + std::lock_guard lock(m_mutex); m_isOpened = true; return true; } void StubMiscStorage::close() { + std::lock_guard lock(m_mutex); m_isOpened = false; } @@ -41,12 +44,14 @@ bool StubMiscStorage::createTable( const std::string& tableName, MiscStorageInterface::KeyType keyType, MiscStorageInterface::ValueType valueType) { + std::lock_guard lock(m_mutex); + std::string key = componentName + ":" + tableName; m_tables.insert(key); return true; } -bool StubMiscStorage::clearTable(const std::string& componentName, const std::string& tableName) { +bool StubMiscStorage::clearTableLocked(const std::string& componentName, const std::string& tableName) { std::string keyPrefix = componentName + ":" + tableName + ":"; auto it = m_storage.begin(); while (it != m_storage.end()) { @@ -58,13 +63,22 @@ bool StubMiscStorage::clearTable(const std::string& componentName, const std::st ++it; } } + return true; } +bool StubMiscStorage::clearTable(const std::string& componentName, const std::string& tableName) { + std::lock_guard lock(m_mutex); + + return clearTableLocked(componentName, tableName); +} + bool StubMiscStorage::deleteTable(const std::string& componentName, const std::string& tableName) { + std::lock_guard lock(m_mutex); + std::string key = componentName + ":" + tableName; m_tables.erase(key); - return clearTable(componentName, tableName); + return clearTableLocked(componentName, tableName); } bool StubMiscStorage::get( @@ -72,6 +86,8 @@ bool StubMiscStorage::get( const std::string& tableName, const std::string& key, std::string* value) { + std::lock_guard lock(m_mutex); + std::string keyStr = componentName + ":" + tableName + ":" + key; auto it = m_storage.find(keyStr); if (m_storage.end() == it) { @@ -102,12 +118,16 @@ bool StubMiscStorage::put( const std::string& tableName, const std::string& key, const std::string& value) { + std::lock_guard lock(m_mutex); + 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::lock_guard lock(m_mutex); + std::string keyStr = componentName + ":" + tableName + ":" + key; m_storage.erase(keyStr); return true; @@ -118,6 +138,8 @@ bool StubMiscStorage::tableEntryExists( const std::string& tableName, const std::string& key, bool* tableEntryExistsValue) { + std::lock_guard lock(m_mutex); + std::string keyStr = componentName + ":" + tableName + ":" + key; auto it = m_storage.find(keyStr); *tableEntryExistsValue = m_storage.end() != it; @@ -128,6 +150,8 @@ bool StubMiscStorage::tableExists( const std::string& componentName, const std::string& tableName, bool* tableExistsValue) { + std::lock_guard lock(m_mutex); + std::string key = componentName + ":" + tableName; bool exists = m_tables.end() != m_tables.find(key); *tableExistsValue = exists; @@ -138,6 +162,11 @@ bool StubMiscStorage::load( const std::string& componentName, const std::string& tableName, std::unordered_map* valueContainer) { + std::lock_guard lock(m_mutex); + if (!valueContainer) { + return false; + } + std::string keyStr = componentName + ":" + tableName + ":"; size_t keyLen = keyStr.length(); for (const auto& it : m_storage) { @@ -147,7 +176,6 @@ bool StubMiscStorage::load( valueContainer->insert(std::pair(targetKey, it.second)); } } - *valueContainer = m_storage; return true; } @@ -159,6 +187,8 @@ StubMiscStorage::StubMiscStorage() : m_isOpened{false} { } bool StubMiscStorage::isOpened() { + std::lock_guard lock(m_mutex); + return m_isOpened; } diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/FileSystem/FileSystemUtils.h b/AVSCommon/Utils/include/AVSCommon/Utils/FileSystem/FileSystemUtils.h new file mode 100644 index 0000000000..63429ff874 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/FileSystem/FileSystemUtils.h @@ -0,0 +1,250 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +#if defined(FILE_SYSTEM_UTILS_ENABLED) + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_FILESYSTEM_FILESYSTEMUTILS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_FILESYSTEM_FILESYSTEMUTILS_H_ + +#include +#include + +/** + * This utility file adds a few helper functions to interact with the file system. + * + * This synchronizes the interactions with the file system across different operating systems. + * A reference implementation will be provided for the following systems by default: + * Linux, Mac OS, Raspberry Pi, Android, and Windows (UWP & Win32) + * + * If a platform is not supported and a custom implementation is not provided, then FILE_SYSTEM_UTILS_ENABLED will not + * be defined and these utilities will not be available or compiled. + * + * @note Thread Safety: These utility provide a certain level of thread safety when it comes to using the specific APIs. + * For example, traversing a directory with list will synchronize the various calls from within the process. + * However, the effect of these utilities on the filesystem is not synchronized, and performing various unsynchronized + * operations on the same set of directories can lead to unpredictable behavior. + * For example, deleting a directory while listing it's content or returning its size. + * + * @note Permissions: Some systems do not differentiate between owner/group/other. For those systems (ie. Windows), + * setting any attribute (read/write/exec) will set it for all. Some systems may also ignore some of these permissions. + * + * @note Case-Sensitivity: Some OS have case-insensitive filesystem (such as Windows by default) while others have + * case-sensitive filesystem (such as Linux based OS). These utilities do not make a hard distinction between those and + * the expectation for search and file checks will be dependant on the OS. + * + * @note Path Delimiters: Some use '/' as a directory delimiter, while others use '\'. This utility will always accept + * '/' as a valid delimiter. + * For OS that use '\' (such as Windows) then '\' will also be accepted. + * For functions that return a path, then the path returned will use the default delimiter used by the OS. + */ +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace filesystem { + +/// Permission mask used to set the permissions of a file or directory +using Permissions = uint32_t; + +/** + * Permission bits used to distinguish the various permissions which can be set for a file or directory. + * If a platform does not differentiate between owner/group/other, then setting read/write for any will set for all. + */ +enum PermBits : Permissions { + NO_PERM = 0000, + + // Owner permissions + OWNER_ALL = 0700, + OWNER_READ = 0400, + OWNER_WRITE = 0200, + OWNER_EXEC = 0100, + + // Group permissions + GROUP_ALL = 0070, + GROUP_READ = 0040, + GROUP_WRITE = 0020, + GROUP_EXEC = 0010, + + // Everyone else's permissions + OTHERS_ALL = 0007, + OTHERS_READ = 0004, + OTHERS_WRITE = 0002, + OTHERS_EXEC = 0001 +}; + +/** Default Permissions for directories, Read/Write/Exec for user, Read/Exec for group, none for everyone else. */ +extern const Permissions DEFAULT_DIRECTORY_PERMISSIONS; +/** Default Permissions for files, Read/Write for user, Read for group, none for everyone else. */ +extern const Permissions DEFAULT_FILE_PERMISSIONS; + +/** + * Enum for distinguishing different file types to drive various operations. + * + * REGULAR_FILE: Normal file types (not symlinks, block, character, fifo, or socket files) + * DIRECTORY: Directory file type (does not include symbolic to directories). + * ALL: All of the above supported types. + * + * @note hidden files or .files are considered Regular Files. + */ +enum class FileType { REGULAR_FILE, DIRECTORY, ALL }; + +/** + * Changes the permissions of a given file or directory. + * @note the outcome of this operation depends on the OS, see Permissions note above. + * + * @param path relative (to currentDirectory) or absolute path to a file or directory. + * @param perms new permissions to override the existing ones. + * @return true if the operation was successful, false otherwise. + */ +bool changePermissions(const std::string& path, Permissions perms); + +/** + * Checks if a file or directory already exists. + * + * @param path absolute or relative (to currentDirectory) path to a file or directory. + * @return true if the file or directory exists, false otherwise. + * + * @note If you do not have permissions for this path, then this will return false even if it exists. + */ +bool exists(const std::string& path); + +/** + * Retrieves the current directory, usually the directory from which the program was started. + * + * @return current directory path. + * + * @note the delimiter used for the returned path will be dependant on the OS default path delimiter. + */ +std::string currentDirectory(); + +/** + * Makes a directory on the given path with the provided permissions. + * + * @param path absolute path to a directory to be created. + * @param perms Optional permissions to set the directory when created (or change them if they already exists). + * @return true if the directory was successfully created or already exists. + * + * @note This will create all the necessary parent directories up until the provided path if they do not exist. + * @note Any directory created recursively by this method will be set to the given permissions. If the final directory + * already exists, then this method will attempt to set the permission of the given directory according to the given + * param, and will return true only if the change permission operation succeeds. + */ +bool makeDirectory(const std::string& path, Permissions perms = DEFAULT_DIRECTORY_PERMISSIONS); + +/** + * Returns a list of directories, or files, or both in a given path. + * + * @param path absolute or relative (to currentDirectory) path to a given directory. + * @param type Optional file type to list, defaults to all files and directories. + * @return a standard vector of the desired file types found in the given path. + * + * @note This does not include ".", "..", any links, fifo, socket, or other special files that are not explicitly + * called out as supported file types by FileType enum (see enum description). + * @note Changing the content of the given directory in the middle of this call can lead to unknown behavior. + */ +std::vector list(const std::string& path, FileType type = FileType::ALL); + +/** + * Moves/renames a source path (file or directory) to a new destination. + * + * @param source existing path to be moved or renamed. + * @param destination desired destination to move to, the parent path of the destination must exist. + * @return true if the move was successful, false otherwise. + */ +bool move(const std::string& source, const std::string& destination); + +/** + * Returns the basename from a given path (regardless if it exists or not). + * + * "/some/file.txt" --> "file" + * "/some/dir/" --> "dir" + * "/some/dir/.." --> ".." + * "." --> "." + * "/" --> "" + * "" --> "" + * + * @param path relative (to currentDirectory) or absolute path to a file or directory. + * @return basename of the given path. + */ +std::string basenameOf(const std::string& path); + +/** + * Returns the directory name of a given path with a trailing '/' (regardless if it exists or not). + * @note if an OS has a concept of drives (ie. Windows) then it will return the drive letter if provided in the path. + * + * "/some/file.txt" --> "/some/" + * "/some/dir/" --> "/some/" + * "/some/dir/.." --> "/some/dir/" + * "." --> "./" + * "/" --> "/" + * "" --> "./" + * "C:/path/file" --> "C:/path" + * + * @param path relative (to currentDirectory) or absolute path to a file or directory. + * @return dirname of the given path. + * + * @note the delimiter used for the returned path will be dependant on the OS default path delimiter. + */ +std::string parentDirNameOf(const std::string& path); + +/** + * Removes a given file or recursively removes all files in a given directory. + * + * @param path file or directory to remove recursively. + * @return true if the path was removed completely or did not exist in the first place, false otherwise. + * + * @note This does not follow any symbolic links to directories outside the given path. Only the links will be removed. + */ +bool removeAll(const std::string& path); + +/** + * Get the size in bytes of a given file or directory recursively. If a directory is given, then this will return the + * sum of all the file sizes recursively under the given directory, without counting the size of each directory. + * This function does not follow any symbolic links, but simply return the size of the link itself. + * + * @param path file or directory for which to calculate the size. + * @return size of the given path in bytes. + * + * @note Changing the content of the given directory in the middle of this call can lead to unknown behavior. + */ +size_t sizeOf(const std::string& path); + +/** + * Get the number of bytes available for writing on a given path. + * + * @param path of the directory that will be written to (directory must exist). + * @return number of bytes available to write. + * + * @note The result will only provide the size available at the time of calling this function, caller is responsible + * for synchronizing space allocation while utilizing this function to avoid running out of space. + */ +size_t availableSpace(const std::string& path); + +/** + * Assert that the given path starts with the prefix after resolving any file traversal tokens or symlinks. + * + * @param path of the directory that will be checked. + * @param prefix the expected prefix of the path after resolution. + * @return true if the path starts with prefix. + */ +bool pathContainsPrefix(const std::string& path, const std::string& prefix); + +} // namespace filesystem +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_FILESYSTEM_FILESYSTEMUTILS_H_ + +#endif // FILE_SYSTEM_UTILS_ENABLED diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlEasyHandleWrapper.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlEasyHandleWrapper.h index aa9cb8d1e5..69976e2b84 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlEasyHandleWrapper.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlEasyHandleWrapper.h @@ -18,18 +18,16 @@ #include #include +#include #include #include +#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 { @@ -343,15 +341,6 @@ class CurlEasyHandleWrapper { * @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 /// Initializes the @c m_interfaceName from config. @@ -380,6 +369,15 @@ class CurlEasyHandleWrapper { CurlEasyHandleWrapperOptionsSettingAdapter m_curlOptionsSettingAdapter; + /// 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; + friend class CurlEasyHandleWrapperOptionsSettingAdapter; }; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HTTPResponse.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HTTPResponse.h index 6eb747bafe..75847d526b 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HTTPResponse.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/HTTPResponse.h @@ -33,6 +33,14 @@ struct HTTPResponse { */ HTTPResponse(); + /** + * Constructor with params. + * + * @param pCode The status code. + * @param pBody The response body. + */ + HTTPResponse(long pCode, const std::string& pBody); + /// The HTTP status code returned by the server. long code; @@ -43,9 +51,6 @@ struct HTTPResponse { std::string serialize(); }; -inline HTTPResponse::HTTPResponse() : code(0) { -} - } // namespace libcurlUtils } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Request.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Request.h index 0b3aaeaef5..0d40c12f0f 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Request.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Request.h @@ -189,6 +189,9 @@ class LibcurlHTTP2Request : public alexaClientSDK::avsCommon::utils::http2::HTTP /// Whether this request has been cancelled. std::atomic_bool m_isCancelled; + + /// Connect timeout. + std::chrono::milliseconds m_connectTimeout; }; void LibcurlHTTP2Request::setTimeOfLastTransfer() { diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h index f77920af2a..93316ca196 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h @@ -54,16 +54,8 @@ class 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); + template + LogEntry& d(const std::string& key, const ValueType& value); /** * Add a @c key, @c value pair to the metadata of this log entry. @@ -73,15 +65,6 @@ class LogEntry { */ LogEntry& d(const char* key, const char* 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. - */ - 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. * @@ -91,15 +74,6 @@ class LogEntry { */ LogEntry& d(const char* 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 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. * @@ -179,7 +153,7 @@ class LogEntry { * @param ptr The raw pointer of the object to add to this LogEntry. * @return This instance to facilitate adding more information to this log entry. */ - LogEntry& p(const char* key, void* ptr); + LogEntry& p(const char* key, const void* ptr); /** * Get the rendered text of this LogEntry. @@ -222,6 +196,11 @@ class LogEntry { LogEntryStream m_stream; }; +template +inline LogEntry& LogEntry::d(const std::string& key, const ValueType& value) { + return d(key.c_str(), value); +} + template LogEntry& LogEntry::d(const char* key, const ValueType& value) { prefixKeyValuePair(); diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaDescription.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaDescription.h index 26be2a0a7f..fb1c8db68e 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaDescription.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaDescription.h @@ -17,6 +17,13 @@ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_MEDIADESCRIPTION_H_ #include +#include + +#include +#include +#include +#include +#include namespace alexaClientSDK { namespace avsCommon { @@ -30,8 +37,26 @@ static std::string PLAY_BEHAVIOR = "playBehavior"; * An object that contains all playback related information needed from the media CA. */ struct MediaDescription { + /// Mixing behavior of the stream. + sdkInterfaces::audio::MixingBehavior mixingBehavior; + + /// Focus channel identifies the content type acquiring focus following FocusManger naming convention. + std::string focusChannel; // "Dialog", "Communications", "Alert", "Content", "Visual" + + /// String identifier of the source. + std::string trackId; + + /// Object that contains CaptionData with unprocessed caption content and metadata of a particular format. + Optional caption; + + /// Audio analyzers used to process provided audio content. + Optional> analyzers; + /// All additional information to be provided, including PlayBehavior. std::unordered_map additionalData; + + /// Are all of the required values in the Media Description struct set + bool enabled; }; /** @@ -39,7 +64,13 @@ struct MediaDescription { * @return an empty Media Description object. */ inline MediaDescription emptyMediaDescription() { - return MediaDescription{{}}; + return MediaDescription{sdkInterfaces::audio::MixingBehavior(), + "", + "", + Optional(), + Optional>(), + {}, + false}; } /** @@ -50,12 +81,36 @@ inline MediaDescription emptyMediaDescription() { * @return The stream that was passed in and written to. */ inline std::ostream& operator<<(std::ostream& stream, const MediaDescription& mediaDescription) { - stream << "AdditionalData:{"; + switch (mediaDescription.mixingBehavior) { + case sdkInterfaces::audio::MixingBehavior::BEHAVIOR_PAUSE: + stream << "BEHAVIOR_PAUSE"; + break; + case sdkInterfaces::audio::MixingBehavior::BEHAVIOR_DUCK: + stream << "BEHAVIOR_DUCK"; + break; + } + stream << ", Channel:" << mediaDescription.focusChannel << ", "; + stream << ", TrackId:" << mediaDescription.trackId; + if (mediaDescription.caption.hasValue()) { + stream << ", CaptionData:{format:" << (mediaDescription.caption.value()).format; + stream << ", content:" << (mediaDescription.caption.value()).content << "}"; + } + if (mediaDescription.analyzers.hasValue()) { + stream << ", Analyzers:{"; + const auto analyzersCopy = mediaDescription.analyzers.value(); + for (auto iter = analyzersCopy.begin(); iter != analyzersCopy.end(); iter++) { + stream << "{name:" << (*iter).name; + stream << ", enableState:" << (*iter).enableState << "}"; + } + stream << "}"; + } + stream << ", AdditionalData:{"; const auto additionalDataCopy = mediaDescription.additionalData; for (auto iter = additionalDataCopy.begin(); iter != additionalDataCopy.end(); iter++) { stream << "{" << (*iter).first; stream << ":" << (*iter).second << "}"; } + stream << "}, enabled: " << (mediaDescription.enabled ? "true" : "false") << " }"; return stream; } diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h b/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h index 1a35e30792..e47afd3c1c 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h @@ -16,17 +16,20 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLATFORMDEFINITIONS_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLATFORMDEFINITIONS_H_ -/* -This file contains wrappers macros to help with compatibility with MSVC, -future operating system helper macros should be placed here. -*/ +/** + * @file + * This file contains wrappers macros to help with compatibility with MSVC, future operating system helper macros should + * be placed here. + */ + +#include #if defined(_MSC_VER) #include typedef SSIZE_T ssize_t; #endif -#if defined(_MSC_VER) +#if defined(_MSC_VER) && defined(ACSDK_CONFIG_SHARED_LIBS) #if defined(IN_AVSCOMMON) #define avscommon_EXPORT __declspec(dllexport) #else diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDKConfig.h.in b/AVSCommon/Utils/include/AVSCommon/Utils/SDKConfig.h.in new file mode 100644 index 0000000000..f7eecde400 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDKConfig.h.in @@ -0,0 +1,48 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_SDKCONFIG_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_SDKCONFIG_H_ + +/** + * @file + * @brief ACSDK Global Configuration Parameters. + * + * This file includes macros that specify which options are used during the build for header installation. By convention + * all CMake defines should start wiht @a ACSDK_CONFIG_ prefix. + * + * CMake defines or undefines macros depending on build options, and for documentation consistency all macros must be + * documented with an explict @a @@def commands. + * + * This file contains ACSDK-global definitions. Components shall use own component-specific configuration files. + */ + +/** + * @def ACSDK_CONFIG_STATIC_LIBS + * @brief Macro to indicate the build generates static libraries. + * + * When defined, this macro indicates that ACSDK build produces static libraries. + */ +#cmakedefine ACSDK_CONFIG_STATIC_LIBS + +/** + * @def ACSDK_CONFIG_SHARED_LIBS + * @brief Macro to indicate the build generates shared libraries. + * + * When defined, this macro indicates that ACSDK build produces shared libraries. + */ +#cmakedefine ACSDK_CONFIG_SHARED_LIBS + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_SDKCONFIG_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimePoint.h b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimePoint.h index eea4124f83..17228eeca7 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimePoint.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimePoint.h @@ -63,11 +63,20 @@ class TimePoint { */ int64_t getTime_Unix() const; + /** + * Returns the time managed by this object in UTC time point format. + * + * @return The time managed by this object in UTC time point format. + */ + std::chrono::system_clock::time_point getTime_Utc_TimePoint() const; + private: /// The scheduled time for the alert in ISO-8601 format. std::string m_time_ISO_8601; /// The scheduled time for the alert in Unix epoch format. int64_t m_time_Unix; + /// The scheduled time for the alert in UTC time point format. + std::chrono::system_clock::time_point m_time_Utc_TimePoint; /// Object used to safely access time utilities. TimeUtils m_timeUtils; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimeUtils.h b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimeUtils.h index b7c7f63282..3e713f6f89 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimeUtils.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimeUtils.h @@ -78,6 +78,18 @@ class TimeUtils { */ bool convert8601TimeStringToUnix(const std::string& timeString, int64_t* unixTime); + /** + * Converts a ISO 8601 date-time string to a timepoint. Referring to function @c convert8601TimeStringToUnix for + * details of the string representation in ISO 8601 format. + * + * @param iso8601TimeString The time string in a ISO 8601 format. + * @param[out] tp The converted time into UTC time point format. + * @return Whether the conversion was successful. + */ + bool convert8601TimeStringToUtcTimePoint( + const std::string& iso8601TimeString, + std::chrono::system_clock::time_point* tp); + /** * Gets the current time in Unix epoch time, as a 64 bit integer. * @@ -101,6 +113,16 @@ class TimeUtils { std::string* iso8601TimeString); private: + /** + * Utility function to convert 8601TimeStringToTimeT. Referring to function @c convert8601TimeStringToUnix for + * details of the string representation in ISO 8601 format. + * + * @param iso8601TimeString The time string in a ISO 8601 format. + * @param[out] The converted time since epoch. + * @return Whether the conversion was successful. + */ + bool convert8601TimeStringToTimeT(const std::string& iso8601TimeString, std::time_t* timeT); + /** * Calculate localtime offset in std::time_t. * diff --git a/AVSCommon/Utils/src/FileSystem/FileSystemUtilsLinux.cpp b/AVSCommon/Utils/src/FileSystem/FileSystemUtilsLinux.cpp new file mode 100644 index 0000000000..1e53d1dfe4 --- /dev/null +++ b/AVSCommon/Utils/src/FileSystem/FileSystemUtilsLinux.cpp @@ -0,0 +1,360 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +#if defined(FILE_SYSTEM_UTILS_ENABLED) + +#include "AVSCommon/Utils/FileSystem/FileSystemUtils.h" +#include "AVSCommon/Utils/Logger/Logger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace filesystem { + +/// String to identify log entries originating from this file. +static const std::string TAG("FileSystemUtils"); +/// Create a LogEntry using this file's TAG and the specified event std::string. +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +const Permissions DEFAULT_DIRECTORY_PERMISSIONS = OWNER_ALL | GROUP_READ | GROUP_EXEC; +const Permissions DEFAULT_FILE_PERMISSIONS = OWNER_READ | OWNER_WRITE | GROUP_READ; + +/** + * Simple locker class that protects the processes umask using a global mutex + */ +class UmaskLocker { +public: + explicit UmaskLocker(mode_t newMode) { + s_umaskMutex.lock(); + m_storedPermission = umask(newMode); // NOLINT(cppcoreguidelines-prefer-member-initializer) set after lock + } + + ~UmaskLocker() { + umask(m_storedPermission); + s_umaskMutex.unlock(); + } + +private: + static std::mutex s_umaskMutex; + + mode_t m_storedPermission; +}; + +std::mutex UmaskLocker::s_umaskMutex; + +static std::string getStrError(int error) { + static const size_t BUFFER_SIZE = 255; + char buffer[BUFFER_SIZE + 1]{}; + auto ignore = strerror_r(error, buffer, BUFFER_SIZE); + (void)ignore; // unused since the type can differ depending on the gnu or posix + return buffer; +} + +bool changePermissions(const std::string& path, Permissions perms) { + if (chmod(path.c_str(), perms) != 0) { + ACSDK_ERROR( + LX("changePermissions").m("Failed to change permission").d("path", path).d("error", getStrError(errno))); + return false; + } + return true; +} + +bool exists(const std::string& path) { + struct stat fileStat {}; + return 0 == stat(path.c_str(), &fileStat); +} + +std::string currentDirectory() { + char cwd[PATH_MAX + 1]{}; + if (getcwd(cwd, PATH_MAX) == nullptr) { + ACSDK_ERROR(LX("currentDirectory").m("Failed to get current directory path")); + return ""; + } + + ACSDK_DEBUG(LX("currentDirectory").d("path", cwd)); + return cwd; +} + +bool makeDirectory(const std::string& inputPath, Permissions perms) { + ACSDK_DEBUG7(LX("makeDirectory").d("path", inputPath)); + struct stat fileStat {}; + + if (inputPath.empty()) { + ACSDK_ERROR(LX("makeDirectory").m("Empty input path, unable to create directory").d("path", inputPath)); + return false; + } + + if (lstat(inputPath.c_str(), &fileStat) == 0) { + if (!S_ISDIR(fileStat.st_mode)) { + ACSDK_ERROR(LX("makeDirectory") + .m("Failed to create directory, a file with the same name already exists") + .d("path", inputPath)); + return false; + } + if (!changePermissions(inputPath, perms)) { + ACSDK_ERROR(LX("makeDirectory").m("Failed to change permission on existing directory")); + return false; + } + return true; + } + + if (inputPath.find("/../") != std::string::npos || inputPath.find("/./") != std::string::npos) { + ACSDK_ERROR( + LX("makeDirectory").m("Attempting to create filepath with \"/../\" or \"/./\"%s").d("path", inputPath)); + return false; + } + + std::string path = inputPath; + const auto umaskLocker = UmaskLocker(0); + auto iter = path.begin(); + while ((iter = find(iter + 1, path.end(), '/')) != path.end()) { + *iter = '\0'; + // if path already exists + if (stat(path.c_str(), &fileStat) == 0) { + // if path is NOT a directory, then we cannot progress + if (!S_ISDIR(fileStat.st_mode)) { + ACSDK_ERROR(LX("makeDirectory") + .m("Failed to create parent directory, a file with the same name already exists") + .d("path", path)); + return false; + } + } else if (mkdir(path.c_str(), perms) != 0 && errno != EEXIST) { + ACSDK_ERROR(LX("makeDirectory") + .m("Failed to create parent directory") + .d("path", path) + .d("error", getStrError(errno))); + return false; + } else { + ACSDK_DEBUG7(LX("makeDirectory").m("Created parent directory").d("path", path)); + } + *iter = '/'; + } + + if (!exists(path) && mkdir(path.c_str(), perms) != 0 && errno != EEXIST) { + ACSDK_ERROR(LX("makeDirectory").m("Failed to create directory").d("path", path).d("error", getStrError(errno))); + return false; + } + + ACSDK_INFO(LX("makeDirectory").m("Created final directory").d("path", path)); + return true; +} + +std::vector list(const std::string& path, const FileType type) { + static std::mutex listMutex; + std::lock_guard lock(listMutex); + std::vector result; + + auto dirPtr = std::unique_ptr(opendir(path.c_str()), &closedir); + while (dirPtr != nullptr) { + auto dp = readdir(dirPtr.get()); // NOLINT(concurrency-mt-unsafe) protected by static mutex + if (dp == nullptr) { + break; + } + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { + continue; + } + FileType currentType; + if (dp->d_type == DT_DIR) { + currentType = FileType::DIRECTORY; + } else if (dp->d_type == DT_REG) { + currentType = FileType::REGULAR_FILE; + } else { + // only list files and directories + continue; + } + if (type == FileType::ALL || currentType == type) { + result.emplace_back(dp->d_name); + } + } + + return result; +} + +bool move(const std::string& source, const std::string& destination) { + ACSDK_INFO(LX("move").d("source", source).d("destination", destination)); + auto result = std::rename(source.c_str(), destination.c_str()); + if (result != 0) { + ACSDK_ERROR(LX("move").m("Move failed").d("error", getStrError(errno))); + return false; + } + + return true; +} + +std::string basenameOf(const std::string& path) { + static std::mutex basenameMutex; + auto lastSlash = path.find_last_not_of('/') + 1; + + if (lastSlash == 0) { + return ""; + } + + std::unique_ptr pathPtr(new char[path.size() + 1]{}); + std::copy(path.begin(), path.begin() + static_cast(lastSlash), pathPtr.get()); + + std::lock_guard lock(basenameMutex); + return basename(pathPtr.get()); // NOLINT(concurrency-mt-unsafe) protected by static mutex +} + +std::string parentDirNameOf(const std::string& path) { + static std::mutex dirnameMutex; + auto lastSlash = path.find_last_not_of('/') + 1; + + if (lastSlash == 0 && path[0] == '/') { + return "/"; + } + + std::unique_ptr pathPtr(new char[path.size() + 1]{}); + std::copy(path.begin(), path.end(), pathPtr.get()); + + std::lock_guard lock(dirnameMutex); + std::string result = dirname(pathPtr.get()); // NOLINT(concurrency-mt-unsafe) protected by static mutex + if (result == "/") { + return result; + } + return result + "/"; +} + +bool removeAll(const std::string& path) { + ACSDK_INFO(LX("removeAll").d("path", path)); + struct stat pathStat {}; + if (lstat(path.c_str(), &pathStat) != 0) { + ACSDK_DEBUG7(LX("removeAll").m("Path does not exists").d("path", path)); + return true; + } + + if (!S_ISDIR(pathStat.st_mode)) { + if (std::remove(path.c_str()) != 0) { + ACSDK_ERROR(LX("removeAll").m("Failed to delete file").d("path", path)); + return false; + } + return true; + } + + auto func = [](const char* fpath, const struct stat* s, int i, struct FTW* f) -> int { + auto rv = std::remove(fpath); + if (rv != 0) { + ACSDK_ERROR(LX("removeAll").m("Failed to delete file").d("path", fpath)); + } + + return rv; + }; + + static constexpr auto maxOpenFd = 64; + static std::mutex nftwMutex; + std::lock_guard lock(nftwMutex); + return 0 == nftw(path.c_str(), func, maxOpenFd, FTW_DEPTH | FTW_PHYS); // NOLINT(concurrency-mt-unsafe) protected +} + +static size_t sizeOfDirectory(const std::string& rootDirectory) { + static std::mutex sizeOfMutex; + std::lock_guard lock(sizeOfMutex); + std::stack directories; + size_t result = 0; + directories.push(rootDirectory); + + do { + auto directory = std::move(directories.top()); + directories.pop(); + auto dirp = std::unique_ptr(opendir(directory.c_str()), &closedir); + while (dirp != nullptr) { + auto dp = readdir(dirp.get()); // NOLINT(concurrency-mt-unsafe) protected by static mutex + if (dp == nullptr) { + break; + } + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { + continue; + } + + auto subPath = directory + "/" + dp->d_name; + struct stat s {}; + if (lstat(subPath.c_str(), &s) != 0) { + ACSDK_ERROR(LX("sizeOfDirectory").m("Subpath does not exists").d("path", subPath)); + continue; + } + + if (S_ISDIR(s.st_mode)) { + directories.emplace(std::move(subPath)); + } else { + result += s.st_size; + } + } + } while (!directories.empty()); + + return result; +} + +size_t sizeOf(const std::string& path) { + struct stat pathStat {}; + if (lstat(path.c_str(), &pathStat) != 0) { + ACSDK_ERROR(LX("sizeOf").m("Path does not exists").d("path", path)); + return 0; + } + + if (S_ISDIR(pathStat.st_mode)) { + return sizeOfDirectory(path); + } + + ACSDK_DEBUG(LX("sizeOf").d("path", path).d("bytes", pathStat.st_size)); + return static_cast(pathStat.st_size); +} + +size_t availableSpace(const std::string& path) { + struct statvfs diskStat {}; + if (statvfs(path.c_str(), &diskStat) != 0) { + ACSDK_ERROR(LX("availableSpace").m("Failed to get free space from system").d("path", path)); + return 0; + } + return diskStat.f_bsize * diskStat.f_bavail; +} + +bool pathContainsPrefix(const std::string& path, const std::string& prefix) { + char resolved_path[PATH_MAX + 1]; + + if (::realpath(path.c_str(), resolved_path) == nullptr) { + auto errno_returned = errno; + // check that it's not a file/directory doesn't exist error, which is acceptable + if (errno_returned != ENOENT && errno_returned != ENOTDIR) { + ACSDK_ERROR(LX("pathContainsPrefix") + .m("Unable to resolve path") + .d("path", path) + .d("error", getStrError(errno_returned))); + return false; + } + } + + // assert resolved path is a prefix + return strncmp(prefix.c_str(), resolved_path, prefix.length()) == 0; +} + +} // namespace filesystem +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // FILE_SYSTEM_UTILS_ENABLED \ No newline at end of file diff --git a/AVSCommon/Utils/src/FileSystem/FileSystemUtilsWindows.cpp b/AVSCommon/Utils/src/FileSystem/FileSystemUtilsWindows.cpp new file mode 100644 index 0000000000..2047b834c8 --- /dev/null +++ b/AVSCommon/Utils/src/FileSystem/FileSystemUtilsWindows.cpp @@ -0,0 +1,351 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +#if defined(FILE_SYSTEM_UTILS_ENABLED) + +#include "AVSCommon/Utils/FileSystem/FileSystemUtils.h" +#include "AVSCommon/Utils/Logger/Logger.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace filesystem { + +/// String to identify log entries originating from this file. +static const std::string TAG("FileSystemUtils"); +/// Create a LogEntry using this file's TAG and the specified event std::string. +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +const Permissions DEFAULT_DIRECTORY_PERMISSIONS = OWNER_ALL | GROUP_READ | GROUP_EXEC; +const Permissions DEFAULT_FILE_PERMISSIONS = OWNER_READ | OWNER_WRITE | GROUP_READ; + +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) +#endif + +#ifndef S_ISREG +#define S_ISREG(mode) (((mode)&S_IFMT) == S_IFREG) +#endif + +static std::string getStrError(int error) { + static const size_t BUFFER_SIZE = 255; + char buffer[BUFFER_SIZE + 1]{}; + auto ignore = strerror_s(buffer, BUFFER_SIZE, error); + (void)ignore; // unused since the type can differ depending on the gnu or posix + return buffer; +} + +static std::string getBackslashPath(std::string path) { + replace(path.begin(), path.end(), '/', '\\'); + return path; +} + +bool changePermissions(const std::string& path, Permissions perms) { + int winPerms{}; + if (perms & OWNER_READ || perms & GROUP_READ || perms & OTHERS_READ) { + winPerms |= _S_IREAD; + } + if (perms & OWNER_WRITE || perms & GROUP_WRITE || perms & OTHERS_WRITE) { + winPerms |= _S_IWRITE; + } + + if (_chmod(path.c_str(), winPerms) != 0) { + ACSDK_ERROR( + LX("changePermissions").m("Failed to change permission").d("path", path).d("error", getStrError(errno))); + return false; + } + return true; +} + +bool exists(const std::string& path) { + struct stat fileStat {}; + return 0 == stat(path.c_str(), &fileStat); +} + +std::string currentDirectory() { + char cwd[MAX_PATH + 1]{}; + if (GetCurrentDirectoryA(MAX_PATH, cwd) == 0) { + ACSDK_ERROR(LX("currentDirectory").m("Could not get current direction path")); + return ""; + } + + ACSDK_DEBUG(LX("currentDirectory").d("path", cwd)); + return cwd; +} + +bool makeDirectory(const std::string& inputPath, Permissions perms) { + ACSDK_DEBUG7(LX("makeDirectory").d("path", inputPath)); + struct stat fileStat {}; + + if (inputPath.empty()) { + ACSDK_ERROR(LX("makeDirectory").m("Empty input path, unable to create directory")); + return false; + } + + if (stat(inputPath.c_str(), &fileStat) == 0) { + if (S_ISDIR(fileStat.st_mode) == 0) { + ACSDK_ERROR(LX("makeDirectory") + .m("Failed to create a directory, a file with the same name already exists") + .d("path", inputPath)); + return false; + } + if (!changePermissions(inputPath, perms)) { + ACSDK_ERROR(LX("makeDirectory").m("Failed to change permission on existing directory")); + return false; + } + return true; + } + + std::string path = getBackslashPath(inputPath); + if (path.find("\\..\\") != std::string::npos || path.find("\\.\\") != std::string::npos) { + ACSDK_ERROR( + LX("makeDirectory").m("Attempting to create filepath with \"\\..\\\" or \"\\.\\\"").d("path", path)); + return false; + } + + auto iter = path.begin(); + while ((iter = find(iter + 1, path.end(), '\\')) != path.end()) { + *iter = '\0'; + if (iter != path.begin() && *(iter - 1) == ':') { + // ignore drive + } else if (stat(path.c_str(), &fileStat) == 0) { + // if path is NOT a directory, then we cannot progress + if (!S_ISDIR(fileStat.st_mode)) { + ACSDK_ERROR(LX("makeDirectory") + .m("Failed to create parent directory, a file with the same name already exists") + .d("path", path)); + return false; + } + } else if (!CreateDirectoryA(path.c_str(), nullptr)) { + ACSDK_ERROR(LX("makeDirectory").m("Failed to create parent directory").d("path", path)); + return false; + } else { + ACSDK_DEBUG7(LX("makeDirectory").m("Created parent directory").d("path", path)); + } + *iter = '\\'; + } + + if (!exists(path) && (!CreateDirectoryA(path.c_str(), nullptr) || !changePermissions(inputPath, perms))) { + ACSDK_ERROR(LX("makeDirectory").m("Failed to create directory").d("path", path)); + return false; + } + + ACSDK_INFO(LX("makeDirectory").m("Created final directory").d("path", path)); + return true; +} + +std::vector list(const std::string& path, const FileType type) { + std::vector result; + WIN32_FIND_DATAA data{}; + auto handle = FindFirstFileA((path + "\\*").c_str(), &data); + + if (handle == INVALID_HANDLE_VALUE) { + ACSDK_ERROR(LX("list").m("Could not open directory").d("path", path)); + return result; + } + + do { + std::string fileOrDirName = data.cFileName; + // skip current and parent + if (fileOrDirName == "." || fileOrDirName == "..") { + continue; + } + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (type == FileType::DIRECTORY || type == FileType::ALL) { + result.push_back(fileOrDirName); + } + } else { + if (type == FileType::REGULAR_FILE || type == FileType::ALL) { + result.push_back(fileOrDirName); + } + } + } while (FindNextFileA(handle, &data)); + + FindClose(handle); + return result; +} + +bool move(const std::string& source, const std::string& destination) { + ACSDK_INFO(LX("move").d("source", source).d("destination", destination)); + auto result = std::rename(source.c_str(), destination.c_str()); + if (result != 0) { + ACSDK_ERROR(LX("move").m("Move failed")); + return false; + } + + return true; +} + +std::string basenameOf(const std::string& path) { + char filename[_MAX_FNAME + 1]{}; + char ext[_MAX_EXT + 1]{}; + auto properPath = getBackslashPath(path); + auto lastSlash = properPath.find_last_not_of('\\') + 1; + _splitpath_s(properPath.substr(0, lastSlash).c_str(), nullptr, 0, nullptr, 0, filename, _MAX_FNAME, ext, _MAX_EXT); + return std::string(filename) + ext; +} + +std::string parentDirNameOf(const std::string& path) { + char driveName[_MAX_DRIVE + 1]{}; + char dirName[_MAX_DIR + 1]{}; + auto properPath = getBackslashPath(path); + auto lastSlash = properPath.find_last_not_of('\\') + 1; + + // if all we have is '\' or a series of '\\\', then what we have is the root folder only + if (lastSlash == 0 && properPath[0] == '\\') { + return "\\"; + } + + _splitpath_s( + properPath.substr(0, lastSlash).c_str(), driveName, _MAX_DRIVE, dirName, _MAX_DIR, nullptr, 0, nullptr, 0); + std::string driveStr = driveName; + // if our directory is empty, then either return the drive only if provided (root folder) or current directory + if (dirName[0] == '\0') { + return driveStr.empty() ? ".\\" : driveStr + "\\"; + } + + return driveStr + dirName; +} + +static bool removeDirectory(const std::string& path) { + bool result = true; + WIN32_FIND_DATAA data{}; + auto handle = FindFirstFileA((path + "\\*").c_str(), &data); + + if (handle == INVALID_HANDLE_VALUE) { + ACSDK_ERROR(LX("removeDirectory").m("Could not open directory").d("path", path)); + return false; + } + + do { + std::string fileOrDirName = data.cFileName; + std::string fullPath = path + "\\" + fileOrDirName; + // skip current and parent + if (fileOrDirName == "." || fileOrDirName == "..") { + continue; + } + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + result &= removeDirectory(fullPath); + } else { + result &= (0 == std::remove(fullPath.c_str())); + } + } while (FindNextFileA(handle, &data)); + + FindClose(handle); + result &= RemoveDirectoryA(path.c_str()); + return result; +} + +bool removeAll(const std::string& path) { + ACSDK_INFO(LX("removeAll").d("path", path)); + struct stat pathStat {}; + if (stat(path.c_str(), &pathStat) != 0) { + ACSDK_DEBUG7(LX("removeAll").m("Path does not exists").d("path", path)); + return true; + } + + if (!S_ISDIR(pathStat.st_mode)) { + if (std::remove(path.data()) != 0) { + ACSDK_ERROR(LX("removeAll").m("Failed to delete file").d("path", path)); + return false; + } + return true; + } + + return removeDirectory(path); +} + +static size_t sizeOfDirectory(const std::string& path) { + size_t size{}; + WIN32_FIND_DATAA data{}; + auto handle = FindFirstFileA((path + "\\*").c_str(), &data); + + if (handle == INVALID_HANDLE_VALUE) { + ACSDK_ERROR(LX("sizeOfDirectory").m("Could not open directory path").d("path", path)); + return 0; + } + + do { + std::string fileOrDirName = data.cFileName; + std::string fullPath = path + "\\" + fileOrDirName; + // skip current and parent + if (fileOrDirName == "." || fileOrDirName == "..") { + continue; + } + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + size += sizeOfDirectory(fullPath); + } else { + size += data.nFileSizeHigh * MAXDWORD + data.nFileSizeLow; + } + } while (FindNextFileA(handle, &data)); + + FindClose(handle); + return size; +} + +size_t sizeOf(const std::string& path) { + struct stat pathStat {}; + if (stat(path.c_str(), &pathStat) != 0) { + ACSDK_ERROR(LX("sizeOf").m("Path does not exists").d("path", path)); + return 0; + } + + if (S_ISDIR(pathStat.st_mode)) { + return sizeOfDirectory(path); + } + + ACSDK_DEBUG(LX("sizeOf").d("path", path).d("bytes", pathStat.st_size)); + return static_cast(pathStat.st_size); +} + +size_t availableSpace(const std::string& path) { + ULARGE_INTEGER i64FreeBytesToCaller; + if (!GetDiskFreeSpaceExA(path.c_str(), &i64FreeBytesToCaller, nullptr, nullptr)) { + ACSDK_ERROR(LX("availableSpace").m("Failed to get free space from system").d("path", path)); + return 0UL; + } + return static_cast(i64FreeBytesToCaller.QuadPart); +} + +bool pathContainsPrefix(const std::string& path, const std::string& prefix) { + char resolvedPath[MAX_PATH + 1]{}; + char resolvedPrefix[MAX_PATH + 1]{}; + if (GetFullPathNameA(path.c_str(), MAX_PATH, resolvedPath, nullptr) == 0) { + ACSDK_ERROR(LX("pathContainsPrefix").m("Unable to resolve path").d("path", path)); + return false; + } + if (GetFullPathNameA(prefix.c_str(), MAX_PATH, resolvedPrefix, nullptr) == 0) { + ACSDK_ERROR(LX("pathContainsPrefix").m("Unable to resolve prefix").d("prefix", prefix)); + return false; + } + + // assert resolved path is a prefix + return strncmp(resolvedPrefix, resolvedPath, strnlen(resolvedPrefix, MAX_PATH)) == 0; +} + +} // namespace filesystem +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // FILE_SYSTEM_UTILS_ENABLED diff --git a/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp b/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp index 36e46ca563..e72244aedd 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp @@ -40,7 +40,7 @@ static const std::string TAG("CurlEasyHandleWrapper"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -62,7 +62,7 @@ bool CurlEasyHandleWrapper::m_isInterfaceNameInitialized = false; std::mutex CurlEasyHandleWrapper::m_interfaceNameMutex; #ifdef ACSDK_EMIT_CURL_LOGS -/// Key under 'acl' configuration node for path/prefix of per-stream log file names. +/// Key under 'libcurlUtils' 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-"); @@ -382,7 +382,7 @@ std::string CurlEasyHandleWrapper::getEffectiveUrl() { if (temp) { effectiveUrl = temp; - ACSDK_DEBUG7(LX(__func__).d("effectiveURL", effectiveUrl)); + ACSDK_DEBUG7(LX("getEffectiveUrl").d("effectiveURL", effectiveUrl)); } } else { diff --git a/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp b/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp index 73cf0c4899..0baa0dae9a 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp @@ -29,7 +29,7 @@ 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. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -51,7 +51,7 @@ std::unique_ptr HTTPConte setCurlOptionsCallback = m_setCurlOptionsCallbackFactory->createSetCurlOptionsCallback(); } - ACSDK_DEBUG9(LX(__func__).sensitive("URL", url).m("Creating a new http content fetcher")); + ACSDK_DEBUG9(LX("create").sensitive("URL", url).m("Creating a new http content fetcher")); return avsCommon::utils::memory::make_unique(url, setCurlOptionsCallback); } diff --git a/AVSCommon/Utils/src/LibcurlUtils/HTTPResponse.cpp b/AVSCommon/Utils/src/LibcurlUtils/HTTPResponse.cpp index d03a0a5ca4..3f96b5be1e 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/HTTPResponse.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/HTTPResponse.cpp @@ -34,6 +34,12 @@ std::string HTTPResponse::serialize() { return serializedValue; } +HTTPResponse::HTTPResponse(long pCode, const std::string& pBody) : code{pCode}, body{pBody} { +} + +HTTPResponse::HTTPResponse() : code{0} { +} + } // namespace libcurlUtils } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp index 12e1f76945..db3955e792 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp @@ -53,7 +53,7 @@ static const std::chrono::minutes MAX_GET_HEADER_WAIT{5}; /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -64,7 +64,7 @@ size_t LibCurlHttpContentFetcher::headerCallback(char* data, size_t size, size_t } auto fetcher = static_cast(userData); - ACSDK_DEBUG9(LX(__func__).sensitive("url", fetcher->m_url).m("CALLED")); + ACSDK_DEBUG9(LX("headerCallback").sensitive("url", fetcher->m_url).m("CALLED")); fetcher->stateTransition(State::FETCHING_HEADER, true); @@ -96,7 +96,7 @@ size_t LibCurlHttpContentFetcher::headerCallback(char* data, size_t size, size_t 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)); + ACSDK_DEBUG9(LX("headerCallback").d("type", "content-length").d("length", fetcher->m_header.contentLength)); } else if (line.compare(0, 13, "content-range") == 0) { // To find lines like: "Content-Range: bytes 1000-3979/3980" std::istringstream iss(line); @@ -104,7 +104,7 @@ size_t LibCurlHttpContentFetcher::headerCallback(char* data, size_t size, size_t std::string contentUnit; std::string range; iss >> contentRangeBeginning >> contentUnit >> range; - ACSDK_DEBUG9(LX(__func__).d("type", "content-range").d("unit", contentUnit).d("range", range)); + ACSDK_DEBUG9(LX("headerCallback").d("type", "content-range").d("unit", contentUnit).d("range", range)); } return size * nmemb; } @@ -121,7 +121,7 @@ size_t LibCurlHttpContentFetcher::bodyCallback(char* data, size_t size, size_t n } if (State::FETCHING_HEADER == fetcher->getState()) { - ACSDK_DEBUG9(LX(__func__).sensitive("url", fetcher->m_url).m("End of header found.")); + ACSDK_DEBUG9(LX("bodyCallback").sensitive("url", fetcher->m_url).m("End of header found.")); fetcher->stateTransition(State::HEADER_DONE, true); } @@ -133,7 +133,7 @@ size_t LibCurlHttpContentFetcher::bodyCallback(char* data, size_t size, size_t n elapsedTime = std::chrono::steady_clock::now() - startTime; } if (MAX_GET_BODY_WAIT <= elapsedTime) { - ACSDK_ERROR(LX(__func__).d("reason", "getBodyCallWaitTimeout")); + ACSDK_ERROR(LX("bodyCallback").d("reason", "getBodyCallWaitTimeout")); fetcher->stateTransition(State::ERROR, false); return 0; } @@ -144,7 +144,7 @@ size_t LibCurlHttpContentFetcher::bodyCallback(char* data, size_t size, size_t n fetcher->stateTransition(State::FETCHING_BODY, true); if (!fetcher->m_streamWriter) { - ACSDK_DEBUG9(LX(__func__).m("No writer received. Creating a new one.")); + ACSDK_DEBUG9(LX("bodyCallback").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); @@ -177,7 +177,7 @@ size_t LibCurlHttpContentFetcher::bodyCallback(char* data, size_t size, size_t n // might still have bytes to write continue; case avsCommon::avs::attachment::AttachmentWriter::WriteStatus::OK_BUFFER_FULL: - ACSDK_ERROR(LX(__func__).d("unexpected return code", "OK_BUFFER_FULL")); + ACSDK_ERROR(LX("bodyCallback").d("unexpected return code", "OK_BUFFER_FULL")); return 0; } ACSDK_ERROR(LX("UnexpectedWriteStatus").d("writeStatus", static_cast(writeStatus))); @@ -188,7 +188,7 @@ size_t LibCurlHttpContentFetcher::bodyCallback(char* data, size_t size, size_t n fetcher->m_totalContentReceivedLength += totalBytesWritten; fetcher->m_currentContentReceivedLength += totalBytesWritten; - ACSDK_DEBUG9(LX(__func__) + ACSDK_DEBUG9(LX("bodyCallback") .d("totalContentReceived", fetcher->m_totalContentReceivedLength) .d("contentLength", fetcher->m_header.contentLength) .d("currentContentReceived", fetcher->m_currentContentReceivedLength) @@ -235,7 +235,7 @@ HTTPContentFetcherInterface::Header LibCurlHttpContentFetcher::getHeader(std::at 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")); + ACSDK_ERROR(LX("getHeader").sensitive("URL", m_url).d("reason", "Invalid state").d("state", "ERROR")); m_header.successful = false; return m_header; } @@ -255,10 +255,11 @@ HTTPContentFetcherInterface::Header LibCurlHttpContentFetcher::getHeader(std::at bool LibCurlHttpContentFetcher::getBody(std::shared_ptr writer) { std::lock_guard lock(m_getBodyMutex); if (State::ERROR == getState()) { + ACSDK_ERROR(LX("getBodyFailed").d("reason", "errorState")); return false; } if (!waitingForBodyRequest()) { - ACSDK_ERROR(LX(__func__).d("reason", "functionAlreadyCalled")); + ACSDK_ERROR(LX("getBodyFailed").d("reason", "functionAlreadyCalled")); return false; } m_streamWriter = writer; @@ -267,7 +268,7 @@ bool LibCurlHttpContentFetcher::getBody(std::shared_ptr LibCurlHttpContentFetcher::getCon // Set this to 1 so that we will try to perform() again. numTransfersLeft = 1; - ACSDK_DEBUG9(LX(__func__) + ACSDK_DEBUG9(LX("getContent") .d("bytesRemaining", bytesRemaining) .d("totalContentReceived", m_totalContentReceivedLength) .d("restartingWithRange", ss.str())); @@ -523,7 +524,7 @@ std::unique_ptr LibCurlHttpContentFetcher::getCon * 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")); + ACSDK_DEBUG9(LX("getContent").m("Closing the writer")); m_streamWriter->close(); } @@ -576,7 +577,7 @@ LibCurlHttpContentFetcher::~LibCurlHttpContentFetcher() { } void LibCurlHttpContentFetcher::reportInvalidStateTransitionAttempt(State currentState, State newState) { - ACSDK_ERROR(LX(__func__) + ACSDK_ERROR(LX("reportInvalidStateTransitionAttempt") .d("currentState", currentState) .d("newState", newState) .m("An attempt was made to perform an invalid state transition.")); @@ -695,10 +696,14 @@ void LibCurlHttpContentFetcher::stateTransition(State newState, bool value) { return; } if (State::ERROR == newState) { - ACSDK_ERROR(LX(__func__).sensitive("URL", m_url).d("oldState", m_state).m("State transition to ERROR")); + ACSDK_ERROR( + LX("stateTransition").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")); + ACSDK_DEBUG9(LX("stateTransition") + .sensitive("URL", m_url) + .d("oldState", m_state) + .d("newState", newState) + .m("State transition")); } m_state = newState; } diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp index ab58a0ddb6..2e473873bd 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp @@ -34,10 +34,17 @@ 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. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) +/** + * Create a LogEntry using this file's TAG, the specified event string, and object pointer as the first argument. + * + * @param event The event string for this @c LogEntry. + */ +#define LX_P(event) LX(event).p("this", this) + /// Timeout for curl_multi_wait const static std::chrono::milliseconds WAIT_FOR_ACTIVITY_TIMEOUT(50); /// Timeout for curl_multi_wait while all non-intermittent HTTP/2 streams are paused. @@ -119,18 +126,19 @@ LibcurlHTTP2Connection::LibcurlHTTP2Connection( const std::shared_ptr& setCurlOptionsCallback) : m_isStopping{false}, m_setCurlOptionsCallback{setCurlOptionsCallback} { + ACSDK_DEBUG5(LX_P("init")); m_networkThread = std::thread(&LibcurlHTTP2Connection::networkLoop, this); } bool LibcurlHTTP2Connection::createMultiHandle() { m_multi = CurlMultiHandleWrapper::create(); if (!m_multi) { - ACSDK_ERROR(LX("initFailed").d("reason", "curlMultiHandleWrapperCreateFailed")); + ACSDK_ERROR(LX_P("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")); + ACSDK_ERROR(LX_P("initFailed").d("reason", "enableHTTP2PipeliningFailed")); return false; } @@ -146,7 +154,7 @@ std::shared_ptr LibcurlHTTP2Connection::create( } LibcurlHTTP2Connection::~LibcurlHTTP2Connection() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX_P("destroy")); disconnect(); } @@ -180,16 +188,16 @@ void LibcurlHTTP2Connection::processNextRequest() { 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())); + ACSDK_DEBUG9(LX_P("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))); + ACSDK_ERROR(LX_P("processNextRequest").d("reason", "addHandleFailed").d("error", curl_multi_strerror(result))); stream->reportCompletion(HTTP2ResponseFinishedStatus::INTERNAL_ERROR); } } void LibcurlHTTP2Connection::networkLoop() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX_P("networkLoop")); while (!isStopping()) { if (!createMultiHandle()) { @@ -215,7 +223,7 @@ void LibcurlHTTP2Connection::networkLoop() { continue; } if (result != CURLM_OK) { - ACSDK_ERROR(LX("networkLoopStopping").d("reason", "performFailed")); + ACSDK_ERROR(LX_P("networkLoopStopping").d("reason", "performFailed")); setIsStopping(); break; } @@ -241,7 +249,7 @@ void LibcurlHTTP2Connection::networkLoop() { result = m_multi->wait(multiWaitTimeout, &numTransfersUpdated); if (result != CURLM_OK) { ACSDK_ERROR( - LX("networkLoopStopping").d("reason", "multiWaitFailed").d("error", curl_multi_strerror(result))); + LX_P("networkLoopStopping").d("reason", "multiWaitFailed").d("error", curl_multi_strerror(result))); setIsStopping(); break; } @@ -265,7 +273,7 @@ void LibcurlHTTP2Connection::networkLoop() { m_multi.reset(); } - ACSDK_DEBUG5(LX("networkLoopExiting")); + ACSDK_DEBUG5(LX_P("networkLoopExiting")); } std::shared_ptr LibcurlHTTP2Connection::createAndSendRequest(const HTTP2RequestConfig& config) { @@ -275,7 +283,7 @@ std::shared_ptr LibcurlHTTP2Connection::createAndSendRequ } void LibcurlHTTP2Connection::disconnect() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX_P("disconnect")); setIsStopping(); if (m_networkThread.joinable()) { m_networkThread.join(); @@ -311,12 +319,12 @@ void LibcurlHTTP2Connection::notifyObserversOfGoawayReceived() { bool LibcurlHTTP2Connection::addStream(std::shared_ptr stream) { if (!stream) { - ACSDK_ERROR(LX("addStream").d("failed", "null stream")); + ACSDK_ERROR(LX_P("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")); + ACSDK_ERROR(LX_P("addStream").d("failed", "network loop stopping")); return false; } m_requestQueue.push_back(std::move(stream)); @@ -339,14 +347,14 @@ void LibcurlHTTP2Connection::cleanupFinishedStreams() { } else { it->second->reportCompletion(HTTP2ResponseFinishedStatus::COMPLETE); } - ACSDK_DEBUG7(LX("streamFinished") + ACSDK_DEBUG7(LX_P("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)); + LX_P("cleanupFinishedStreamError").d("reason", "streamNotFound").d("handle", message->easy_handle)); } } } while (message); @@ -359,7 +367,7 @@ void LibcurlHTTP2Connection::cleanupCancelledAndStalledStreams() { if (stream->isCancelled()) { cancelActiveStream(*stream); } else if (stream->hasProgressTimedOut()) { - ACSDK_WARN(LX("streamProgressTimedOut").d("streamId", stream->getId())); + ACSDK_WARN(LX_P("streamProgressTimedOut").d("streamId", stream->getId())); stream->reportCompletion(HTTP2ResponseFinishedStatus::TIMEOUT); releaseStream(*stream); } @@ -388,7 +396,7 @@ void LibcurlHTTP2Connection::unPauseActiveStreams() { } bool LibcurlHTTP2Connection::cancelActiveStream(LibcurlHTTP2Request& stream) { - ACSDK_INFO(LX(__func__).d("streamId", stream.getId())); + ACSDK_INFO(LX_P("cancelActiveStream").d("streamId", stream.getId())); stream.reportCompletion(HTTP2ResponseFinishedStatus::CANCELLED); return releaseStream(stream); } @@ -408,7 +416,7 @@ void LibcurlHTTP2Connection::cancelPendingStreams() { } for (auto pendingStream : pendingStreamsCopy) { - ACSDK_DEBUG9(LX(__func__).d("pending streamId", pendingStream->getId())); + ACSDK_DEBUG9(LX_P("cancelPendingStreams").d("pending streamId", pendingStream->getId())); pendingStream->reportCompletion(HTTP2ResponseFinishedStatus::CANCELLED); } } @@ -421,11 +429,11 @@ void LibcurlHTTP2Connection::cancelAllStreams() { bool LibcurlHTTP2Connection::releaseStream(LibcurlHTTP2Request& stream) { auto handle = stream.getCurlHandle(); - ACSDK_DEBUG9(LX("releaseStream").d("streamId", stream.getId())); + ACSDK_DEBUG9(LX_P("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())); + ACSDK_ERROR(LX_P("releaseStreamFailed").d("reason", "removeHandleFailed").d("streamId", stream.getId())); return false; } return true; diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp index 74d5eef545..5bdafb015e 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp @@ -33,18 +33,19 @@ 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. + * @param event 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")); + ACSDK_ERROR(LX("writeCallback").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)); + ACSDK_DEBUG9( + LX("writeCallback").d("id", stream->getId()).d("size", size).d("nmemb", nmemb).d("userData", userData)); stream->setTimeOfLastTransfer(); stream->reportResponseCode(); @@ -73,7 +74,8 @@ size_t LibcurlHTTP2Request::headerCallback(char* data, size_t size, size_t nmemb } LibcurlHTTP2Request* stream = static_cast(userData); - ACSDK_DEBUG9(LX(__func__).d("id", stream->getId()).d("size", size).d("nmemb", nmemb).d("userData", userData)); + ACSDK_DEBUG9( + LX("headerCallback").d("id", stream->getId()).d("size", size).d("nmemb", nmemb).d("userData", userData)); stream->setTimeOfLastTransfer(); stream->reportResponseCode(); @@ -94,7 +96,7 @@ size_t LibcurlHTTP2Request::readCallback(char* data, size_t size, size_t nmemb, } LibcurlHTTP2Request* stream = static_cast(userData); - ACSDK_DEBUG9(LX(__func__).d("id", stream->getId()).d("size", size).d("nmemb", nmemb).d("userData", userData)); + ACSDK_DEBUG9(LX("readCallback").d("id", stream->getId()).d("size", size).d("nmemb", nmemb).d("userData", userData)); stream->setTimeOfLastTransfer(); @@ -145,7 +147,8 @@ LibcurlHTTP2Request::LibcurlHTTP2Request( m_stream{std::move(id)}, m_isIntermittentTransferExpected{config.isIntermittentTransferExpected()}, m_isPaused{false}, - m_isCancelled{false} { + m_isCancelled{false}, + m_connectTimeout{std::chrono::milliseconds{0}} { switch (config.getRequestType()) { case HTTP2RequestType::GET: m_stream.setTransferType(CurlEasyHandleWrapper::TransferType::kGET); @@ -187,6 +190,7 @@ LibcurlHTTP2Request::LibcurlHTTP2Request( } if (config.getConnectionTimeout() != std::chrono::milliseconds::zero()) { m_stream.curlOptionsSetter().setopt(CURLOPT_CONNECTTIMEOUT_MS, config.getConnectionTimeout().count()); + m_connectTimeout = config.getConnectionTimeout(); } if (config.getTransferTimeout() != std::chrono::milliseconds::zero()) { m_stream.curlOptionsSetter().setopt(CURLOPT_TIMEOUT_MS, config.getTransferTimeout().count()); @@ -201,11 +205,15 @@ LibcurlHTTP2Request::LibcurlHTTP2Request( }; bool LibcurlHTTP2Request::hasProgressTimedOut() const { + if (!m_responseCodeReported && milliseconds::zero() != m_connectTimeout) { + return duration_cast(steady_clock::now() - m_timeOfLastTransfer) > m_connectTimeout; + } 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; } diff --git a/AVSCommon/Utils/src/Logger/LogEntry.cpp b/AVSCommon/Utils/src/Logger/LogEntry.cpp index 1fa9b2ab68..593d687171 100644 --- a/AVSCommon/Utils/src/Logger/LogEntry.cpp +++ b/AVSCommon/Utils/src/Logger/LogEntry.cpp @@ -64,14 +64,6 @@ LogEntry::LogEntry(const std::string& source, const std::string& event) : m_hasM 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) { @@ -82,18 +74,10 @@ LogEntry& LogEntry::d(const char* key, const char* 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); } @@ -112,7 +96,7 @@ LogEntry& LogEntry::m(const std::string& message) { return *this; } -LogEntry& LogEntry::p(const char* key, void* ptr) { +LogEntry& LogEntry::p(const char* key, const void* ptr) { return d(key, ptr); } diff --git a/AVSCommon/Utils/src/MediaPlayer/PlaybackContext.cpp b/AVSCommon/Utils/src/MediaPlayer/PlaybackContext.cpp index 4671a74e50..1975d473e3 100644 --- a/AVSCommon/Utils/src/MediaPlayer/PlaybackContext.cpp +++ b/AVSCommon/Utils/src/MediaPlayer/PlaybackContext.cpp @@ -40,6 +40,7 @@ const std::string PlaybackContext::HTTP_AUDIOSEGMENT_HEADERS = "audioSegment"; const std::string PlaybackContext::HTTP_ALL_HEADERS = "all"; static const std::string AUTHORIZATION = "Authorization"; static const std::string ALLOWED_PREFIX = "x-"; +static const std::string ALLOWED_PREFIX_CAP = "X-"; static const std::string COOKIE = "Cookie"; static const unsigned int MIN_KEY_LENGTH = 3; static const unsigned int MAX_KEY_LENGTH = 256; @@ -63,8 +64,9 @@ static std::pair validatePlaybackContextHeadersInternal(HeaderConfig if (!validateIfNotMalicious(entry->first) || !validateIfNotMalicious(entry->second)) { foundMaliciousHeaders = true; } - if ((entry->first.find(ALLOWED_PREFIX) == 0 && entry->first.length() >= MIN_KEY_LENGTH && - entry->first.length() <= MAX_KEY_LENGTH && entry->second.length() <= MAX_VALUE_LENGTH) || + if (((entry->first.find(ALLOWED_PREFIX) == 0 || entry->first.find(ALLOWED_PREFIX_CAP) == 0) && + entry->first.length() >= MIN_KEY_LENGTH && entry->first.length() <= MAX_KEY_LENGTH && + entry->second.length() <= MAX_VALUE_LENGTH) || (entry->first.compare(AUTHORIZATION) == 0 && entry->second.length() <= MAX_VALUE_LENGTH) || (entry->first.compare(COOKIE) == 0 && entry->second.length() <= MAX_VALUE_LENGTH)) { if (!foundMaliciousHeaders) { diff --git a/AVSCommon/Utils/src/Metrics/MetricEvent.cpp b/AVSCommon/Utils/src/Metrics/MetricEvent.cpp index 0f1f519e9e..c93393dc63 100644 --- a/AVSCommon/Utils/src/Metrics/MetricEvent.cpp +++ b/AVSCommon/Utils/src/Metrics/MetricEvent.cpp @@ -56,7 +56,7 @@ Priority MetricEvent::getPriority() const { Optional MetricEvent::getDataPoint(const std::string& name, DataType dataType) const { std::string key = MetricEventBuilder::generateKey(name, dataType); if (m_dataPoints.find(key) == m_dataPoints.end()) { - ACSDK_WARN(LX("getDataPointWarning").d("reason", "dataPointDoesntExist")); + ACSDK_DEBUG9(LX("getDataPointWarning").d("reason", "dataPointDoesntExist")); return Optional{}; } diff --git a/AVSCommon/Utils/src/TimePoint.cpp b/AVSCommon/Utils/src/TimePoint.cpp index 79ed501463..5083650c3c 100644 --- a/AVSCommon/Utils/src/TimePoint.cpp +++ b/AVSCommon/Utils/src/TimePoint.cpp @@ -55,8 +55,15 @@ bool TimePoint::setTime_ISO_8601(const std::string& time_ISO_8601) { return false; } + std::chrono::system_clock::time_point tp; + if (!m_timeUtils.convert8601TimeStringToUtcTimePoint(time_ISO_8601, &tp)) { + ACSDK_ERROR(LX("setTime_ISO_8601Failed").d("input", time_ISO_8601).m("Could not convert to time_point.")); + return false; + } + m_time_ISO_8601 = time_ISO_8601; m_time_Unix = tempUnixTime; + m_time_Utc_TimePoint = tp; return true; } @@ -68,6 +75,10 @@ int64_t TimePoint::getTime_Unix() const { return m_time_Unix; } +std::chrono::system_clock::time_point TimePoint::getTime_Utc_TimePoint() const { + return m_time_Utc_TimePoint; +} + } // namespace timing } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/TimeUtils.cpp b/AVSCommon/Utils/src/TimeUtils.cpp index 8312004dcf..03f470c6ee 100644 --- a/AVSCommon/Utils/src/TimeUtils.cpp +++ b/AVSCommon/Utils/src/TimeUtils.cpp @@ -137,60 +137,92 @@ bool TimeUtils::convertToUtcTimeT(const std::tm* utcTm, std::time_t* ret) { return true; } +bool TimeUtils::convert8601TimeStringToUtcTimePoint( + const std::string& iso8601TimeString, + std::chrono::system_clock::time_point* tp) { + if (!tp) { + ACSDK_ERROR(LX("convert8601TimeStringToUtcTimePoint").m("tp was nullptr.")); + return false; + } + std::time_t timeT; + if (!convert8601TimeStringToTimeT(iso8601TimeString, &timeT)) { + ACSDK_ERROR(LX("convert8601TimeStringToUtcTimePointFailed").m("convert8601TimeStringToTimeT failed")); + return false; + } + *tp = std::chrono::system_clock::from_time_t(timeT); + return true; +} + bool TimeUtils::convert8601TimeStringToUnix(const std::string& timeString, int64_t* convertedTime) { + if (!convertedTime) { + ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").m("convertedTime was nullptr.")); + return false; + } + std::time_t timeT; + if (!convert8601TimeStringToTimeT(timeString, &timeT)) { + ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").m("convert8601TimeStringToTimeT failed")); + return false; + } + + *convertedTime = static_cast(timeT); + return true; +} + +bool TimeUtils::convert8601TimeStringToTimeT(const std::string& iso8601TimeString, std::time_t* timeT) { // TODO : Use std::get_time once we only support compilers that implement this function (GCC 5.1+ / Clang 3.3+) - if (!convertedTime) { - ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").m("convertedTime parameter was nullptr.")); + if (!timeT) { + ACSDK_ERROR(LX("convert8601TimeStringToTimeTFailed").m("timeT parameter was nullptr.")); return false; } std::tm timeInfo; - if (timeString.length() != ENCODED_TIME_STRING_EXPECTED_LENGTH) { - ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").d("unexpected time string length:", timeString.length())); + if (iso8601TimeString.length() != ENCODED_TIME_STRING_EXPECTED_LENGTH) { + ACSDK_ERROR( + LX("convert8601TimeStringToTimeTFailed").d("unexpected time string length:", iso8601TimeString.length())); return false; } if (!stringToInt( - timeString.substr(ENCODED_TIME_STRING_YEAR_OFFSET, ENCODED_TIME_STRING_YEAR_STRING_LENGTH), + iso8601TimeString.substr(ENCODED_TIME_STRING_YEAR_OFFSET, ENCODED_TIME_STRING_YEAR_STRING_LENGTH), &(timeInfo.tm_year))) { - ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").m("error parsing year. Input:" + timeString)); + ACSDK_ERROR(LX("convert8601TimeStringToTimeTFailed").m("error parsing year. Input:" + iso8601TimeString)); return false; } if (!stringToInt( - timeString.substr(ENCODED_TIME_STRING_MONTH_OFFSET, ENCODED_TIME_STRING_MONTH_STRING_LENGTH), + iso8601TimeString.substr(ENCODED_TIME_STRING_MONTH_OFFSET, ENCODED_TIME_STRING_MONTH_STRING_LENGTH), &(timeInfo.tm_mon))) { - ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").m("error parsing month. Input:" + timeString)); + ACSDK_ERROR(LX("convert8601TimeStringToTimeTFailed").m("error parsing month. Input:" + iso8601TimeString)); return false; } if (!stringToInt( - timeString.substr(ENCODED_TIME_STRING_DAY_OFFSET, ENCODED_TIME_STRING_DAY_STRING_LENGTH), + iso8601TimeString.substr(ENCODED_TIME_STRING_DAY_OFFSET, ENCODED_TIME_STRING_DAY_STRING_LENGTH), &(timeInfo.tm_mday))) { - ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").m("error parsing day. Input:" + timeString)); + ACSDK_ERROR(LX("convert8601TimeStringToTimeTFailed").m("error parsing day. Input:" + iso8601TimeString)); return false; } if (!stringToInt( - timeString.substr(ENCODED_TIME_STRING_HOUR_OFFSET, ENCODED_TIME_STRING_HOUR_STRING_LENGTH), + iso8601TimeString.substr(ENCODED_TIME_STRING_HOUR_OFFSET, ENCODED_TIME_STRING_HOUR_STRING_LENGTH), &(timeInfo.tm_hour))) { - ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").m("error parsing hour. Input:" + timeString)); + ACSDK_ERROR(LX("convert8601TimeStringToTimeTFailed").m("error parsing hour. Input:" + iso8601TimeString)); return false; } if (!stringToInt( - timeString.substr(ENCODED_TIME_STRING_MINUTE_OFFSET, ENCODED_TIME_STRING_MINUTE_STRING_LENGTH), + iso8601TimeString.substr(ENCODED_TIME_STRING_MINUTE_OFFSET, ENCODED_TIME_STRING_MINUTE_STRING_LENGTH), &(timeInfo.tm_min))) { - ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").m("error parsing minute. Input:" + timeString)); + ACSDK_ERROR(LX("convert8601TimeStringToTimeTFailed").m("error parsing minute. Input:" + iso8601TimeString)); return false; } if (!stringToInt( - timeString.substr(ENCODED_TIME_STRING_SECOND_OFFSET, ENCODED_TIME_STRING_SECOND_STRING_LENGTH), + iso8601TimeString.substr(ENCODED_TIME_STRING_SECOND_OFFSET, ENCODED_TIME_STRING_SECOND_STRING_LENGTH), &(timeInfo.tm_sec))) { - ACSDK_ERROR(LX("convert8601TimeStringToUnixFailed").m("error parsing second. Input:" + timeString)); + ACSDK_ERROR(LX("convert8601TimeStringToTimeTFailed").m("error parsing second. Input:" + iso8601TimeString)); return false; } @@ -198,14 +230,7 @@ bool TimeUtils::convert8601TimeStringToUnix(const std::string& timeString, int64 timeInfo.tm_year -= 1900; timeInfo.tm_mon -= 1; - std::time_t convertedTimeT; - bool ok = convertToUtcTimeT(&timeInfo, &convertedTimeT); - - if (!ok) { - return false; - } - *convertedTime = static_cast(convertedTimeT); - return true; + return convertToUtcTimeT(&timeInfo, timeT); } bool TimeUtils::getCurrentUnixTime(int64_t* currentTime) { diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/Common/MockRequiresShutdown.h b/AVSCommon/Utils/test/AVSCommon/Utils/Common/MockRequiresShutdown.h new file mode 100644 index 0000000000..260852dea5 --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Common/MockRequiresShutdown.h @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_COMMON_MOCKREQUIRESSHUTDOWN_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_MOCKREQUIRESSHUTDOWN_H_ + +#include +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace test { + +/// Mock class that implements RequiresShutdown. +class MockRequiresShutdown : public RequiresShutdown { +public: + MockRequiresShutdown(const std::string& name); + MOCK_CONST_METHOD0(name, std::string()); + MOCK_METHOD0(shutdown, void()); + MOCK_METHOD0(isShutdown, bool()); + MOCK_METHOD0(doShutdown, void()); +}; + +} // namespace test +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_COMMON_MOCKREQUIRESSHUTDOWN_H_ diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/LibcurlUtils/MockHttpGet.h b/AVSCommon/Utils/test/AVSCommon/Utils/LibcurlUtils/MockHttpGet.h new file mode 100644 index 0000000000..2b86329c7f --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/LibcurlUtils/MockHttpGet.h @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_LIBCURLUTILS_MOCKHTTPGET_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_LIBCURLUTILS_MOCKHTTPGET_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { +namespace test { + +/// A mock object that implements the @c HttpGetInterface. +class MockHttpGet : public HttpGetInterface { +public: + MOCK_METHOD2(doGet, HTTPResponse(const std::string& url, const std::vector& headers)); +}; + +} // namespace test +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_LIBCURLUTILS_MOCKHTTPGET_H_ diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/LibcurlUtils/MockHttpPost.h b/AVSCommon/Utils/test/AVSCommon/Utils/LibcurlUtils/MockHttpPost.h new file mode 100644 index 0000000000..60d96f87be --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/LibcurlUtils/MockHttpPost.h @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_LIBCURLUTILS_MOCKHTTPPOST_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_LIBCURLUTILS_MOCKHTTPPOST_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { +namespace test { + +/// A mock object that implements the @c HttpPostInterface. +class MockHttpPost : public HttpPostInterface { +public: + MOCK_METHOD4( + doPost, + long(const std::string& url, const std::string& data, std::chrono::seconds timeout, std::string& body)); + MOCK_METHOD4( + doPost, + HTTPResponse( + const std::string& url, + const std::vector headerLines, + const std::vector>& data, + std::chrono::seconds timeout)); + MOCK_METHOD4( + doPost, + HTTPResponse( + const std::string& url, + const std::vector headerLines, + const std::string& data, + std::chrono::seconds timeout)); +}; + +} // namespace test +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_LIBCURLUTILS_MOCKHTTPPOST_H_ diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/Logger/TestTrace.h b/AVSCommon/Utils/test/AVSCommon/Utils/Logger/TestTrace.h new file mode 100644 index 0000000000..0921267a36 --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Logger/TestTrace.h @@ -0,0 +1,75 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_LOGGER_TESTTRACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_LOGGER_TESTTRACE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace logger { + +/** + * Utility class that can be used to print logs in testcases. + * + * This class will print debug logs and it will include the test suite and test name as part of the logs. + */ +class TestTrace { +public: + /** + * Initialize object and logger as well. + */ + TestTrace(); + + /** + * Log a message. + * + * @param message The message to be logged. + */ + void log(const std::string& message); + + /** + * Log a value. + * + * @tparam ValueType The type of the value (must be supported by LogEntry). + * @param name A string used to identify the value. + * @param value The value to be logged. + */ + template + void log(const std::string& name, const ValueType& value); + +private: + // The test name extracted from googletest framework. + std::string m_testName; + + // The test case extracted from googletest framework. + std::string m_testCase; +}; + +template +void TestTrace::log(const std::string& name, const ValueType& value) { + ACSDK_DEBUG(LogEntry(m_testCase, m_testName).d(name, value)); +} + +} // namespace logger +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_AVSCOMMON_UTILS_LOGGER_TESTTRACE_H_ diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/PlaybackContextTest.cpp b/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/PlaybackContextTest.cpp index 6e925748ea..a5b7c3d484 100644 --- a/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/PlaybackContextTest.cpp +++ b/AVSCommon/Utils/test/AVSCommon/Utils/MediaPlayer/PlaybackContextTest.cpp @@ -97,9 +97,12 @@ TEST(PlaybackContextTest, test_validateMixValidInvalidHeaders) { // Test with more than 20 valid headers x-* { PlaybackContext playbackContext; - for (int i = 0; i < 25; i++) { + for (int i = 0; i < 10; i++) { playbackContext.audioSegmentConfig["x-" + std::to_string(i)] = "abcd" + std::to_string(i); } + for (int i = 10; i < 25; i++) { + playbackContext.audioSegmentConfig["X-" + std::to_string(i)] = "abcd" + std::to_string(i); + } auto isValid = validatePlaybackContextHeaders(&playbackContext); EXPECT_FALSE(isValid.first); diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/Timing/TimerDelegateTest.cpp b/AVSCommon/Utils/test/AVSCommon/Utils/Timing/TimerDelegateTest.cpp new file mode 100644 index 0000000000..f15dfa907a --- /dev/null +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Timing/TimerDelegateTest.cpp @@ -0,0 +1,351 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace timing { +namespace test { + +using namespace std; +using namespace std::chrono; +using namespace alexaClientSDK::avsCommon::sdkInterfaces::timing; +using namespace ::testing; + +class TimerDelegateTest : public Test { +public: + /// SetUp for the test. + void SetUp() override; + + /// TearDown for the test. + void TearDown() override; + + /// Simple timer task that increments the task counter. + void testTask(); + + /** + * Get the current value of the task counter. + * @return Value of the task counter. + */ + size_t getTaskCounter(); + + /** + * Task with fixed duration emulated as a sleep. + * @param sleepDuration duration emulating the task in milliseconds + */ + void testTaskWithSleep(milliseconds sleepDuration); + + /** + * Task that calls an internal stop to the timer delegate. + * @param timer @c TimerDelegateInterface used to call stop internally. + */ + void taskWithStop(TimerDelegateInterface* timer); + + /** + * Task with customizable task durations across task iterations. + * @param taskTimes vector containing task durations in milliseconds across + * task iterations. + */ + void customVariableDurationTask(vector taskTimes); + +protected: + /// @c TimerDelegate under test. + TimerDelegate m_timerDelegate; + +private: + /// Counter to track number of invocations of the timer task. + atomic m_taskCounter; +}; + +void TimerDelegateTest::SetUp() { + m_taskCounter = 0; +} + +void TimerDelegateTest::TearDown() { + m_timerDelegate.stop(); +} + +void TimerDelegateTest::testTask() { + m_taskCounter++; +} + +size_t TimerDelegateTest::getTaskCounter() { + return m_taskCounter; +} + +void TimerDelegateTest::testTaskWithSleep(milliseconds sleepDuration) { + this_thread::sleep_for(sleepDuration); + m_taskCounter++; +} + +void TimerDelegateTest::taskWithStop(TimerDelegateInterface* timer) { + m_taskCounter++; + timer->stop(); +} + +void TimerDelegateTest::customVariableDurationTask(vector taskTimes) { + if (!taskTimes.empty() && (m_taskCounter < taskTimes.size())) { + this_thread::sleep_for(taskTimes[m_taskCounter]); + } + m_taskCounter++; +} + +/** + * Test to verify basic APIs to activate, start, and ensure that the TimerDelegate triggered the expected number of + * times. + */ +TEST_F(TimerDelegateTest, test_basicTimerDelegateAPI) { + auto delay = milliseconds(100); + auto period = milliseconds(500); + auto maxCount = 2u; + auto graceTime = milliseconds(50); + + m_timerDelegate.start( + delay, period, TimerDelegateInterface::PeriodType::ABSOLUTE, maxCount, [this] { testTask(); }); + + /// Check after first task call. + this_thread::sleep_for(delay + graceTime); + ASSERT_EQ(getTaskCounter(), 1u); + ASSERT_TRUE(m_timerDelegate.isActive()); + + /// Sleep until the timer completes all iterations. + this_thread::sleep_for((maxCount - 1) * period + graceTime); + ASSERT_EQ(getTaskCounter(), maxCount); + + /// Ensure timer is inactive post completion of all iterations. + ASSERT_FALSE(m_timerDelegate.isActive()); +} + +/// Test to verify the stop and start API. +TEST_F(TimerDelegateTest, test_stopAndStartTimeDelegate) { + auto delay = milliseconds(500); + auto period = milliseconds(500); + auto maxCount = 2u; + auto graceTime = milliseconds(50); + + m_timerDelegate.start( + delay, period, TimerDelegateInterface::PeriodType::ABSOLUTE, maxCount, [this] { testTask(); }); + + /// Confirm that the timer stops immediately since the timer is not active yet (due to the delay). + m_timerDelegate.stop(); + ASSERT_EQ(getTaskCounter(), 0u); + ASSERT_FALSE(m_timerDelegate.isActive()); + + /// Ensure timer is active once start is called. + m_timerDelegate.start( + delay, period, TimerDelegateInterface::PeriodType::ABSOLUTE, maxCount, [this] { testTask(); }); + ASSERT_TRUE(m_timerDelegate.isActive()); + + /// Sleep until all iterations complete. + this_thread::sleep_for(delay + period * maxCount + graceTime); + ASSERT_EQ(getTaskCounter(), maxCount); + ASSERT_FALSE(m_timerDelegate.isActive()); +} + +/// Test to verify that stopping an already stopped timer is a no-op. +TEST_F(TimerDelegateTest, test_doubleStopTestAVS) { + auto delay = milliseconds(100); + auto period = milliseconds(100); + auto maxCount = 2u; + auto graceTime = milliseconds(50); + + m_timerDelegate.start( + delay, period, TimerDelegateInterface::PeriodType::ABSOLUTE, maxCount, [this] { testTask(); }); + + /// Wait until all iterations complete. + this_thread::sleep_for(delay + period * maxCount + graceTime); + + /// Stop the timer and confirm that the timer becomes inactive. + m_timerDelegate.stop(); + ASSERT_EQ(getTaskCounter(), maxCount); + ASSERT_FALSE(m_timerDelegate.isActive()); + + /// Verify that subsequent stop calls changes nothing. + m_timerDelegate.stop(); + ASSERT_EQ(getTaskCounter(), maxCount); + ASSERT_FALSE(m_timerDelegate.isActive()); +} + +/** + * Test to verify timer operations with a task of fixed duration. + * Delay : 100ms. + * Period : 100ms. + * Task Time : 40ms. + * Expectation : 4 task iterations should complete after 440 ms. ++----------------+------------------------+-----------------+ +| Iteration Time | Intermediate timepoint | Action | ++================+========================+=================+ +| 100 | | Start Task#1 | ++----------------+------------------------+-----------------+ +| | 140 | End of Task#1 | ++----------------+------------------------+-----------------+ +| 200 | | Start of Task#2 | ++----------------+------------------------+-----------------+ +| | 240 | End of Task#2 | ++----------------+------------------------+-----------------+ +| 300 | | Start of Task#3 | ++----------------+------------------------+-----------------+ +| | 340 | End of Task#3 | ++----------------+------------------------+-----------------+ +| 400 | | Start of Task#4 | ++----------------+------------------------+-----------------+ +| | 440 | End of Task#4 | ++----------------+------------------------+-----------------+ +*/ +TEST_F(TimerDelegateTest, test_verifyTaskWithFixedDuration) { + auto delay = milliseconds(100); + auto period = milliseconds(100); + auto maxCount = 4u; + auto taskDuration = milliseconds(40); + auto graceTime = milliseconds(50); + + m_timerDelegate.start( + delay, period, TimerDelegateInterface::PeriodType::ABSOLUTE, maxCount, [this, taskDuration]() { + testTaskWithSleep(taskDuration); + }); + + /// Sleep until timer completes. + this_thread::sleep_for(delay + maxCount * period + graceTime); + + /// Confirm the task counter is as expected. + ASSERT_EQ(getTaskCounter(), maxCount); + ASSERT_FALSE(m_timerDelegate.isActive()); +} + +/** + * Test to verify timer operations with a task of variable duration. + * Delay: 100ms. + * Period: 100ms. + * Task Times: + ** Iteration 1: 220ms. + ** Iteration 2: 120ms. + ** Iteration 3 (and beyond): 80ms. + * MaxCount: 9. + * Expectation of 1000 ms only 6 iterations are run and deactivated. ++----------------+------------------------+------------------------+ +| Iteration Time | Intermediate timepoint | Action | ++================+========================+========================+ +| 100 | | Start Task#1 | ++----------------+------------------------+------------------------+ +| 200 | | Skip | ++----------------+------------------------+------------------------+ +| 300 | | Skip | ++----------------+------------------------+------------------------+ +| | 320 | End of Task#1 | ++----------------+------------------------+------------------------+ +| 400 | | Start of Task#2 | ++----------------+------------------------+------------------------+ +| 500 | | Skip | ++----------------+------------------------+------------------------+ +| | 520 | End of Task#2 | ++----------------+------------------------+------------------------+ +| 600 | | Start of Task#3 | ++----------------+------------------------+------------------------+ +| | 680 | End of Task#3 | ++----------------+------------------------+------------------------+ +| 700 | 780 | Start and End of Task#4| ++----------------+------------------------+------------------------+ +| 800 | 880 | Start and End of Task#5| ++----------------+------------------------+------------------------+ +| 900 | 980 | Start and End of Task#6| ++----------------+------------------------+------------------------+ +*/ +TEST_F(TimerDelegateTest, test_verifyTaskWithVariableDuration) { + auto delay = milliseconds(100); + auto period = milliseconds(100); + auto maxCount = 9; + auto expectedNumTaskCalls = 6u; + auto gracePeriod = milliseconds(50); + auto taskDurations = { + milliseconds(220), milliseconds(120), milliseconds(80), milliseconds(80), milliseconds(80), milliseconds(80)}; + + m_timerDelegate.start( + delay, period, TimerDelegateInterface::PeriodType::ABSOLUTE, maxCount, [this, taskDurations]() { + TimerDelegateTest::customVariableDurationTask(taskDurations); + }); + + /// Wait for all iterations to complete and verify the task counter. + this_thread::sleep_for(delay + maxCount * period + gracePeriod); + ASSERT_EQ(getTaskCounter(), expectedNumTaskCalls); + ASSERT_FALSE(m_timerDelegate.isActive()); +} + +/** + * Test to verify that subsequent start API call must wait for the previous iterations + * to complete (when using Relative PeriodType). + */ +TEST_F(TimerDelegateTest, test_doubleStartMustWaitForPreviousIterations) { + auto delay = milliseconds(500); + auto period = milliseconds(500); + auto maxCount = 2; + auto expectedTaskCounter = 4u; + auto gracePeriod = milliseconds(100); + + m_timerDelegate.start( + delay, period, TimerDelegateInterface::PeriodType::RELATIVE, maxCount, [this] { testTask(); }); + + /// Sleep for more time than the initial delay, verify the task counter. + this_thread::sleep_for(delay + gracePeriod); + ASSERT_EQ(getTaskCounter(), 1u); + ASSERT_TRUE(m_timerDelegate.isActive()); + + /// Start again after 1 iteration. + /// This start will wait for all iterations from previous start call to complete. + m_timerDelegate.start( + delay, period, TimerDelegateInterface::PeriodType::RELATIVE, maxCount, [this] { testTask(); }); + + /// Sleep until all iterations complete. + this_thread::sleep_for(delay + period * maxCount + gracePeriod); + + /// Expected task count is 4 : 2 iterations in prev start call + 2 in later start call. + ASSERT_EQ(getTaskCounter(), expectedTaskCounter); + ASSERT_FALSE(m_timerDelegate.isActive()); +} + +/** + * Test to verify that if the task internally stops the timer, the timer will not run + * the remaining iterations. + */ +TEST_F(TimerDelegateTest, test_taskWithStop) { + auto delay = milliseconds(100); + auto period = milliseconds(500); + auto maxCount = 2u; + auto gracePeriod = milliseconds(100); + + m_timerDelegate.start(delay, period, TimerDelegateInterface::PeriodType::ABSOLUTE, maxCount, [this]() { + taskWithStop(&m_timerDelegate); + }); + + /// Wait for 1 iteration to complete. + this_thread::sleep_for(delay + gracePeriod); + + /// Since the task stopped the timer after the first iteration, + /// expected task counter shall be 1. + ASSERT_EQ(getTaskCounter(), 1u); + ASSERT_FALSE(m_timerDelegate.isActive()); +} + +} // namespace test +} // namespace timing +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK \ No newline at end of file diff --git a/AVSCommon/Utils/test/Common/CMakeLists.txt b/AVSCommon/Utils/test/Common/CMakeLists.txt index c7289137d0..f6cc1acc27 100644 --- a/AVSCommon/Utils/test/Common/CMakeLists.txt +++ b/AVSCommon/Utils/test/Common/CMakeLists.txt @@ -5,11 +5,13 @@ if (BUILD_TESTING) MockMediaPlayer.cpp MockHTTP2MimeRequestEncodeSource.cpp MockHTTP2MimeResponseDecodeSink.cpp + MockRequiresShutdown.cpp Common.cpp MimeUtils.cpp TestableAttachmentManager.cpp TestableAttachmentWriter.cpp TestableMessageObserver.cpp + TestTrace.cpp Timing/StopTaskTimer.cpp) target_include_directories(UtilsCommonTestLib PUBLIC "${AVSCommon_INCLUDE_DIRS}" @@ -17,6 +19,5 @@ if (BUILD_TESTING) "${AVSCommon_SOURCE_DIR}/Utils/test") target_link_libraries(UtilsCommonTestLib AVSCommon - gtest_main gmock_main) endif() diff --git a/AVSCommon/Utils/test/Common/MockRequiresShutdown.cpp b/AVSCommon/Utils/test/Common/MockRequiresShutdown.cpp new file mode 100644 index 0000000000..cacfe36107 --- /dev/null +++ b/AVSCommon/Utils/test/Common/MockRequiresShutdown.cpp @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 test { + +MockRequiresShutdown::MockRequiresShutdown(const std::string& name) : RequiresShutdown(name) { +} + +} // namespace test +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK \ No newline at end of file diff --git a/AVSCommon/Utils/test/Common/TestTrace.cpp b/AVSCommon/Utils/test/Common/TestTrace.cpp new file mode 100644 index 0000000000..c600bb3f76 --- /dev/null +++ b/AVSCommon/Utils/test/Common/TestTrace.cpp @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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/TestTrace.h" +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace logger { + +using namespace std; + +void TestTrace::log(const string& message) { + ACSDK_DEBUG(LogEntry(m_testCase, m_testName).m(message)); +} + +TestTrace::TestTrace() : m_testName{"UnknownTest"}, m_testCase{"unknownTestCase"} { + auto gtestPtr = testing::UnitTest::GetInstance(); + if (gtestPtr) { + auto testInfoPtr = gtestPtr->current_test_info(); + if (testInfoPtr) { + m_testName = testInfoPtr->name(); + m_testCase = testInfoPtr->test_case_name(); + } + } +} + +} // namespace logger +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/FileSystemUtilsTest.cpp b/AVSCommon/Utils/test/FileSystemUtilsTest.cpp new file mode 100644 index 0000000000..1a91f61335 --- /dev/null +++ b/AVSCommon/Utils/test/FileSystemUtilsTest.cpp @@ -0,0 +1,353 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +#if defined(FILE_SYSTEM_UTILS_ENABLED) + +#include +#include +#include + +#include "AVSCommon/Utils/FileSystem/FileSystemUtils.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace filesystem { +namespace test { + +using namespace std; +using namespace ::testing; + +class FileSystemUtilsTest : public ::testing::Test { +public: + void SetUp() override { + char dirName[L_tmpnam + 1]{}; + ASSERT_NE(nullptr, tmpnam(dirName)); + WORKING_DIR = dirName + string("_FileSystemUtilsTest/"); + + ASSERT_FALSE(exists(WORKING_DIR)); + createDirectory(WORKING_DIR); + ASSERT_TRUE(exists(WORKING_DIR)); + +#if defined(__linux__) or defined(__APPLE__) + // on some OS, the temp path is symbolically linked, which can cause issues for prefix tests + // to accommodate this, get the realpath of the temp directory + char resolved_path[PATH_MAX + 1]; + ASSERT_NE(nullptr, ::realpath(WORKING_DIR.c_str(), resolved_path)); + WORKING_DIR = string(resolved_path); + ASSERT_TRUE(exists(WORKING_DIR)); +#endif + + ASSERT_FALSE(WORKING_DIR.empty()); + if (*WORKING_DIR.rbegin() != '/') { + WORKING_DIR += "/"; + } + } + + void TearDown() override { + removeAll(WORKING_DIR); + ASSERT_FALSE(exists(WORKING_DIR)); + } + + static void createFile(const string& filePath, const string& content = "defaultContent") { + ofstream of(filePath); + ASSERT_TRUE(of.good()); + of << content; + of.close(); + ASSERT_TRUE(of.good()); + } + + static void createDirectory(const string& dirPath) { + makeDirectory(dirPath); + ASSERT_TRUE(exists(dirPath)); + } + + static string unifyDelimiter(string path) { + replace(path.begin(), path.end(), '\\', '/'); + return path; + }; + + string WORKING_DIR; +}; + +TEST_F(FileSystemUtilsTest, testChangingFilePermissions) { + auto path = WORKING_DIR + "file.txt"; + auto originalContent = "testing"; + auto updatedContent = "updated_testing"; + string content; + ifstream reader; + ofstream writer; + + // setup test file with content + writer.open(path); + writer << originalContent; + writer.close(); + ASSERT_TRUE(exists(path)); + +#ifndef _WIN32 // all files in windows are readable + // giving the file write only permission makes it impossible for us to read it + ASSERT_TRUE(changePermissions(path, OWNER_WRITE)); + reader.open(path); + ASSERT_FALSE(reader.good()); +#endif + + // changing the permissions to read only will allow us then to read + ASSERT_TRUE(changePermissions(path, OWNER_READ)); + reader.open(path); + ASSERT_TRUE(reader.good()); + reader >> content; + ASSERT_EQ(content, originalContent); + reader.close(); + + // however, with read only permission, we cannot then write + writer.open(path); + ASSERT_FALSE(writer.good()); + + // finally, giving the file read/write permission allows us to both update it and read it again + ASSERT_TRUE(changePermissions(path, OWNER_WRITE | OWNER_READ)); + writer.open(path); + ASSERT_TRUE(writer.good()); + writer << updatedContent; + writer.close(); + + reader.open(path); + ASSERT_TRUE(reader.good()); + reader >> content; + ASSERT_EQ(content, updatedContent); + reader.close(); +} + +TEST_F(FileSystemUtilsTest, testExistsValidatesThatAFileOrDirectoryExists) { // NOLINT + auto file = WORKING_DIR + "file"; + auto directory = WORKING_DIR + "directory"; + + ASSERT_FALSE(exists(file)); + ASSERT_FALSE(exists(directory)); + createFile(file); + createDirectory(directory); + ASSERT_TRUE(exists(file)); + ASSERT_TRUE(exists(directory)); +} + +TEST_F(FileSystemUtilsTest, testMovingFileToNewPath) { // NOLINT + auto directoryBefore = WORKING_DIR + "directory/"; + auto directoryAfter = WORKING_DIR + "newDirectory/"; + auto fileBefore = WORKING_DIR + "file"; + auto fileAfter = directoryBefore + "newFileName"; + + createDirectory(directoryBefore); + createFile(fileBefore); + ASSERT_TRUE(exists(directoryBefore)); + ASSERT_TRUE(exists(fileBefore)); + + ASSERT_TRUE(move(fileBefore, fileAfter)); + ASSERT_FALSE(exists(fileBefore)); + ASSERT_TRUE(exists(fileAfter)); + + ASSERT_TRUE(move(directoryBefore, directoryAfter)); + ASSERT_FALSE(exists(directoryBefore)); + ASSERT_TRUE(exists(directoryAfter)); +} + +TEST_F(FileSystemUtilsTest, testCheckingDiskSpace) { // NOLINT + ASSERT_GT(availableSpace(WORKING_DIR), 0UL); + ASSERT_EQ(availableSpace("/some/non/existing/directory"), 0UL); +} + +TEST_F(FileSystemUtilsTest, testCheckingSizeOfFilesAndDirectory) { // NOLINT + auto subDirectory = WORKING_DIR + "directory/"; + auto file1 = WORKING_DIR + "file1"; + auto file2 = subDirectory + "file2"; + string fileContent = "This is some text to fill into the file that's being created"; + + createDirectory(subDirectory); + createFile(file1, fileContent); + createFile(file2, fileContent); + ASSERT_EQ(sizeOf(file1), fileContent.size()); + ASSERT_EQ(sizeOf(file2), fileContent.size()); + ASSERT_EQ(sizeOf(WORKING_DIR), fileContent.size() * 2); +} + +TEST_F(FileSystemUtilsTest, testThatCurrentDirectoryExists) { // NOLINT + auto dir = currentDirectory(); + ASSERT_FALSE(dir.empty()); + ASSERT_TRUE(exists(dir)); +} + +TEST_F(FileSystemUtilsTest, testMakeDirectory) { // NOLINT + Permissions mode = OWNER_WRITE | OWNER_READ | OWNER_EXEC; + auto simpleDirName = WORKING_DIR + "simple-dir-name"; + auto recursiveCreate = WORKING_DIR + "first-directory/second-directory/third-directory"; + auto repeatedSlash = WORKING_DIR + "before-double-slash//after-double-slash"; + auto recursiveCreateWithSlashAtEnd = WORKING_DIR + "slash/at/the/end/"; + auto filePath = WORKING_DIR + "file"; + auto filePathFollowedByDir = WORKING_DIR + "file/some/dir"; + createFile(filePath); + + ASSERT_TRUE(makeDirectory(simpleDirName)); + ASSERT_TRUE(exists(simpleDirName)); + ASSERT_TRUE(makeDirectory(simpleDirName)); + ASSERT_TRUE(makeDirectory(recursiveCreate, mode)); + ASSERT_TRUE(exists(recursiveCreate)); + ASSERT_TRUE(makeDirectory(repeatedSlash, mode)); + ASSERT_TRUE(exists(repeatedSlash)); + ASSERT_TRUE(makeDirectory(recursiveCreateWithSlashAtEnd, mode)); + ASSERT_TRUE(exists(recursiveCreateWithSlashAtEnd)); + ASSERT_FALSE(makeDirectory(filePath, mode)); + ASSERT_FALSE(makeDirectory(filePathFollowedByDir, mode)); + ASSERT_FALSE(makeDirectory(WORKING_DIR + "first-directory/../this-fails", mode)); + ASSERT_FALSE(makeDirectory(WORKING_DIR + "first-directory/./this-fails", mode)); + ASSERT_FALSE(makeDirectory("")); +} + +TEST_F(FileSystemUtilsTest, testPathContainsPrefix) { // NOLINT + auto prefix = WORKING_DIR + "davs"; + createDirectory(prefix); + string good_path = prefix + "/valid_locale"; + string ok_path = prefix + "/valid_locale/../still/valid"; + string minimal_ok_path = prefix; + ASSERT_TRUE(pathContainsPrefix(good_path, prefix)); + ASSERT_TRUE(pathContainsPrefix(ok_path, prefix)); + ASSERT_TRUE(pathContainsPrefix(minimal_ok_path, prefix)); + + string sneaky_bad_path = prefix + "/../../system/bin"; + string flagrant_bad_path = "/system/bin"; + string invalid_bad_path = "&*$)#%^*("; + ASSERT_FALSE(pathContainsPrefix(sneaky_bad_path, prefix)); + ASSERT_FALSE(pathContainsPrefix(flagrant_bad_path, prefix)); + ASSERT_FALSE(pathContainsPrefix(invalid_bad_path, prefix)); +} + +TEST_F(FileSystemUtilsTest, testFileBasename) { // NOLINT + EXPECT_EQ(basenameOf("/tmp/file.txt"), "file.txt"); + EXPECT_EQ(basenameOf("/tmp/directory"), "directory"); + EXPECT_EQ(basenameOf("/tmp/directory/"), "directory"); + EXPECT_EQ(basenameOf("/tmp/directory//"), "directory"); + EXPECT_EQ(basenameOf("/tmp"), "tmp"); + EXPECT_EQ(basenameOf("tmp/"), "tmp"); + EXPECT_EQ(basenameOf("tmp"), "tmp"); + EXPECT_EQ(basenameOf("tmp///"), "tmp"); + EXPECT_EQ(basenameOf("/t"), "t"); + EXPECT_EQ(basenameOf("t/"), "t"); + EXPECT_EQ(basenameOf("/"), ""); + EXPECT_EQ(basenameOf("////"), ""); + EXPECT_EQ(basenameOf("/some/.."), ".."); + EXPECT_EQ(basenameOf("/some/."), "."); + EXPECT_EQ(basenameOf(".."), ".."); + EXPECT_EQ(basenameOf("."), "."); + EXPECT_EQ(basenameOf(""), ""); + +#ifdef _WIN32 + // Windows is able to accept '\\' as well as '/' delimiters + EXPECT_EQ(basenameOf("\\tmp\\directory\\"), "directory"); + EXPECT_EQ(basenameOf("C:\\tmp\\directory"), "directory"); +#endif +} + +TEST_F(FileSystemUtilsTest, testPathDirname) { // NOLINT + EXPECT_EQ(unifyDelimiter(parentDirNameOf("/tmp/file.txt")), "/tmp/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("/tmp/directory")), "/tmp/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("/tmp/directory/")), "/tmp/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("/tmp/directory//")), "/tmp/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("/tmp")), "/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("tmp/")), "./"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("tmp")), "./"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("tmp///")), "./"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("/t")), "/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("t/")), "./"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("/")), "/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("////")), "/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("/some/..")), "/some/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("/some/.")), "/some/"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("..")), "./"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf(".")), "./"); + EXPECT_EQ(unifyDelimiter(parentDirNameOf("")), "./"); + +#ifdef _WIN32 + // Windows is able to accept '\\' as well as '/' delimiters + EXPECT_EQ(parentDirNameOf("C:\\tmp/path"), "C:\\tmp\\"); + EXPECT_EQ(parentDirNameOf("C:/tmp/path"), "C:\\tmp\\"); + EXPECT_EQ(parentDirNameOf("C:/"), "C:\\"); + EXPECT_EQ(parentDirNameOf("C:"), "C:\\"); +#endif +} + +TEST_F(FileSystemUtilsTest, testListOfDifferentKinds) { // NOLINT + string file1 = "file1"; + string file2 = "file2"; + string dir1 = "dir1"; + string dir2 = "dir2"; + string link = "link"; + createFile(WORKING_DIR + file1); + createFile(WORKING_DIR + file2); + createDirectory(WORKING_DIR + dir1); + createDirectory(WORKING_DIR + dir2); + + auto files = list(WORKING_DIR, FileType::REGULAR_FILE); + auto directories = list(WORKING_DIR, FileType::DIRECTORY); + auto all = list(WORKING_DIR, FileType::ALL); + auto def = list(WORKING_DIR); + + ASSERT_EQ(all, def); + ASSERT_EQ(all.size(), 4UL); + ASSERT_EQ(files.size(), 2UL); + ASSERT_EQ(directories.size(), 2UL); + + ASSERT_TRUE(find(all.begin(), all.end(), file1) != all.end()); + ASSERT_TRUE(find(all.begin(), all.end(), file2) != all.end()); + ASSERT_TRUE(find(all.begin(), all.end(), dir1) != all.end()); + ASSERT_TRUE(find(all.begin(), all.end(), dir2) != all.end()); + + ASSERT_TRUE(find(files.begin(), files.end(), file1) != files.end()); + ASSERT_TRUE(find(files.begin(), files.end(), file2) != files.end()); + + ASSERT_TRUE(find(directories.begin(), directories.end(), dir1) != directories.end()); + ASSERT_TRUE(find(directories.begin(), directories.end(), dir2) != directories.end()); +} + +TEST_F(FileSystemUtilsTest, testRemoveAllFilesAndOrDirectories) { // NOLINT + string file = "file.txt"; + string directory = "dir"; + string fullDirectory = "fulldir"; + + createFile(WORKING_DIR + file); + createDirectory(WORKING_DIR + directory); + createDirectory(WORKING_DIR + fullDirectory + "/" + fullDirectory + "/" + fullDirectory); + createFile(WORKING_DIR + fullDirectory + "/" + fullDirectory + "/" + file); + createFile(WORKING_DIR + fullDirectory + "/" + file); + + ASSERT_TRUE(exists(WORKING_DIR + file)); + ASSERT_TRUE(removeAll(WORKING_DIR + file)); + ASSERT_FALSE(exists(WORKING_DIR + file)); + + ASSERT_TRUE(exists(WORKING_DIR + directory)); + ASSERT_TRUE(removeAll(WORKING_DIR + directory)); + ASSERT_FALSE(exists(WORKING_DIR + directory)); + + ASSERT_TRUE(exists(WORKING_DIR + fullDirectory)); + ASSERT_TRUE(removeAll(WORKING_DIR + fullDirectory)); + ASSERT_FALSE(exists(WORKING_DIR + fullDirectory)); + + ASSERT_TRUE(removeAll(WORKING_DIR + file)); + ASSERT_FALSE(exists(WORKING_DIR + file)); +} + +} // namespace test +} // namespace filesystem +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // FILE_SYSTEM_UTILS_ENABLED \ No newline at end of file diff --git a/AVSCommon/Utils/test/MultiTimerTest.cpp b/AVSCommon/Utils/test/MultiTimerTest.cpp index 28485ea278..d93391e91a 100644 --- a/AVSCommon/Utils/test/MultiTimerTest.cpp +++ b/AVSCommon/Utils/test/MultiTimerTest.cpp @@ -14,6 +14,8 @@ */ #include +#include +#include #include "AVSCommon/Utils/Timing/MultiTimer.h" #include "AVSCommon/Utils/WaitEvent.h" @@ -61,7 +63,7 @@ TEST(MultiTimerTest, test_executionOrderFollowExpirationTime) { EXPECT_TRUE(false); }); - sleep(1); + std::this_thread::sleep_for(std::chrono::seconds(1)); timer.submitTask(std::chrono::milliseconds(10), [&calledEvent, &counter] { // This function is due first and should be called first. counter++; diff --git a/AVSCommon/Utils/test/TimeUtilsTest.cpp b/AVSCommon/Utils/test/TimeUtilsTest.cpp index ddd9c13b90..b05456dcc5 100644 --- a/AVSCommon/Utils/test/TimeUtilsTest.cpp +++ b/AVSCommon/Utils/test/TimeUtilsTest.cpp @@ -45,6 +45,21 @@ TEST(TimeTest, test_stringConversion) { ASSERT_EQ(dateTm.tm_min, 30); } +TEST(TimeTest, test_iso8601StringConversion) { + TimeUtils timeUtils; + std::string iso8601Str{"1986-08-10T21:30:00+0000"}; + int64_t unixTime; + auto successUnix = timeUtils.convert8601TimeStringToUnix(iso8601Str, &unixTime); + ASSERT_TRUE(successUnix); + + std::chrono::system_clock::time_point utcTimePoint; + auto successUtcTimePoint = timeUtils.convert8601TimeStringToUtcTimePoint(iso8601Str, &utcTimePoint); + ASSERT_TRUE(successUtcTimePoint); + + auto sec = std::chrono::duration_cast(utcTimePoint.time_since_epoch()); + ASSERT_EQ(static_cast(sec.count()), unixTime); +} + TEST(TimeTest, test_stringConversionError) { TimeUtils timeUtils; std::string dateStr{"1986-8-10T21:30:00+0000"}; diff --git a/AVSGatewayManager/src/CMakeLists.txt b/AVSGatewayManager/src/CMakeLists.txt index 3761e0d188..d120665aa7 100644 --- a/AVSGatewayManager/src/CMakeLists.txt +++ b/AVSGatewayManager/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=avsGatewayManager") -add_library(AVSGatewayManager SHARED +add_library(AVSGatewayManager AVSGatewayManager.cpp Storage/AVSGatewayManagerStorage.cpp PostConnectVerifyGatewaySender.cpp diff --git a/ApplicationUtilities/AndroidUtilities/src/CMakeLists.txt b/ApplicationUtilities/AndroidUtilities/src/CMakeLists.txt index 9b6b70e57a..8fa34a4c97 100644 --- a/ApplicationUtilities/AndroidUtilities/src/CMakeLists.txt +++ b/ApplicationUtilities/AndroidUtilities/src/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_definitions("-DACSDK_LOG_MODULE=androidUtilities") -add_library(AndroidUtilities SHARED +add_library(AndroidUtilities AndroidLogger.cpp AndroidSLESBufferQueue.cpp AndroidSLESEngine.cpp @@ -12,8 +12,7 @@ add_library(AndroidUtilities SHARED target_include_directories(AndroidUtilities PUBLIC "${AndroidUtilities_SOURCE_DIR}/include" "${AudioResources_SOURCE_DIR}/include" - "${AVSCommon_INCLUDE_DIRS}" - "${ANDROID_NDK}/sysroot/usr/include") + "${AVSCommon_INCLUDE_DIRS}") target_link_libraries(AndroidUtilities AVSCommon OpenSLES log) diff --git a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h index bc16592cf5..2569e2d653 100644 --- a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h +++ b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h @@ -68,8 +68,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -143,6 +144,7 @@ class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerI std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, @@ -202,6 +204,7 @@ class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerI std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, @@ -449,7 +452,7 @@ AudioInputProcessor. std::shared_ptr alertStorage, std::shared_ptr messageStorage, std::shared_ptr notificationsStorage, - std::unique_ptr deviceSettingStorage, + std::shared_ptr deviceSettingStorage, std::shared_ptr bluetoothStorage, std::shared_ptr miscStorage, std::unordered_set> @@ -1084,6 +1087,13 @@ AudioInputProcessor. */ std::shared_ptr getDeviceSetup(); + /** + * Gets the @c BluetoothLocalInterface for local applications that wish to invoke Bluetooth functionality. + * + * @return A shared_ptr to the @c BluetoothLocalInterface. + */ + std::shared_ptr getBluetoothLocal(); + private: /** * Initializes the SDK and "glues" all the components together. @@ -1183,6 +1193,9 @@ AudioInputProcessor. /// The alerts capability agent. std::shared_ptr m_alertsCapabilityAgent; + /// The bluetooth capability agent. + std::shared_ptr m_bluetoothLocal; + /// The bluetooth notifier. std::shared_ptr m_bluetoothNotifier; diff --git a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientComponent.h b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientComponent.h index d37fdf26b7..0673c3c6c6 100644 --- a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientComponent.h +++ b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientComponent.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,7 @@ using DefaultClientComponent = acsdkManufactory::Component< std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, diff --git a/ApplicationUtilities/DefaultClient/include/DefaultClient/ExternalCapabilitiesBuilderInterface.h b/ApplicationUtilities/DefaultClient/include/DefaultClient/ExternalCapabilitiesBuilderInterface.h index 9536889609..615a48c9dd 100644 --- a/ApplicationUtilities/DefaultClient/include/DefaultClient/ExternalCapabilitiesBuilderInterface.h +++ b/ApplicationUtilities/DefaultClient/include/DefaultClient/ExternalCapabilitiesBuilderInterface.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -159,6 +160,7 @@ class ExternalCapabilitiesBuilderInterface { * @param powerResourceManager Object to manage power resource. * @param softwareComponentReporter Object to report adapters' versions. * @param playbackRouter Object to route local playback control command. + * @param endpointRegistrationManager Object to manage endpoints. * @return A list with all capabilities as well as objects that require explicit shutdown. Shutdown will be * performed in the reverse order of occurrence. */ @@ -188,7 +190,9 @@ class ExternalCapabilitiesBuilderInterface { #endif std::shared_ptr powerResourceManager, std::shared_ptr softwareComponentReporter, - std::shared_ptr playbackRouter) = 0; + std::shared_ptr playbackRouter, + std::shared_ptr + endpointRegistrationManager) = 0; }; } // namespace defaultClient diff --git a/ApplicationUtilities/DefaultClient/src/CMakeLists.txt b/ApplicationUtilities/DefaultClient/src/CMakeLists.txt index 10d8024461..3ff7530960 100644 --- a/ApplicationUtilities/DefaultClient/src/CMakeLists.txt +++ b/ApplicationUtilities/DefaultClient/src/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_definitions("-DACSDK_LOG_MODULE=defaultClient") -add_library(DefaultClient SHARED +add_library(DefaultClient ConnectionRetryTrigger.cpp DefaultClient.cpp DefaultClientComponent.cpp @@ -75,7 +75,10 @@ if (PCC) endif() if (MC) - target_link_libraries(DefaultClient acsdkMessagingController) + target_link_libraries(DefaultClient + acsdkMessenger + acsdkMessagingController + ) endif() if (MCC) diff --git a/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp b/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp index fe2f8e270a..e377a10f05 100644 --- a/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp +++ b/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp @@ -187,7 +187,7 @@ std::unique_ptr DefaultClient::create( std::shared_ptr alertStorage, std::shared_ptr messageStorage, std::shared_ptr notificationsStorage, - std::unique_ptr deviceSettingStorage, + std::shared_ptr deviceSettingStorage, std::shared_ptr bluetoothStorage, std::shared_ptr miscStorage, std::unordered_set> @@ -608,6 +608,16 @@ bool DefaultClient::initialize( return false; } + m_bluetoothLocal = manufactory->get>(); + if (!m_bluetoothLocal) { +#ifdef BLUETOOTH_ENABLED + ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateBluetoothLocal")); + return false; +#else + ACSDK_DEBUG5(LX("nullBluetooth").m("Bluetooth disabled")); +#endif + } + m_bluetoothNotifier = manufactory->get>(); if (!m_bluetoothNotifier) { #ifdef BLUETOOTH_ENABLED @@ -1100,7 +1110,8 @@ bool DefaultClient::initialize( #endif powerResourceManager, m_softwareReporterCapabilityAgent, - m_playbackRouter); + m_playbackRouter, + m_endpointRegistrationManager); for (auto& capability : externalCapabilities.first) { if (capability.configuration.hasValue()) { m_defaultEndpointBuilder->withCapability(capability.configuration.value(), capability.directiveHandler); @@ -1674,6 +1685,10 @@ std::shared_ptr DefaultClient: return m_deviceSetup; } +std::shared_ptr DefaultClient::getBluetoothLocal() { + return m_bluetoothLocal; +} + DefaultClient::~DefaultClient() { while (!m_shutdownObjects.empty()) { if (m_shutdownObjects.back()) { diff --git a/ApplicationUtilities/DefaultClient/src/DefaultClientComponent.cpp b/ApplicationUtilities/DefaultClient/src/DefaultClientComponent.cpp index 64dfcb8f67..98bab7f423 100644 --- a/ApplicationUtilities/DefaultClient/src/DefaultClientComponent.cpp +++ b/ApplicationUtilities/DefaultClient/src/DefaultClientComponent.cpp @@ -24,6 +24,7 @@ #include #ifdef ENABLE_MC #include +#include #endif #include #include @@ -302,6 +303,7 @@ DefaultClientComponent getComponent( .addComponent(acsdkExternalMediaPlayer::getBackwardsCompatibleComponent(adapterCreationMap)) .addComponent(acsdkInteractionModel::getComponent()) #ifdef ENABLE_MC + .addComponent(acsdkMessenger::getComponent()) .addComponent(acsdkMessagingController::getComponent()) #endif .addComponent(acsdkNotifications::getComponent()) diff --git a/ApplicationUtilities/Resources/Audio/src/CMakeLists.txt b/ApplicationUtilities/Resources/Audio/src/CMakeLists.txt index 1486df6ed7..2b66b0c3c4 100644 --- a/ApplicationUtilities/Resources/Audio/src/CMakeLists.txt +++ b/ApplicationUtilities/Resources/Audio/src/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -add_library(AudioResources SHARED +add_library(AudioResources AlertsAudioFactory.cpp AudioFactory.cpp NotificationsAudioFactory.cpp diff --git a/ApplicationUtilities/SDKComponent/src/CMakeLists.txt b/ApplicationUtilities/SDKComponent/src/CMakeLists.txt index d5468ea62a..02e137af95 100644 --- a/ApplicationUtilities/SDKComponent/src/CMakeLists.txt +++ b/ApplicationUtilities/SDKComponent/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=SDKComponent") -add_library(SDKComponent SHARED SDKComponent.cpp) +add_library(SDKComponent SDKComponent.cpp) target_include_directories(SDKComponent PUBLIC "${SDKComponent_SOURCE_DIR}/include") diff --git a/ApplicationUtilities/SystemSoundPlayer/src/CMakeLists.txt b/ApplicationUtilities/SystemSoundPlayer/src/CMakeLists.txt index c46cd4fa4d..e76e0175a9 100644 --- a/ApplicationUtilities/SystemSoundPlayer/src/CMakeLists.txt +++ b/ApplicationUtilities/SystemSoundPlayer/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=systemSoundPlayer") -add_library(SystemSoundPlayer SHARED SystemSoundPlayer.cpp) +add_library(SystemSoundPlayer SystemSoundPlayer.cpp) target_include_directories(SystemSoundPlayer PUBLIC "${SystemSoundPlayer_SOURCE_DIR}/include") diff --git a/BluetoothImplementations/BlueZ/include/BlueZ/DBusConnection.h b/BluetoothImplementations/BlueZ/include/BlueZ/DBusConnection.h index 2542ede183..53d510ea34 100644 --- a/BluetoothImplementations/BlueZ/include/BlueZ/DBusConnection.h +++ b/BluetoothImplementations/BlueZ/include/BlueZ/DBusConnection.h @@ -84,12 +84,16 @@ class DBusConnection { * Private constructor used in create() method. * * @param connection Raw @c GDBusConnection pointer to attach to. + * @param connectionType A @c GBusType of the connection. */ - explicit DBusConnection(GDBusConnection* connection); + explicit DBusConnection(GDBusConnection* connection, GBusType connectionType); /// Raw @c GDBusConnection* pointer used for operations GDBusConnection* m_connection; + /// GBusType of the connection. + const GBusType m_connectionType; + /// Mutex to guard subscriptions std::mutex m_subscriptionsMutex; diff --git a/BluetoothImplementations/BlueZ/src/CMakeLists.txt b/BluetoothImplementations/BlueZ/src/CMakeLists.txt index 154c17d446..7c4b4388c5 100644 --- a/BluetoothImplementations/BlueZ/src/CMakeLists.txt +++ b/BluetoothImplementations/BlueZ/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=bluetoothImplementationsBlueZ") -add_library(BluetoothImplementationsBlueZ SHARED +add_library(BluetoothImplementationsBlueZ BlueZA2DPSink.cpp BlueZA2DPSource.cpp BlueZAVRCPController.cpp diff --git a/BluetoothImplementations/BlueZ/src/DBusConnection.cpp b/BluetoothImplementations/BlueZ/src/DBusConnection.cpp index 51296ea3e7..24f57a6d15 100644 --- a/BluetoothImplementations/BlueZ/src/DBusConnection.cpp +++ b/BluetoothImplementations/BlueZ/src/DBusConnection.cpp @@ -45,9 +45,14 @@ std::unique_ptr DBusConnection::create(GBusType connectionType) return nullptr; } + if (g_dbus_connection_is_closed(connection)) { + ACSDK_ERROR(LX("createNewFailed").d("reason", "connection is closed.")); + return nullptr; + } + g_dbus_connection_set_exit_on_close(connection, false); - return std::unique_ptr(new DBusConnection(connection)); + return std::unique_ptr(new DBusConnection(connection, connectionType)); } unsigned int DBusConnection::subscribeToSignal( @@ -105,7 +110,9 @@ unsigned int DBusConnection::subscribeToSignal( return subId; } -DBusConnection::DBusConnection(GDBusConnection* connection) : m_connection{connection} { +DBusConnection::DBusConnection(GDBusConnection* connection, GBusType connectionType) : + m_connection{connection}, + m_connectionType{connectionType} { } void DBusConnection::close() { @@ -125,7 +132,9 @@ void DBusConnection::close() { } g_dbus_connection_flush_sync(m_connection, nullptr, nullptr); - g_dbus_connection_close_sync(m_connection, nullptr, nullptr); + if (G_BUS_TYPE_SYSTEM != m_connectionType) { + g_dbus_connection_close_sync(m_connection, nullptr, nullptr); + } g_object_unref(m_connection); m_connection = nullptr; } diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e81758c14..a49a5c5b6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,12 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) # Set project information -project(AlexaClientSDK VERSION 1.25.0 LANGUAGES CXX) +project(AlexaClientSDK VERSION 1.26.0 LANGUAGES CXX) set(PROJECT_BRIEF "A cross-platform, modular SDK for interacting with the Alexa Voice Service") # This variable should be used by extension packages to include cmake files from this project. get_filename_component(AVS_CORE . ABSOLUTE) +get_filename_component(AVS_CORE_BINARY ${PROJECT_BINARY_DIR} ABSOLUTE) # This variable should be used to get the cmake build files. get_filename_component(AVS_CMAKE_BUILD cmakeBuild ABSOLUTE) @@ -18,9 +19,14 @@ include(${AVS_CMAKE_BUILD}/cmake/PrepareInstall.cmake) configure_file ( "${PROJECT_SOURCE_DIR}/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h.in" - "${PROJECT_SOURCE_DIR}/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h" + "${PROJECT_BINARY_DIR}/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h" ) +configure_file ( + "${PROJECT_SOURCE_DIR}/AVSCommon/Utils/include/AVSCommon/Utils/SDKConfig.h.in" + "${PROJECT_BINARY_DIR}/AVSCommon/Utils/include/AVSCommon/Utils/SDKConfig.h" +) + # Add utils under ThirdParty first so extensions can also utilize them add_subdirectory("ThirdParty") @@ -49,7 +55,6 @@ if (DIAGNOSTICS) endif() add_subdirectory("InterruptModel") add_subdirectory("PlaylistParser") -add_subdirectory("KWD") add_subdirectory("CapabilityAgents") if (NOT MSVC) add_subdirectory("Integration") diff --git a/CapabilitiesDelegate/src/CMakeLists.txt b/CapabilitiesDelegate/src/CMakeLists.txt index ae94980113..c759b594d8 100644 --- a/CapabilitiesDelegate/src/CMakeLists.txt +++ b/CapabilitiesDelegate/src/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_definitions("-DACSDK_LOG_MODULE=capabilitiesDelegate") -add_library(CapabilitiesDelegate SHARED +add_library(CapabilitiesDelegate CapabilitiesDelegate.cpp DiscoveryEventSender.cpp PostConnectCapabilitiesPublisher.cpp diff --git a/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp b/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp index 0e59286d1f..57ba3563b2 100644 --- a/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp +++ b/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp @@ -781,6 +781,17 @@ void CapabilitiesDelegate::filterUnchangedPendingAddOrUpdateEndpointsLocked( std::unordered_map addOrUpdateEndpointIdToConfigPairs = m_addOrUpdateEndpoints.pending; + // Find the endpoints with the same ID that are being added and deleted + for (auto& endpointIdToConfigPair : addOrUpdateEndpointIdToConfigPairs) { + if (m_deleteEndpoints.pending.end() != m_deleteEndpoints.pending.find(endpointIdToConfigPair.first)) { + ACSDK_DEBUG9(LX(__func__) + .d("step", "endpoint removed in deleteReport") + .d("reason", "endpoint being added") + .sensitive("endpointId", endpointIdToConfigPair.first)); + m_deleteEndpoints.pending.erase(endpointIdToConfigPair.first); + } + } + /// Find the endpoints that are unchanged for (auto& endpointIdToConfigPair : addOrUpdateEndpointIdToConfigPairs) { auto storedEndpointConfigId = storedEndpointConfig->find(endpointIdToConfigPair.first); diff --git a/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp b/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp index 887cb3814b..f170dba2fd 100644 --- a/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp +++ b/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp @@ -1032,6 +1032,83 @@ TEST_F( instance->shutdown(); } +/** + * Tests if before the stale endpoint is deleted and the stale endpoint is added, that the first + * createPostConnectOperation will create a deleteReport for the stale endpoint, but the second + * createPostConnectOperation will return a nullptr operation because the stale endpoint has been added, and this + * results in no change in capabilities. + */ +TEST_F( + CapabilitiesDelegateTest, + test_createTwoPostConnectOperationWithStaleEndpointAndPendingEndpointsWithSameEndpointConfigs) { + auto unchangedEndpointAttributes = createEndpointAttributes("endpointId"); + auto unchangedEndpointConfiguration = createCapabilityConfiguration(); + std::vector unchangedCapabilityConfigs = {unchangedEndpointConfiguration}; + + auto staleEndpointAttributes = createEndpointAttributes("staleEndpointId"); + auto staleEndpointConfiguration = createCapabilityConfiguration(); + std::vector staleCapabilityConfigs = {staleEndpointConfiguration}; + + std::string unchangedEndpointConfig = + utils::getEndpointConfigJson(unchangedEndpointAttributes, unchangedCapabilityConfigs); + std::string staleEndpointConfig = utils::getEndpointConfigJson(staleEndpointAttributes, staleCapabilityConfigs); + EXPECT_CALL(*m_mockCapabilitiesStorage, open()).Times(1).WillOnce(Return(true)); + EXPECT_CALL(*m_mockCapabilitiesStorage, load(_)) + .Times(2) + .WillRepeatedly( + Invoke([unchangedEndpointAttributes, unchangedEndpointConfig, staleEndpointAttributes, staleEndpointConfig]( + std::unordered_map* storedEndpoints) { + storedEndpoints->insert({unchangedEndpointAttributes.endpointId, unchangedEndpointConfig}); + storedEndpoints->insert({staleEndpointAttributes.endpointId, staleEndpointConfig}); + return true; + })); + int numCallbacks = 0; + EXPECT_CALL(*m_mockCapabilitiesDelegateObserver, onCapabilitiesStateChange(_, _, _, _)) + .Times(2) + .WillRepeatedly(Invoke([&numCallbacks]( + CapabilitiesDelegateObserverInterface::State newState, + CapabilitiesDelegateObserverInterface::Error newError, + std::vector addOrUpdateReportEndpointIdentifiers, + std::vector deleteReportEndpointIdentifiers) { + if (numCallbacks == 0) { + EXPECT_EQ(newState, CapabilitiesDelegateObserverInterface::State::UNINITIALIZED); + EXPECT_EQ(newError, CapabilitiesDelegateObserverInterface::Error::UNINITIALIZED); + EXPECT_EQ(addOrUpdateReportEndpointIdentifiers, std::vector{}); + EXPECT_EQ(deleteReportEndpointIdentifiers, std::vector{}); + } else { + EXPECT_EQ(newState, CapabilitiesDelegateObserverInterface::State::SUCCESS); + EXPECT_EQ(newError, CapabilitiesDelegateObserverInterface::Error::SUCCESS); + EXPECT_EQ(addOrUpdateReportEndpointIdentifiers, std::vector{"staleEndpointId"}); + EXPECT_EQ(deleteReportEndpointIdentifiers, std::vector{}); + } + numCallbacks++; + })); + + auto instance = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); + instance->addCapabilitiesObserver(m_mockCapabilitiesDelegateObserver); + instance->addOrUpdateEndpoint(unchangedEndpointAttributes, unchangedCapabilityConfigs); + + /// Observer callback should only contain the pending endpoint to add (since that is already registered), + /// but not the stale endpoint to delete (since that still needs to be sent to AVS). + EXPECT_CALL( + *m_mockCapabilitiesDelegateObserver, + onCapabilitiesStateChange( + CapabilitiesDelegateObserverInterface::State::SUCCESS, + CapabilitiesDelegateObserverInterface::Error::SUCCESS, + std::vector{unchangedEndpointAttributes.endpointId}, + std::vector{})); + + auto publisher = instance->createPostConnectOperation(); + ASSERT_NE(publisher, nullptr); + + instance->addOrUpdateEndpoint(staleEndpointAttributes, staleCapabilityConfigs); + auto publisher1 = instance->createPostConnectOperation(); + ASSERT_EQ(publisher1, nullptr); + + // Clean-up. + instance->shutdown(); +} + /** * Tests if the createPostConnectOperation() creates a new @c PostConnectCapabilitiesPublisher when storage is empty. * When the capabilities are successfully published, a subsequent call to createPostConnectOperation() results in a diff --git a/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h b/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h index 9aafd05c5b..e01cda8854 100644 --- a/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h +++ b/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h @@ -225,10 +225,18 @@ class AudioInputProcessor * cloud will perform additional verification of the wakeword audio before proceeding to recognize the subsequent * audio. * + * @attention User perceived latency metrics will only be accurate if the startOfSpeechTimestamp is correct. Some + * keyword detectors determine start of speech at different times, and in some cases exclude the wakeword. This + * leads to a later timestamp and excludes the wakeword duration from the user perceived latency calculation, thus + * underestimating the latency. Verify that the startOfSpeechTimestamp is including the wakeword duration if the + * audio stream is initiated by wakeword detection. (Tap-To-Talk remains unaffected) + * * @param audioProvider The @c AudioProvider to stream audio from. * @param initiator The type of interface that initiated this recognize event. * @param startOfSpeechTimestamp Moment in time when user started talking to Alexa. This parameter is optional - * and it is used to measure user perceived latency. + * and it is used to measure user perceived latency. The startOfSpeechTimestamp must include the wakeword + * duration if the audio stream is initiated by a wakeword, otherwise the latency calculation will not be + * correct. * @param begin The @c Index in @c audioProvider.stream where audio streaming should begin. This parameter is * optional, and defaults to @c INVALID_INDEX. When this parameter is not specified, @c recognize() will * stream audio starting at the time of the @c recognize() call. If the @c initiator is @c WAKEWORD, and this @@ -905,6 +913,11 @@ class AudioInputProcessor */ std::chrono::milliseconds m_timeSinceLastPartialMS; + /** + * Value that will contain the resource type since last partial LPM state change when AIP acquires the wakelock. + */ + avsCommon::sdkInterfaces::PowerResourceManagerInterface::PartialStateBitSet m_resourceFlags; + /** * Value to indicate if audio encoder is being used. */ diff --git a/CapabilityAgents/AIP/include/AIP/AudioProvider.h b/CapabilityAgents/AIP/include/AIP/AudioProvider.h index 4feb873795..723f8feb82 100644 --- a/CapabilityAgents/AIP/include/AIP/AudioProvider.h +++ b/CapabilityAgents/AIP/include/AIP/AudioProvider.h @@ -49,6 +49,39 @@ struct AudioProvider { bool canOverride, bool canBeOverridden); + /** + * This function provides an @c AudioProvider for a TapToTalk Interaction. + * + * @param stream The @c ByteStream to use for audio input. + * @param format The @c AudioFormat of the data in @c byteStream. + * @return A TapToTalk configured @c AudioProvider. + */ + static AudioProvider TapAudioProvider( + std::shared_ptr stream, + const avsCommon::utils::AudioFormat& format); + + /** + * This function provides an @c AudioProvider for a Wakeword Interaction. + * + * @param stream The @c ByteStream to use for audio input. + * @param format The @c AudioFormat of the data in @c byteStream. + * @return A Wakeword configured @c AudioProvider. + */ + static AudioProvider WakeAudioProvider( + std::shared_ptr stream, + const avsCommon::utils::AudioFormat& format); + + /** + * This function provides an @c AudioProvider for a HoldToTalk Interaction. + * + * @param stream The @c ByteStream to use for audio input. + * @param format The @c AudioFormat of the data in @c byteStream. + * @return A HoldToTalk configured @c AudioProvider. + */ + static AudioProvider HoldAudioProvider( + std::shared_ptr stream, + const avsCommon::utils::AudioFormat& format); + /** * This function provides an invalid AudioProvider which has no stream associated with it. * @@ -101,6 +134,40 @@ inline AudioProvider::AudioProvider( canBeOverridden{canBeOverridden} { } +inline AudioProvider AudioProvider::TapAudioProvider( + std::shared_ptr stream, + const avsCommon::utils::AudioFormat& format) { + bool alwaysReadable = true; + bool canOverride = true; + bool canBeOverridden = true; + AudioProvider tapProvider{stream, format, ASRProfile::NEAR_FIELD, alwaysReadable, canOverride, canBeOverridden}; + + return tapProvider; +} + +inline AudioProvider AudioProvider::WakeAudioProvider( + std::shared_ptr stream, + const avsCommon::utils::AudioFormat& format) { + bool alwaysReadable = true; + bool canOverride = false; + bool canBeOverridden = true; + AudioProvider WakeAudioProvider{ + stream, format, ASRProfile::NEAR_FIELD, alwaysReadable, canOverride, canBeOverridden}; + + return WakeAudioProvider; +} + +inline AudioProvider AudioProvider::HoldAudioProvider( + std::shared_ptr stream, + const avsCommon::utils::AudioFormat& format) { + bool alwaysReadable = false; + bool canOverride = true; + bool canBeOverridden = false; + AudioProvider HoldProvider{stream, format, ASRProfile::CLOSE_TALK, alwaysReadable, canOverride, canBeOverridden}; + + return HoldProvider; +} + inline const AudioProvider& AudioProvider::null() { static AudioProvider nullAudioProvider{nullptr, {avsCommon::utils::AudioFormat::Encoding::LPCM, diff --git a/CapabilityAgents/AIP/src/AudioInputProcessor.cpp b/CapabilityAgents/AIP/src/AudioInputProcessor.cpp index c0e4c5f5e3..d0944a00c8 100644 --- a/CapabilityAgents/AIP/src/AudioInputProcessor.cpp +++ b/CapabilityAgents/AIP/src/AudioInputProcessor.cpp @@ -209,6 +209,14 @@ static const std::string WW_DURATION_ACTIVITY_NAME = METRIC_ACTIVITY_NAME_PREFIX static const std::string STOP_CAPTURE_RECEIVED = "STOP_CAPTURE"; static const std::string STOP_CAPTURE_RECEIVED_ACTIVITY_NAME = METRIC_ACTIVITY_NAME_PREFIX_AIP + STOP_CAPTURE_RECEIVED; +/// The duration metric for acquire power resource +static const std::string ACQUIRE_POWER_RESOURCE = "ACQUIRE_POWER_RESOURCE"; +static const std::string ACQUIRE_POWER_RESOURCE_ACTIVITY = METRIC_ACTIVITY_NAME_PREFIX_AIP + ACQUIRE_POWER_RESOURCE; + +/// The duration metric for release power resource +static const std::string RELEASE_POWER_RESOURCE = "RELEASE_POWER_RESOURCE"; +static const std::string RELEASE_POWER_RESOURCE_ACTIVITY = METRIC_ACTIVITY_NAME_PREFIX_AIP + RELEASE_POWER_RESOURCE; + /// End of Speech Offset Received Activity Name for AIP metric source static const std::string END_OF_SPEECH_OFFSET_RECEIVED = "END_OF_SPEECH_OFFSET"; static const std::string END_OF_SPEECH_OFFSET_RECEIVED_ACTIVITY_NAME = @@ -233,6 +241,15 @@ static const std::string AUDIO_ENCODING_FORMAT_LPCM = "LPCMAudioEncoding"; /// The default resolveKey used as a placeholder when only one encoding format is configured for @c AudioInputProcessor static const std::string DEFAULT_RESOLVE_KEY = "DEFAULT_RESOLVE_KEY"; +/// Keys for instance entry metric specific fields +static const std::string ENTRY_METRIC_ACTOR_NAME = "AudioInputProcessor"; +static const std::string ENTRY_METRIC_ACTIVITY_NAME = METRIC_ACTIVITY_NAME_PREFIX_AIP + ENTRY_METRIC_ACTOR_NAME; +static const std::string ENTRY_METRIC_KEY_SEGMENT_ID = "segment_id"; +static const std::string ENTRY_METRIC_KEY_ACTOR = "actor"; +static const std::string ENTRY_METRIC_KEY_ENTRY_TYPE = "entry_type"; +static const std::string ENTRY_METRIC_KEY_ENTRY_NAME = "entry_name"; +static const std::string ENTRY_METRIC_NAME_STATE_CHANGE = "StateChange"; + /// Preroll duration is a fixed 500ms. static const std::chrono::milliseconds PREROLL_DURATION = std::chrono::milliseconds(500); @@ -316,6 +333,42 @@ static void submitMetric( recordMetric(metricRecorder, metricEvent); } +/** + * Creates and records an instance entry metric with the given identifiers and metadata. + * @param metricRecorder The @c MetricRecorderInterface which records Metric events. + * @param segmentId The segmentId corresponding to this metric event. + * @param name The name of this metric + * @param metadata Any metadata to be associated with this metric; default is empty + */ +static void submitInstanceEntryMetric( + const std::shared_ptr& metricRecorder, + const std::string& segmentId, + const std::string& name, + const std::map& metadata = {}) { + if (segmentId.empty() || name.empty()) { + ACSDK_ERROR(LX(__FUNCTION__).m("Unable to create instance metric").d("segmentId", segmentId).d("name", name)); + return; + } + + auto metricBuilder = MetricEventBuilder{}.setActivityName(ENTRY_METRIC_ACTIVITY_NAME); + metricBuilder.addDataPoint( + DataPointStringBuilder{}.setName(ENTRY_METRIC_KEY_SEGMENT_ID).setValue(segmentId).build()); + metricBuilder.addDataPoint( + DataPointStringBuilder{}.setName(ENTRY_METRIC_KEY_ACTOR).setValue(ENTRY_METRIC_ACTOR_NAME).build()); + metricBuilder.addDataPoint(DataPointStringBuilder{}.setName(ENTRY_METRIC_KEY_ENTRY_NAME).setValue(name).build()); + metricBuilder.addDataPoint( + DataPointStringBuilder{}.setName(ENTRY_METRIC_KEY_ENTRY_TYPE).setValue("INSTANCE").build()); + for (auto const& pair : metadata) { + metricBuilder.addDataPoint(DataPointStringBuilder{}.setName(pair.first).setValue(pair.second).build()); + } + auto metric = metricBuilder.build(); + if (metric == nullptr) { + ACSDK_ERROR(LX(__FUNCTION__).m("Error creating instance entry metric.")); + return; + } + recordMetric(metricRecorder, metric); +} + /** * Creates the SpeechRecognizer capability configuration. * @@ -651,6 +704,7 @@ AudioInputProcessor::AudioInputProcessor( m_expectSpeechTimeoutHandler{expectSpeechTimeoutHandler}, m_timeSinceLastResumeMS{std::chrono::milliseconds(0)}, m_timeSinceLastPartialMS{std::chrono::milliseconds(0)}, + m_resourceFlags{0}, m_usingEncoder{false}, m_messageRequestResolver{nullptr}, m_encodingAudioFormats{{DEFAULT_RESOLVE_KEY, AudioFormat::Encoding::LPCM}} { @@ -1224,6 +1278,10 @@ bool AudioInputProcessor::executeRecognize( .setName("TIME_SINCE_PARTIAL_ID") .setValue(std::to_string(m_timeSinceLastPartialMS.count())) .build()) + .addDataPoint(DataPointStringBuilder{} + .setName("RESOURCE_TYPE_ID") + .setValue(std::to_string(m_resourceFlags.to_ulong())) + .build()) .addDataPoint(DataPointCounterBuilder{}.setName(START_OF_UTTERANCE).increment(1).build()), m_preCachedDialogRequestId); @@ -1260,6 +1318,12 @@ bool AudioInputProcessor::executeRecognize( ACSDK_DEBUG(LX(__func__).d("WW_DURATION(ms)", duration.count())); } + submitInstanceEntryMetric( + m_metricRecorder, + m_preCachedDialogRequestId, + START_OF_UTTERANCE, + std::map{{"initiator", !initiatorString.empty() ? initiatorString : "unknown"}}); + return true; } @@ -1567,7 +1631,19 @@ void AudioInputProcessor::setState(ObserverInterface::State state) { // Reset the user inactivity if transitioning to or from `RECOGNIZING` state. if (ObserverInterface::State::RECOGNIZING == m_state || ObserverInterface::State::RECOGNIZING == state) { - m_userInactivityMonitor->onUserActive(); + m_executor.submit([this]() { m_userInactivityMonitor->onUserActive(); }); + } + + auto currentDialogRequestId = + m_preCachedDialogRequestId.empty() ? m_directiveSequencer->getDialogRequestId() : m_preCachedDialogRequestId; + ACSDK_DEBUG5(LX(__func__).d("currentDialogRequestId", currentDialogRequestId)); + if (!currentDialogRequestId.empty()) { + submitInstanceEntryMetric( + m_metricRecorder, + currentDialogRequestId, + ENTRY_METRIC_NAME_STATE_CHANGE, + std::map{{"from", ObserverInterface::stateToString(m_state)}, + {"to", ObserverInterface::stateToString(state)}}); } ACSDK_DEBUG(LX("setState").d("from", m_state).d("to", state)); @@ -1791,18 +1867,35 @@ void AudioInputProcessor::managePowerResource(ObserverInterface::State newState) if (!m_powerResourceId) { return; } - + auto startTime = steady_clock::now(); ACSDK_DEBUG5(LX(__func__).d("state", newState)); switch (newState) { case ObserverInterface::State::RECOGNIZING: case ObserverInterface::State::EXPECTING_SPEECH: m_powerResourceManager->acquire(m_powerResourceId); m_timeSinceLastResumeMS = m_powerResourceManager->getTimeSinceLastResumeMS(); - m_timeSinceLastPartialMS = m_powerResourceManager->getTimeSinceLastPartialMS(POWER_RESOURCE_COMPONENT_NAME); + m_timeSinceLastPartialMS = + m_powerResourceManager->getTimeSinceLastPartialMS(POWER_RESOURCE_COMPONENT_NAME, m_resourceFlags); + submitMetric( + m_metricRecorder, + MetricEventBuilder{} + .setActivityName(ACQUIRE_POWER_RESOURCE_ACTIVITY) + .addDataPoint(DataPointDurationBuilder{duration_cast(steady_clock::now() - startTime)} + .setName(ACQUIRE_POWER_RESOURCE) + .build()), + m_preCachedDialogRequestId); break; case ObserverInterface::State::BUSY: case ObserverInterface::State::IDLE: m_powerResourceManager->release(m_powerResourceId); + submitMetric( + m_metricRecorder, + MetricEventBuilder{} + .setActivityName(RELEASE_POWER_RESOURCE_ACTIVITY) + .addDataPoint(DataPointDurationBuilder{duration_cast(steady_clock::now() - startTime)} + .setName(RELEASE_POWER_RESOURCE) + .build()), + m_preCachedDialogRequestId); break; } } diff --git a/CapabilityAgents/AIP/src/CMakeLists.txt b/CapabilityAgents/AIP/src/CMakeLists.txt index 461688d652..2c2eed5328 100644 --- a/CapabilityAgents/AIP/src/CMakeLists.txt +++ b/CapabilityAgents/AIP/src/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_definitions("-DACSDK_LOG_MODULE=aip") -add_library(AIP SHARED +add_library(AIP AudioInputProcessor.cpp) target_include_directories(AIP PUBLIC "${AIP_SOURCE_DIR}/include" diff --git a/CapabilityAgents/Alexa/include/Alexa/AlexaEventProcessedNotifier.h b/CapabilityAgents/Alexa/include/Alexa/AlexaEventProcessedNotifier.h index 4cc3cfc09d..5a07998cd8 100644 --- a/CapabilityAgents/Alexa/include/Alexa/AlexaEventProcessedNotifier.h +++ b/CapabilityAgents/Alexa/include/Alexa/AlexaEventProcessedNotifier.h @@ -19,7 +19,7 @@ #include #include -#include +#include #include namespace alexaClientSDK { diff --git a/CapabilityAgents/Alexa/src/CMakeLists.txt b/CapabilityAgents/Alexa/src/CMakeLists.txt index 96510885c1..179ef78c24 100644 --- a/CapabilityAgents/Alexa/src/CMakeLists.txt +++ b/CapabilityAgents/Alexa/src/CMakeLists.txt @@ -1,7 +1,7 @@ add_definitions("-DACSDK_LOG_MODULE=alexa") add_library( - Alexa SHARED + Alexa AlexaEventProcessedNotifier.cpp AlexaInterfaceCapabilityAgent.cpp AlexaInterfaceMessageSender.cpp diff --git a/CapabilityAgents/Alexa/test/CMakeLists.txt b/CapabilityAgents/Alexa/test/CMakeLists.txt index 8e31066426..145dcd3996 100644 --- a/CapabilityAgents/Alexa/test/CMakeLists.txt +++ b/CapabilityAgents/Alexa/test/CMakeLists.txt @@ -1,5 +1,10 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +add_library(AlexaCATestUtils INTERFACE) + +target_include_directories(AlexaCATestUtils INTERFACE + "${Alexa_SOURCE_DIR}/test/include") + set(INCLUDE_PATH "${Alexa_INCLUDE_DIRS}" "${AVSCommon_SOURCE_DIR}/AVS/test") diff --git a/CapabilityAgents/Alexa/test/include/MockAlexaInterfaceMessageSenderInternal.h b/CapabilityAgents/Alexa/test/include/MockAlexaInterfaceMessageSenderInternal.h new file mode 100644 index 0000000000..4a26769f4b --- /dev/null +++ b/CapabilityAgents/Alexa/test/include/MockAlexaInterfaceMessageSenderInternal.h @@ -0,0 +1,80 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_ALEXA_TEST_INCLUDE_MOCKALEXAINTERFACEMESSAGESENDERINTERNAL_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_ALEXA_TEST_INCLUDE_MOCKALEXAINTERFACEMESSAGESENDERINTERNAL_H_ + +#include + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace alexa { +namespace test { + +/// Mock class that implements the AlexaInterfaceMessageSenderInternalInterface. +class MockAlexaInterfaceMessageSenderInternal : public AlexaInterfaceMessageSenderInternalInterface { +public: + MOCK_METHOD4( + sendResponseEvent, + bool( + const std::string& instance, + const std::string& correlationToken, + const avsCommon::avs::AVSMessageEndpoint& endpoint, + const std::string& jsonPayload)); + MOCK_METHOD6( + sendResponseEvent, + bool( + const std::string& instance, + const std::string& correlationToken, + const avsCommon::avs::AVSMessageEndpoint& endpoint, + const std::string& responseNamespace, + const std::string& responseName, + const std::string& jsonPayload)); + MOCK_METHOD5( + sendErrorResponseEvent, + bool( + const std::string& instance, + const std::string& correlationToken, + const avsCommon::avs::AVSMessageEndpoint& endpoint, + const ErrorResponseType errorType, + const std::string& errorMessage)); + MOCK_METHOD5( + sendErrorResponseEvent, + bool( + const std::string& instance, + const std::string& correlationToken, + const avsCommon::avs::AVSMessageEndpoint& endpoint, + const std::string& responseNamespace, + const std::string& payload)); + MOCK_METHOD3( + sendDeferredResponseEvent, + bool(const std::string& instance, const std::string& correlationToken, const int estimatedDeferralInSeconds)); + MOCK_METHOD1( + alexaResponseTypeToErrorType, + ErrorResponseType(const avsCommon::avs::AlexaResponseType& responseType)); + MOCK_METHOD3( + sendStateReportEvent, + bool( + const std::string& instance, + const std::string& correlationToken, + const avsCommon::avs::AVSMessageEndpoint& endpoint)); +}; + +} // namespace test +} // namespace alexa +} // namespace capabilityAgents +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_ALEXA_TEST_INCLUDE_MOCKALEXAINTERFACEMESSAGESENDERINTERNAL_H_ diff --git a/CapabilityAgents/ApiGateway/src/CMakeLists.txt b/CapabilityAgents/ApiGateway/src/CMakeLists.txt index 8bb605ca39..5b12d56869 100644 --- a/CapabilityAgents/ApiGateway/src/CMakeLists.txt +++ b/CapabilityAgents/ApiGateway/src/CMakeLists.txt @@ -1,7 +1,7 @@ add_definitions("-DACSDK_LOG_MODULE=apiGateway") add_library( - ApiGateway SHARED + ApiGateway ApiGatewayCapabilityAgent.cpp ) diff --git a/CapabilityAgents/CMakeLists.txt b/CapabilityAgents/CMakeLists.txt index e673ee26b7..013ad92743 100644 --- a/CapabilityAgents/CMakeLists.txt +++ b/CapabilityAgents/CMakeLists.txt @@ -29,6 +29,10 @@ if (MCC) list(APPEND CAPABILITY_AGENTS "MeetingClientController") endif() +if (RTCSC) + list(APPEND CAPABILITY_AGENTS "RTCSessionController") +endif() + if (ENDPOINT_CONTROLLERS_POWER_CONTROLLER) list(APPEND CAPABILITY_AGENTS "PowerController") endif() diff --git a/CapabilityAgents/InteractionModel/acsdkInteractionModel/privateInclude/acsdkInteractionModel/InteractionModelNotifier.h b/CapabilityAgents/InteractionModel/acsdkInteractionModel/privateInclude/acsdkInteractionModel/InteractionModelNotifier.h index 15e0fd428a..a60a79dbdf 100644 --- a/CapabilityAgents/InteractionModel/acsdkInteractionModel/privateInclude/acsdkInteractionModel/InteractionModelNotifier.h +++ b/CapabilityAgents/InteractionModel/acsdkInteractionModel/privateInclude/acsdkInteractionModel/InteractionModelNotifier.h @@ -20,7 +20,7 @@ #include #include -#include +#include namespace alexaClientSDK { namespace acsdkInteractionModel { diff --git a/CapabilityAgents/InteractionModel/acsdkInteractionModel/src/CMakeLists.txt b/CapabilityAgents/InteractionModel/acsdkInteractionModel/src/CMakeLists.txt index 27f2876a5c..392766b04a 100644 --- a/CapabilityAgents/InteractionModel/acsdkInteractionModel/src/CMakeLists.txt +++ b/CapabilityAgents/InteractionModel/acsdkInteractionModel/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=interactionModel") -add_library(acsdkInteractionModel SHARED +add_library(acsdkInteractionModel InteractionModelCapabilityAgent.cpp InteractionModelComponent.cpp InteractionModelNotifier.cpp) diff --git a/CapabilityAgents/InteractionModel/acsdkInteractionModelInterfaces/include/acsdkInteractionModelInterfaces/InteractionModelNotifierInterface.h b/CapabilityAgents/InteractionModel/acsdkInteractionModelInterfaces/include/acsdkInteractionModelInterfaces/InteractionModelNotifierInterface.h index 1f7d64f335..17170763c3 100644 --- a/CapabilityAgents/InteractionModel/acsdkInteractionModelInterfaces/include/acsdkInteractionModelInterfaces/InteractionModelNotifierInterface.h +++ b/CapabilityAgents/InteractionModel/acsdkInteractionModelInterfaces/include/acsdkInteractionModelInterfaces/InteractionModelNotifierInterface.h @@ -18,7 +18,7 @@ #include -#include +#include #include "acsdkInteractionModelInterfaces/InteractionModelRequestProcessingObserverInterface.h" diff --git a/CapabilityAgents/ModeController/src/CMakeLists.txt b/CapabilityAgents/ModeController/src/CMakeLists.txt index 520bef3292..fc3b547367 100644 --- a/CapabilityAgents/ModeController/src/CMakeLists.txt +++ b/CapabilityAgents/ModeController/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=modeController") -add_library(ModeController SHARED +add_library(ModeController ModeControllerAttributeBuilder.cpp ModeControllerCapabilityAgent.cpp) diff --git a/CapabilityAgents/PlaybackController/src/CMakeLists.txt b/CapabilityAgents/PlaybackController/src/CMakeLists.txt index 389db8f158..205a9d5c20 100644 --- a/CapabilityAgents/PlaybackController/src/CMakeLists.txt +++ b/CapabilityAgents/PlaybackController/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=playbackcontroller") -add_library(PlaybackController SHARED +add_library(PlaybackController "${CMAKE_CURRENT_LIST_DIR}/PlaybackController.cpp" "${CMAKE_CURRENT_LIST_DIR}/PlaybackControllerComponent.cpp" "${CMAKE_CURRENT_LIST_DIR}/PlaybackRouter.cpp" diff --git a/CapabilityAgents/PlaybackController/src/PlaybackRouter.cpp b/CapabilityAgents/PlaybackController/src/PlaybackRouter.cpp index 616e7e7312..c47f2dcfb4 100644 --- a/CapabilityAgents/PlaybackController/src/PlaybackRouter.cpp +++ b/CapabilityAgents/PlaybackController/src/PlaybackRouter.cpp @@ -147,7 +147,7 @@ bool PlaybackRouter::localOperation(LocalPlaybackHandlerInterface::PlaybackOpera if (useFallback) { switch (op) { case LocalPlaybackHandlerInterface::PlaybackOperation::STOP_PLAYBACK: - case LocalPlaybackHandlerInterface::PlaybackOperation::PAUSE_PLAYBACK: + case LocalPlaybackHandlerInterface::PlaybackOperation::RESUMABLE_STOP: buttonPressed(PlaybackButton::PAUSE); break; case LocalPlaybackHandlerInterface::PlaybackOperation::RESUME_PLAYBACK: diff --git a/CapabilityAgents/PowerController/src/CMakeLists.txt b/CapabilityAgents/PowerController/src/CMakeLists.txt index 23f387506d..62f2f0e247 100644 --- a/CapabilityAgents/PowerController/src/CMakeLists.txt +++ b/CapabilityAgents/PowerController/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=powerController") -add_library(PowerController SHARED +add_library(PowerController PowerControllerCapabilityAgent.cpp) target_include_directories(PowerController diff --git a/CapabilityAgents/RangeController/src/CMakeLists.txt b/CapabilityAgents/RangeController/src/CMakeLists.txt index b51b147372..2303713413 100644 --- a/CapabilityAgents/RangeController/src/CMakeLists.txt +++ b/CapabilityAgents/RangeController/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=rangeController") -add_library(RangeController SHARED +add_library(RangeController RangeControllerAttributeBuilder.cpp RangeControllerCapabilityAgent.cpp) diff --git a/CapabilityAgents/SoftwareComponentReporter/src/CMakeLists.txt b/CapabilityAgents/SoftwareComponentReporter/src/CMakeLists.txt index aa196d7c20..765b9ec980 100644 --- a/CapabilityAgents/SoftwareComponentReporter/src/CMakeLists.txt +++ b/CapabilityAgents/SoftwareComponentReporter/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=SoftwareComponentReporter") -add_library(SoftwareComponentReporter SHARED +add_library(SoftwareComponentReporter SoftwareComponentReporterCapabilityAgent.cpp) target_include_directories(SoftwareComponentReporter PUBLIC "${SoftwareComponentReporter_SOURCE_DIR}/include") diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h index 3120b62b36..229b724c5a 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h +++ b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,9 @@ #include #include #include +#include +#include +#include namespace alexaClientSDK { namespace capabilityAgents { @@ -74,6 +78,7 @@ class SpeakerManager * Create an instance of @c SpeakerManagerInterface. @c ChannelVolumeInterfaces can be registered via * addChannelVolumeInterface. * + * @param storage A @c MiscStorageInterface to access persistent configuration. * @param contextManager A @c ContextManagerInterface to manage the context. * @param messageSender A @c MessageSenderInterface to send messages to AVS. * @param exceptionEncounteredSender An @c ExceptionEncounteredSenderInterface to send @@ -85,6 +90,7 @@ class SpeakerManager * @param metricRecorder The metric recorder. */ static std::shared_ptr createSpeakerManagerCapabilityAgent( + const std::shared_ptr& storage, const std::shared_ptr& contextManager, const std::shared_ptr& messageSender, const std::shared_ptr& @@ -99,6 +105,7 @@ class SpeakerManager * Create an instance of @c SpeakerManager, and register the @c ChannelVolumeInterfaces that will be controlled * by it. ChannelVolumeInterfaces will be grouped by @c ChannelVolumeInterface::Type. * + * @param storage A @c SpeakerManagerStorageInterface to access persistent configuration. * @param volumeInterfaces The @c ChannelVolumeInterfaces to register. * @param contextManager A @c ContextManagerInterface to manage the context. * @param messageSender A @c MessageSenderInterface to send messages to AVS. @@ -107,6 +114,7 @@ class SpeakerManager * @param metricRecorder The metric recorder. */ static std::shared_ptr create( + const std::shared_ptr& storage, const std::vector>& volumeInterfaces, std::shared_ptr contextManager, std::shared_ptr messageSender, @@ -183,20 +191,20 @@ class SpeakerManager /** * Constructor. Called after validation has occurred on parameters. * + * @param speakerManagerStorage The @c SpeakerManagerStorageInterace to register. * @param groupVolumeInterfaces The @c ChannelVolumeInterfaces to register. * @param contextManager A @c ContextManagerInterface to manage the context. * @param messageSender A @c MessageSenderInterface to send messages to AVS. * @param exceptionEncounteredSender An @c ExceptionEncounteredSenderInterface to send. * directive processing exceptions to AVS. - * @param minUnmuteVolume The volume level to increase to when unmuting. * @param metricRecorder The metric recorder. */ SpeakerManager( + const std::shared_ptr& speakerManagerStorage, const std::vector>& groupVolumeInterfaces, std::shared_ptr contextManager, std::shared_ptr messageSender, std::shared_ptr exceptionEncounteredSender, - const int minUnmuteVolume, std::shared_ptr metricRecorder); /// Hash functor to use identifier of @c ChannelVolumeInterface as the key in SpeakerSet. @@ -422,6 +430,21 @@ class SpeakerManager const avsCommon::sdkInterfaces::ChannelVolumeInterface::Type& type, const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& settings); + /** + * Persist channel configuration. + */ + void executePersistConfiguration(); + + /** + * Helper method to convert internally stored channel state into config format. + * + * @param type Channel type. + * @param storageState Config container. + */ + void convertSettingsToChannelState( + avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, + SpeakerManagerStorageState::ChannelState* storageState); + /** * Get the maximum volume limit. * @@ -441,6 +464,44 @@ class SpeakerManager template bool retryAndApplySettings(Task task, Args&&... args); + /** + * Configure channel volume and mute status defaults. + * + * @param type Channel type. + * @param state Channel state. + * @return A bool indicating success. + */ + void presetChannelDefaults( + avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, + const SpeakerManagerStorageState::ChannelState& state); + + /** + * Configures channels with default values. + */ + void loadConfiguration(); + + /** + * Updates volume and mute status on managed channels according to configured settings. + */ + void updateChannelSettings(); + + /** + * Updates managed channels according to configured settings. + * @param type Channel type. + */ + void updateChannelSettings(avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type); + + /** + * Helper function to adjust input volume into acceptable range. + * + * @param volume Input volume. + * @return Volume within correct limits. + */ + int8_t adjustVolumeRange(int64_t volume); + + /// Component's configuration access. + SpeakerManagerConfigHelper m_config; + /// The metric recorder. std::shared_ptr m_metricRecorder; @@ -451,7 +512,7 @@ class SpeakerManager std::shared_ptr m_messageSender; /// the @c volume to restore to when unmuting at 0 volume - const int m_minUnmuteVolume; + int m_minUnmuteVolume; /// An unordered_map contains ChannelVolumeInterfaces keyed by @c Type. Only internal function /// addChannelVolumeInterfaceIntoSpeakerMap can insert an element into this map to ensure that no invalid element @@ -480,6 +541,9 @@ class SpeakerManager /// maximumVolumeLimit The maximum volume level speakers in this system can reach. int8_t m_maximumVolumeLimit; + /// Restore mute state flag from configuration + bool m_restoreMuteState; + /// Mapping of each speaker type to its speaker settings. std::map< avsCommon::sdkInterfaces::ChannelVolumeInterface::Type, diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerComponent.h b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerComponent.h index 6f84025b1e..6321b07c58 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerComponent.h +++ b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerComponent.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,7 @@ namespace speakerManager { */ using SpeakerManagerComponent = acsdkManufactory::Component< std::shared_ptr, + acsdkManufactory::Import>, acsdkManufactory::Import>, acsdkManufactory::Import>, acsdkManufactory::Import>, diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConfigHelper.h b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConfigHelper.h new file mode 100644 index 0000000000..2561933635 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConfigHelper.h @@ -0,0 +1,107 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCONFIGHELPER_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCONFIGHELPER_H_ + +#include +#include + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace speakerManager { + +/** + * Helper class to manage configuration operations for SpeakerManager CA. + * + * This class implements all configuration operations and merges logic of accessing different configuration sources. + * SpeakerManager get configuration values from three sources: hardcoded values, platform configuration, and persistent + * storage. + */ +class SpeakerManagerConfigHelper { +public: + /** + * Creates object. + * @param[in] storage Storage interface. + */ + SpeakerManagerConfigHelper(const std::shared_ptr& storage); + + /** + * Load configuration. + * + * This method always succeeds (assuming @a state is not @a nullptr), as it first tries to load configuration + * from config storage, then from platform configuration files, and falls back to hardcoded values. + * + * @param[out] state Pointer to configuration container to fill with config values. + */ + void loadState(SpeakerManagerStorageState& state); + + /** + * Saves configuration to to config storage. + * + * @param[in] state Configuration data to persist. + * + * @return Boolean that indicates operation success. + */ + bool saveState(const SpeakerManagerStorageState& state); + + /** + * Loads minimum unmute volume level from platform configuration. The method tries to load the unmute value from + * platform configuration files, and if it fails, it returns a hardcoded value. + * + * @return Minimum volume level to unmute speakers. + */ + int getMinUnmuteVolume() const; + + /** + * Loads mute state handling from configuration. By default the speaker manager sets the mute status to the value + * prior to reboot, but this behaviour can be overridden by configuration. + * + * @return Returns configured value, where true indicates that mute status is configured according to the last + * saved state, and "false" indicates a default shall be kept. + */ + bool getRestoreMuteState() const; + +private: + /** + * Load channels settings from hardcoded defaults. + * + * @param[out] state Destination container for configuration data. + */ + void loadHardcodedState(SpeakerManagerStorageState& state); + + /** + * Load channels settings from platform configuration. + * + * @param[out] state Destination container for configuration data. + * + * @return A bool indicating success. + */ + bool loadStateFromConfig(SpeakerManagerStorageState& state); + + /** + * Default values that are used when no other configuration sources are available. + */ + static const SpeakerManagerStorageState c_defaults; + + /// Reference to configuration storage interface. + std::shared_ptr m_storage; +}; + +} // namespace speakerManager +} // namespace capabilityAgents +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCONFIGHELPER_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerMiscStorage.h b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerMiscStorage.h new file mode 100644 index 0000000000..33562cba1a --- /dev/null +++ b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerMiscStorage.h @@ -0,0 +1,97 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERMISCSTORAGE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERMISCSTORAGE_H_ + +#include +#include +#include + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace speakerManager { + +/** + * Configuration interface for SpeakerManager. + */ +class SpeakerManagerMiscStorage : public SpeakerManagerStorageInterface { +public: + /** + * Creates an instance of the @c SpeakerManagerMiscStorage. + * + * @param miscStorage The underlying miscellaneous storage to store @c SpeakerManager data. + * @return A unique pointer to the instance of the newly created @c SpeakerManagerMiscStorage. + */ + static std::shared_ptr create( + const std::shared_ptr& miscStorage); + + /// @name SpeakerManagerStorageInterface Functions + /// @{ + bool loadState(SpeakerManagerStorageState& state) override; + bool saveState(const SpeakerManagerStorageState& state) override; + /// @} + +private: + /// Helper to convert structure to JSON string. + static std::string convertToStateString(const SpeakerManagerStorageState& state); + static std::string convertToStateString(const SpeakerManagerStorageState::ChannelState& state); + + /** + * Constructor. + * + * @param miscStorage The underlying miscellaneous storage used to store component data. + */ + SpeakerManagerMiscStorage( + const std::shared_ptr& miscStorage); + + /** + * Method to initialize the object. + * + * This method connects to underlying storage and performs necessary actions. + * + * @return Boolean status, indicating operation success. + */ + bool init(); + + /** + * Helper to convert JSON string to structure. + * + * @param stateString JSON string describing @a SpeakerManagerStorageState data. + * @param state Pointer to storage for parsed values. + * + * @return Boolean status, indicating operation success. + */ + bool convertFromStateString(const std::string& stateString, SpeakerManagerStorageState& state); + + /** + * Helper to convert JSON string to structure. + * + * @param stateString JSON string describing @a SpeakerManagerStorageState::ChannelState data. + * @param state Pointer to storage for parsed values. + * + * @return Boolean status, indicating operation success. + */ + bool convertFromStateString(const std::string& stateString, SpeakerManagerStorageState::ChannelState& state); + + /// The Misc storage. + std::shared_ptr m_miscStorage; +}; + +} // namespace speakerManager +} // namespace capabilityAgents +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERMISCSTORAGE_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageInterface.h b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageInterface.h new file mode 100644 index 0000000000..cd100bb911 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageInterface.h @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGEINTERFACE_H_ + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace speakerManager { + +struct SpeakerManagerStorageState; + +/** + * Storage interface for SpeakerManager. + */ +struct SpeakerManagerStorageInterface { + /** + * Virtual destructor to assure proper cleanup of derived types. + */ + virtual ~SpeakerManagerStorageInterface() = default; + + /** + * Loads state from underlying storage. + * @param[out] state Pointer to state structure for loaded values. + * @return Boolean flag if the operation is successful. + */ + virtual bool loadState(SpeakerManagerStorageState& state) = 0; + + /** + * Stores state to underlying storage. + * @param[in] state Reference of state structure for values to store. + * @return Boolean flag if the operation is successful. + */ + virtual bool saveState(const SpeakerManagerStorageState& state) = 0; +}; + +} // namespace speakerManager +} // namespace capabilityAgents +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGEINTERFACE_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageState.h b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageState.h new file mode 100644 index 0000000000..0037b1cbeb --- /dev/null +++ b/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageState.h @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGESTATE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGESTATE_H_ + +#include + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace speakerManager { + +/** + * Storage state for SpeakerManager. SpeakerManager configuration includes configuration for two channel types: speaker + * and alerts. There can be any number of channels for each of types, but all of them share the same configuraiton. + */ +struct SpeakerManagerStorageState { + /** + * SpeakerManager channel type configuration state. + */ + struct ChannelState { + /// Channel volume. + uint8_t channelVolume; + /// Channel mute status. + bool channelMuteStatus; + }; + /// Configuration for speaker channels. + ChannelState speakerChannelState; + /// Configuration for alerts channels. + ChannelState alertsChannelState; +}; + +} // namespace speakerManager +} // namespace capabilityAgents +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGESTATE_H_ diff --git a/CapabilityAgents/SpeakerManager/src/CMakeLists.txt b/CapabilityAgents/SpeakerManager/src/CMakeLists.txt index 3d298b4f69..565e2341b0 100644 --- a/CapabilityAgents/SpeakerManager/src/CMakeLists.txt +++ b/CapabilityAgents/SpeakerManager/src/CMakeLists.txt @@ -1,10 +1,11 @@ add_definitions("-DACSDK_LOG_MODULE=speakerManager") -add_library(SpeakerManager SHARED SpeakerManager.cpp +add_library(SpeakerManager SpeakerManager.cpp ChannelVolumeManager.cpp DefaultChannelVolumeFactory.cpp SpeakerManagerComponent.cpp - ) + SpeakerManagerMiscStorage.cpp + SpeakerManagerConfigHelper.cpp) target_include_directories(SpeakerManager PUBLIC "${ContextManager_INCLUDE_DIRS}" diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp b/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp index c9e53f96cd..77cd9a8a75 100644 --- a/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp +++ b/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include "SpeakerManager/SpeakerManagerConstants.h" #include "SpeakerManager/SpeakerManager.h" +#include "SpeakerManager/SpeakerManagerMiscStorage.h" namespace alexaClientSDK { namespace capabilityAgents { @@ -42,6 +44,7 @@ using namespace acsdkShutdownManagerInterfaces; using namespace avsCommon::avs; using namespace avsCommon::avs::speakerConstants; using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::storage; using namespace avsCommon::utils::json; using namespace avsCommon::utils::configuration; using namespace avsCommon::utils::metrics; @@ -63,11 +66,6 @@ static const std::vector DEFAULT_RETRY_TABLE = {std::chrono::milliseconds(1 /// String to identify log entries originating from this file. static const std::string TAG{"SpeakerManager"}; -/// The key in our config file to find the root of speaker manager configuration. -static const std::string SPEAKERMANAGER_CONFIGURATION_ROOT_KEY = "speakerManagerCapabilityAgent"; -/// The key in our config file to find the minUnmuteVolume value. -static const std::string SPEAKERMANAGER_MIN_UNMUTE_VOLUME_KEY = "minUnmuteVolume"; - /// prefix for metrics emitted from the SpeakerManager CA static const std::string SPEAKER_MANAGER_METRIC_PREFIX = "SPEAKER_MANAGER-"; @@ -139,6 +137,7 @@ static void submitMetric( static std::shared_ptr getSpeakerCapabilityConfiguration(); std::shared_ptr SpeakerManager::createSpeakerManagerCapabilityAgent( + const std::shared_ptr& storage, const std::shared_ptr& contextManager, const std::shared_ptr& messageSender, const std::shared_ptr& exceptionEncounteredSender, @@ -154,9 +153,10 @@ std::shared_ptr SpeakerManager::createSpeakerManagerCap return nullptr; } + auto speakerStorage = SpeakerManagerMiscStorage::create(storage); std::vector> speakers; - auto speakerManager = - SpeakerManager::create(speakers, contextManager, messageSender, exceptionEncounteredSender, metricRecorder); + auto speakerManager = SpeakerManager::create( + speakerStorage, speakers, contextManager, messageSender, exceptionEncounteredSender, metricRecorder); if (!speakerManager) { ACSDK_ERROR(LX("createSpeakerManagerCapabilityAgentFailed")); @@ -170,6 +170,7 @@ std::shared_ptr SpeakerManager::createSpeakerManagerCap } std::shared_ptr SpeakerManager::create( + const std::shared_ptr& storage, const std::vector>& groupVolumeInterfaces, std::shared_ptr contextManager, std::shared_ptr messageSender, @@ -184,44 +185,42 @@ std::shared_ptr SpeakerManager::create( } else if (!exceptionEncounteredSender) { ACSDK_ERROR(LX("createFailed").d("reason", "nullExceptionEncounteredSender")); return nullptr; + } else if (!storage) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullStorage")); + return nullptr; } - int minUnmuteVolume = MIN_UNMUTE_VOLUME; - - auto configurationRoot = ConfigurationNode::getRoot()[SPEAKERMANAGER_CONFIGURATION_ROOT_KEY]; - // If key is present, then read and initilize the value from config or set to default. - configurationRoot.getInt(SPEAKERMANAGER_MIN_UNMUTE_VOLUME_KEY, &minUnmuteVolume, MIN_UNMUTE_VOLUME); - auto speakerManager = std::shared_ptr(new SpeakerManager( - groupVolumeInterfaces, - contextManager, - messageSender, - exceptionEncounteredSender, - minUnmuteVolume, - metricRecorder)); + storage, groupVolumeInterfaces, contextManager, messageSender, exceptionEncounteredSender, metricRecorder)); return speakerManager; } SpeakerManager::SpeakerManager( + const std::shared_ptr& speakerManagerStorage, const std::vector>& groupVolumeInterfaces, std::shared_ptr contextManager, std::shared_ptr messageSender, std::shared_ptr exceptionEncounteredSender, - const int minUnmuteVolume, std::shared_ptr metricRecorder) : CapabilityAgent{NAMESPACE, exceptionEncounteredSender}, RequiresShutdown{"SpeakerManager"}, + m_config{speakerManagerStorage}, m_metricRecorder{metricRecorder}, m_contextManager{contextManager}, m_messageSender{messageSender}, - m_minUnmuteVolume{minUnmuteVolume}, + m_minUnmuteVolume{MIN_UNMUTE_VOLUME}, m_retryTimer{DEFAULT_RETRY_TABLE}, m_maxRetries{DEFAULT_RETRY_TABLE.size()}, - m_maximumVolumeLimit{AVS_SET_VOLUME_MAX} { + m_maximumVolumeLimit{AVS_SET_VOLUME_MAX}, + m_restoreMuteState{true} { for (auto& groupVolume : groupVolumeInterfaces) { addChannelVolumeInterfaceIntoSpeakerMap(groupVolume); } + + loadConfiguration(); // Load configuration (either from previous run, or from configuration). + updateChannelSettings(); // Apply loaded configuration + m_capabilityConfigurations.insert(getSpeakerCapabilityConfiguration()); } @@ -255,6 +254,7 @@ void SpeakerManager::addChannelVolumeInterfaceIntoSpeakerMap( it->second.insert(channelVolumeInterface); } } + ACSDK_DEBUG(LX(__func__).d("type", type).d("sizeOfSpeakerSet", m_speakerMap[type].size())); } @@ -658,6 +658,10 @@ bool SpeakerManager::executeSetVolume( ACSDK_DEBUG(LX("executeSetVolumeSuccess").d("newVolume", static_cast(settings.volume))); + if (previousVolume != settings.volume) { + executePersistConfiguration(); + } + updateContextManager(type, settings); if (properties.notifyObservers) { @@ -672,6 +676,26 @@ bool SpeakerManager::executeSetVolume( return true; } +void SpeakerManager::convertSettingsToChannelState( + avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, + SpeakerManagerStorageState::ChannelState* storageState) { + const auto& settings = m_speakerSettings[type]; + storageState->channelVolume = settings.volume; + storageState->channelMuteStatus = settings.mute; +} + +void SpeakerManager::executePersistConfiguration() { + SpeakerManagerStorageState state; + convertSettingsToChannelState(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, &state.speakerChannelState); + convertSettingsToChannelState(ChannelVolumeInterface::Type::AVS_ALERTS_VOLUME, &state.alertsChannelState); + + if (!m_config.saveState(state)) { + ACSDK_ERROR(LX("executePersistConfigurationFailed")); + } else { + ACSDK_DEBUG(LX("executePersistConfigurationSuccess")); + } +} + bool SpeakerManager::executeRestoreVolume( ChannelVolumeInterface::Type type, SpeakerManagerObserverInterface::Source source) { @@ -802,6 +826,10 @@ bool SpeakerManager::executeAdjustVolume( ACSDK_DEBUG(LX("executeAdjustVolumeSuccess").d("newVolume", static_cast(settings.volume))); + if (previousVolume != settings.volume) { + executePersistConfiguration(); + } + updateContextManager(type, settings); if (properties.notifyObservers) { @@ -897,6 +925,8 @@ bool SpeakerManager::executeSetMute( ACSDK_DEBUG(LX("executeSetMuteSuccess").d("mute", mute)); + executePersistConfiguration(); + updateContextManager(type, settings); if (properties.notifyObservers) { @@ -1039,8 +1069,7 @@ bool SpeakerManager::executeSetSpeakerSettings( return true; } -void SpeakerManager::addChannelVolumeInterface( - std::shared_ptr channelVolumeInterface) { +void SpeakerManager::addChannelVolumeInterface(std::shared_ptr channelVolumeInterface) { addChannelVolumeInterfaceIntoSpeakerMap(channelVolumeInterface); } @@ -1073,6 +1102,84 @@ bool SpeakerManager::retryAndApplySettings(Task task, Args&&... args) { return attempt < m_maxRetries; } +int8_t SpeakerManager::adjustVolumeRange(int64_t volume) { + auto adjustedVolume = std::min( + static_cast(AVS_ADJUST_VOLUME_MAX), std::max(static_cast(AVS_ADJUST_VOLUME_MIN), volume)); + return static_cast(adjustedVolume); +} + +void SpeakerManager::presetChannelDefaults( + ChannelVolumeInterface::Type type, + const SpeakerManagerStorageState::ChannelState& state) { + auto adjustedVolume = adjustVolumeRange(state.channelVolume); + + if (adjustedVolume != state.channelVolume) { + ACSDK_DEBUG9(LX(__func__) + .m("adjusted configured value") + .d("type", type) + .d("configured volume", state.channelVolume) + .d("adjusted volume", adjustedVolume)); + } + + m_speakerSettings[type].volume = adjustedVolume; + if (m_restoreMuteState) { + m_speakerSettings[type].mute = state.channelMuteStatus; + } +} + +void SpeakerManager::loadConfiguration() { + ACSDK_DEBUG5(LX("configureDefaults").m("Loading configuration")); + + m_minUnmuteVolume = m_config.getMinUnmuteVolume(); + m_restoreMuteState = m_config.getRestoreMuteState(); + + SpeakerManagerStorageState state; + m_config.loadState(state); + + presetChannelDefaults(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, state.speakerChannelState); + presetChannelDefaults(ChannelVolumeInterface::Type::AVS_ALERTS_VOLUME, state.alertsChannelState); +} + +void SpeakerManager::updateChannelSettings() { + updateChannelSettings(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME); + updateChannelSettings(ChannelVolumeInterface::Type::AVS_ALERTS_VOLUME); +} + +void SpeakerManager::updateChannelSettings(ChannelVolumeInterface::Type type) { + auto it = m_speakerMap.find(type); + if (it != m_speakerMap.end()) { + SpeakerInterface::SpeakerSettings& settings = m_speakerSettings[type]; + + auto begin = it->second.begin(); + auto end = it->second.end(); + retryAndApplySettings([this, &settings, &begin, end]() -> bool { + // Go through list of Speakers with ChannelVolumeInterface::Type equal + // to type, and call setVolume. + while (begin != end) { + ACSDK_DEBUG9(LX(__func__) + .d("speaker id", (*begin)->getId()) + .d("speaker type", (*begin)->getSpeakerType()) + .d("default volume set to ", settings.volume)); + if (!(*begin)->setUnduckedVolume(settings.volume)) { + submitMetric(m_metricRecorder, "setVolumeFailed", 1); + return false; + } + if (!(*begin)->setMute(settings.mute)) { + submitMetric(m_metricRecorder, "setMuteFailed", 1); + return false; + } + begin++; + } + + submitMetric(m_metricRecorder, "setVolumeFailed", 0); + submitMetric(m_metricRecorder, "setMuteFailed", 0); + return true; + }); + + executeInitializeSpeakerSettings(type); + } +} + } // namespace speakerManager } // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManagerComponent.cpp b/CapabilityAgents/SpeakerManager/src/SpeakerManagerComponent.cpp index 83c0e278af..12b851c9f8 100644 --- a/CapabilityAgents/SpeakerManager/src/SpeakerManagerComponent.cpp +++ b/CapabilityAgents/SpeakerManager/src/SpeakerManagerComponent.cpp @@ -14,7 +14,6 @@ */ #include -#include #include "SpeakerManager/SpeakerManager.h" #include "SpeakerManager/SpeakerManagerComponent.h" diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManagerConfigHelper.cpp b/CapabilityAgents/SpeakerManager/src/SpeakerManagerConfigHelper.cpp new file mode 100644 index 0000000000..56e400e48a --- /dev/null +++ b/CapabilityAgents/SpeakerManager/src/SpeakerManagerConfigHelper.cpp @@ -0,0 +1,104 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +using namespace alexaClientSDK::avsCommon::sdkInterfaces; +using namespace alexaClientSDK::avsCommon::sdkInterfaces::storage; +using namespace alexaClientSDK::avsCommon::avs::speakerConstants; +using namespace alexaClientSDK::capabilityAgents::speakerManager; +using namespace alexaClientSDK::avsCommon::utils::configuration; + +/// String to identify log entries originating from this file. +static const std::string TAG{"SpeakerManagerConfigHelper"}; + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param TAG Component tag. + * @param evemt The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// The key in our config file to find the root of speaker manager configuration. +static const std::string SPEAKERMANAGER_CONFIGURATION_ROOT_KEY = "speakerManagerCapabilityAgent"; +/// The key in our config file to find the minUnmuteVolume value. +static const std::string SPEAKERMANAGER_MIN_UNMUTE_VOLUME_KEY = "minUnmuteVolume"; +/// The key in our config file to find the defaultSpeakerVolume value. +static const std::string SPEAKERMANAGER_DEFAULT_SPEAKER_VOLUME_KEY = "defaultSpeakerVolume"; +/// The key in our config file to find the defaultAlertsVolume value. +static const std::string SPEAKERMANAGER_DEFAULT_ALERTS_VOLUME_KEY = "defaultAlertsVolume"; +/// The key in our config file to find mute status keep flag +static const std::string SPEAKERMANAGER_RESTORE_MUTE_STATE_KEY = "restoreMuteState"; + +const SpeakerManagerStorageState SpeakerManagerConfigHelper::c_defaults = {{DEFAULT_SPEAKER_VOLUME, false}, + {DEFAULT_ALERTS_VOLUME, false}}; + +SpeakerManagerConfigHelper::SpeakerManagerConfigHelper(const std::shared_ptr& storage) : + m_storage(storage) { +} + +int SpeakerManagerConfigHelper::getMinUnmuteVolume() const { + int minUnmuteVolume = MIN_UNMUTE_VOLUME; + + auto node = ConfigurationNode::getRoot()[SPEAKERMANAGER_CONFIGURATION_ROOT_KEY]; + // If key is present, then read and initialize the value from config or set to default. + node.getInt(SPEAKERMANAGER_MIN_UNMUTE_VOLUME_KEY, &minUnmuteVolume, MIN_UNMUTE_VOLUME); + + return minUnmuteVolume; +} + +void SpeakerManagerConfigHelper::loadState(SpeakerManagerStorageState& state) { + if (!m_storage->loadState(state) && !loadStateFromConfig(state)) { + loadHardcodedState(state); + } +} + +bool SpeakerManagerConfigHelper::saveState(const SpeakerManagerStorageState& state) { + return m_storage->saveState(state); +} + +bool SpeakerManagerConfigHelper::loadStateFromConfig(SpeakerManagerStorageState& state) { + int speakerVolume = 0; + int alertsVolume = 0; + + auto node = ConfigurationNode::getRoot()[SPEAKERMANAGER_CONFIGURATION_ROOT_KEY]; + + if (node.getInt(SPEAKERMANAGER_DEFAULT_SPEAKER_VOLUME_KEY, &speakerVolume) && + node.getInt(SPEAKERMANAGER_DEFAULT_ALERTS_VOLUME_KEY, &alertsVolume)) { + state.speakerChannelState.channelMuteStatus = false; + state.speakerChannelState.channelVolume = speakerVolume; + state.alertsChannelState.channelMuteStatus = false; + state.alertsChannelState.channelVolume = alertsVolume; + + return true; + } + return false; +} + +void SpeakerManagerConfigHelper::loadHardcodedState(SpeakerManagerStorageState& state) { + state = c_defaults; +} + +bool SpeakerManagerConfigHelper::getRestoreMuteState() const { + auto node = ConfigurationNode::getRoot()[SPEAKERMANAGER_CONFIGURATION_ROOT_KEY]; + bool result = false; + if (node.getBool(SPEAKERMANAGER_RESTORE_MUTE_STATE_KEY, &result)) { + return result; + } else { + return true; + } +} diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManagerMiscStorage.cpp b/CapabilityAgents/SpeakerManager/src/SpeakerManagerMiscStorage.cpp new file mode 100644 index 0000000000..1209c2d99c --- /dev/null +++ b/CapabilityAgents/SpeakerManager/src/SpeakerManagerMiscStorage.cpp @@ -0,0 +1,184 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "SpeakerManager/SpeakerManager.h" +#include "SpeakerManager/SpeakerManagerMiscStorage.h" +#include "SpeakerManager/SpeakerManagerStorageState.h" + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace speakerManager { + +using namespace avsCommon::sdkInterfaces::storage; +using namespace avsCommon::utils::json; + +/// String to identify log entries originating from this file. +static const std::string TAG("SpeakerManagerMiscStorage"); + +/** + * Create a LogEntry using the 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) + +/// Component name for Misc DB. +static const std::string COMPONENT_NAME = "SpeakerManager"; +/// Misc DB table for component state. +static const std::string COMPONENT_STATE_TABLE = "SpeakerManagerConfig"; +/// Misc DB table entry for component state. +static const std::string COMPONENT_STATE_KEY = "SpeakerManagerConfig"; + +/// The key in our config for speaker volume. +static const std::string SPEAKER_CHANNEL_STATE = "speakerChannelState"; +/// The key in our config for speaker volume. +static const std::string ALERTS_CHANNEL_STATE = "alertsChannelState"; +/// The key in our config for alerts volume. +static const std::string ALERTS_VOLUME_KEY = "alertsVolume"; +/// The key in our config for speaker volume. +static const std::string CHANNEL_VOLUME_KEY = "channelVolume"; +/// The key in our config for speaker volume. +static const std::string CHANNEL_MUTE_STATUS_KEY = "channelMuteStatus"; + +std::shared_ptr SpeakerManagerMiscStorage::create( + const std::shared_ptr& miscStorage) { + if (miscStorage) { + auto res = std::shared_ptr(new SpeakerManagerMiscStorage(miscStorage)); + if (res->init()) { + return res; + } else { + ACSDK_ERROR(LX("createFailed").d("reason", "failedToInitialize")); + } + } else { + ACSDK_ERROR(LX("createFailed").d("reason", "nullMiscStorage")); + } + return nullptr; +} + +SpeakerManagerMiscStorage::SpeakerManagerMiscStorage( + const std::shared_ptr& miscStorage) : + m_miscStorage{miscStorage} { +} + +bool SpeakerManagerMiscStorage::init() { + if (!m_miscStorage->isOpened() && !m_miscStorage->open()) { + ACSDK_DEBUG3(LX(__func__).m("Couldn't open misc database. Creating.")); + if (!m_miscStorage->createDatabase()) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "Could not create misc database.")); + return false; + } + } + + bool tableExists = false; + if (!m_miscStorage->tableExists(COMPONENT_NAME, COMPONENT_STATE_TABLE, &tableExists)) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "Could not check state table information in misc database.")); + return false; + } + + if (!tableExists) { + ACSDK_DEBUG3(LX(__func__).m("Table doesn't exist in misc database. Creating new.")); + if (!m_miscStorage->createTable( + COMPONENT_NAME, + COMPONENT_STATE_TABLE, + MiscStorageInterface::KeyType::STRING_KEY, + MiscStorageInterface::ValueType::STRING_VALUE)) { + ACSDK_ERROR(LX("initializeFailed") + .d("reason", "Cannot create table") + .d("table", COMPONENT_STATE_TABLE) + .d("key", COMPONENT_STATE_KEY) + .d("component", COMPONENT_NAME)); + return false; + } + } + return true; +} + +bool SpeakerManagerMiscStorage::convertFromStateString( + const std::string& stateString, + SpeakerManagerStorageState::ChannelState& state) { + rapidjson::Document document; + if (!jsonUtils::parseJSON(stateString, &document)) { + ACSDK_ERROR(LX("convertFromStateString").d("reason", "parsingError")); + return false; + } + + int64_t tmpVolume; + + if (jsonUtils::retrieveValue(document, CHANNEL_VOLUME_KEY, &tmpVolume)) { + state.channelVolume = tmpVolume; + } else { + return false; + } + if (jsonUtils::retrieveValue(document, CHANNEL_MUTE_STATUS_KEY, &state.channelMuteStatus)) { + return true; + } + + return false; +} + +bool SpeakerManagerMiscStorage::convertFromStateString( + const std::string& stateString, + SpeakerManagerStorageState& state) { + std::string tmp; + + return jsonUtils::retrieveValue(stateString, SPEAKER_CHANNEL_STATE, &tmp) && + convertFromStateString(tmp, state.speakerChannelState) && + jsonUtils::retrieveValue(stateString, ALERTS_CHANNEL_STATE, &tmp) && + convertFromStateString(tmp, state.alertsChannelState); +} + +bool SpeakerManagerMiscStorage::loadState(SpeakerManagerStorageState& state) { + std::string stateString; + + return m_miscStorage->get(COMPONENT_NAME, COMPONENT_STATE_TABLE, COMPONENT_STATE_KEY, &stateString) && + !stateString.empty() && convertFromStateString(stateString, state); +} + +std::string SpeakerManagerMiscStorage::convertToStateString(const SpeakerManagerStorageState::ChannelState& state) { + JsonGenerator generator; + generator.addMember(CHANNEL_VOLUME_KEY, state.channelVolume); + generator.addMember(CHANNEL_MUTE_STATUS_KEY, state.channelMuteStatus); + return generator.toString(); +} + +std::string SpeakerManagerMiscStorage::convertToStateString(const SpeakerManagerStorageState& state) { + ACSDK_DEBUG5(LX(__func__)); + JsonGenerator generator; + generator.addRawJsonMember(SPEAKER_CHANNEL_STATE, convertToStateString(state.speakerChannelState)); + generator.addRawJsonMember(ALERTS_CHANNEL_STATE, convertToStateString(state.alertsChannelState)); + return generator.toString(); +} + +bool SpeakerManagerMiscStorage::saveState(const SpeakerManagerStorageState& state) { + std::string stateString = convertToStateString(state); + if (!m_miscStorage->put(COMPONENT_NAME, COMPONENT_STATE_TABLE, COMPONENT_STATE_KEY, stateString)) { + ACSDK_ERROR(LX("saveStateFailed") + .d("reason", "Unable to update the table") + .d("table", COMPONENT_STATE_TABLE) + .d("key", COMPONENT_STATE_KEY) + .d("component", COMPONENT_NAME)); + return false; + } + return true; +} + +} // namespace speakerManager +} // namespace capabilityAgents +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp b/CapabilityAgents/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp new file mode 100644 index 0000000000..b35b9b7f28 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp @@ -0,0 +1,236 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "SpeakerManager/SpeakerManagerConfigHelper.h" +#include "SpeakerManager/SpeakerManagerStorageState.h" + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace speakerManager { +namespace test { + +using namespace avsCommon::avs; +using namespace avsCommon::avs::speakerConstants; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils::configuration; +using namespace avsCommon::utils::memory; +using namespace rapidjson; +using namespace ::testing; +using namespace alexaClientSDK::capabilityAgents::speakerManager; + +static const std::string JSON_TEST_CONFIG = + "{\"speakerManagerCapabilityAgent\":{\"minUnmuteVolume\":3,\"defaultSpeakerVolume\":5,\"defaultAlertsVolume\":6," + "\"restoreMuteState\":true}}"; +static const std::string JSON_TEST_CONFIG_NO_MUTE = + "{\"speakerManagerCapabilityAgent\":{\"minUnmuteVolume\":3,\"defaultSpeakerVolume\":5,\"defaultAlertsVolume\":6," + "\"restoreMuteState\":false}}"; + +class MockSpeakerManagerStorageInterface : public SpeakerManagerStorageInterface { +public: + MOCK_METHOD1(loadState, bool(SpeakerManagerStorageState&)); + MOCK_METHOD1(saveState, bool(const SpeakerManagerStorageState&)); +}; + +class SpeakerManagerConfigHelperTest : public Test { +public: + SpeakerManagerConfigHelperTest(); + +protected: + /// SetUp before each test. + void SetUp() override; + + /// TearDown after each test. + void TearDown() override; + + // Upstream interface mock + std::shared_ptr> m_stubStorage; +}; + +SpeakerManagerConfigHelperTest::SpeakerManagerConfigHelperTest() : m_stubStorage() { +} + +void SpeakerManagerConfigHelperTest::SetUp() { + m_stubStorage = std::make_shared>(); +} + +void SpeakerManagerConfigHelperTest::TearDown() { + m_stubStorage.reset(); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_initDoesntCallLoadSave) { + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); + EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); + + SpeakerManagerConfigHelper helper(m_stubStorage); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_getMinUnmuteVolumeFromConfiguration) { + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); + EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); + + // Provide a valid configuration. + std::shared_ptr istr(new std::istringstream(JSON_TEST_CONFIG)); + ConfigurationNode::uninitialize(); + ASSERT_TRUE(ConfigurationNode::initialize({istr})); + + SpeakerManagerConfigHelper helper(m_stubStorage); + ASSERT_EQ(3, helper.getMinUnmuteVolume()); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_getMinUnmuteVolumeReturnsDefaults) { + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); + EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); + + // Provide an empty configuration. + ConfigurationNode::uninitialize(); + ASSERT_TRUE(ConfigurationNode::initialize({})); + + SpeakerManagerConfigHelper helper(m_stubStorage); + ASSERT_EQ(MIN_UNMUTE_VOLUME, helper.getMinUnmuteVolume()); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_getRestoreMuteStateReturnsDefaults) { + // Provide an empty configuration. + ConfigurationNode::uninitialize(); + ASSERT_TRUE(ConfigurationNode::initialize({})); + + SpeakerManagerConfigHelper helper(m_stubStorage); + ASSERT_TRUE(helper.getRestoreMuteState()); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_getRestoreMuteStateReturnsTrue) { + ConfigurationNode::uninitialize(); + std::shared_ptr istr(new std::istringstream(JSON_TEST_CONFIG)); + ASSERT_TRUE(ConfigurationNode::initialize({istr})); + + SpeakerManagerConfigHelper helper(m_stubStorage); + ASSERT_TRUE(helper.getRestoreMuteState()); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_getRestoreMuteStateReturnsFalse) { + ConfigurationNode::uninitialize(); + std::shared_ptr istr(new std::istringstream(JSON_TEST_CONFIG_NO_MUTE)); + ASSERT_TRUE(ConfigurationNode::initialize({istr})); + + SpeakerManagerConfigHelper helper(m_stubStorage); + ASSERT_FALSE(helper.getRestoreMuteState()); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_loadStateDelegate) { + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(1); + EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); + + SpeakerManagerConfigHelper helper(m_stubStorage); + + ON_CALL(*m_stubStorage, loadState(_)).WillByDefault(Invoke([](SpeakerManagerStorageState& state) { + state.speakerChannelState.channelVolume = 10; + state.speakerChannelState.channelMuteStatus = true; + state.alertsChannelState.channelMuteStatus = false; + state.alertsChannelState.channelVolume = 20; + return true; + })); + + SpeakerManagerStorageState state = {{255, false}, {255, false}}; + helper.loadState(state); + + EXPECT_EQ(10, state.speakerChannelState.channelVolume); + EXPECT_TRUE(state.speakerChannelState.channelMuteStatus); + EXPECT_EQ(20, state.alertsChannelState.channelVolume); + EXPECT_FALSE(state.alertsChannelState.channelMuteStatus); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_loadStateFromConfig) { + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(1); + EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); + + std::shared_ptr istr(new std::istringstream(JSON_TEST_CONFIG)); + ConfigurationNode::uninitialize(); + ASSERT_TRUE(ConfigurationNode::initialize({istr})); + + SpeakerManagerConfigHelper helper(m_stubStorage); + + ON_CALL(*m_stubStorage, loadState(_)).WillByDefault(Return(false)); + + SpeakerManagerStorageState state = {{255, true}, {255, true}}; + helper.loadState(state); + + EXPECT_EQ(5, state.speakerChannelState.channelVolume); + EXPECT_FALSE(state.speakerChannelState.channelMuteStatus); + EXPECT_EQ(6, state.alertsChannelState.channelVolume); + EXPECT_FALSE(state.alertsChannelState.channelMuteStatus); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_loadStateDefaults) { + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(1); + EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); + + ConfigurationNode::uninitialize(); + ASSERT_TRUE(ConfigurationNode::initialize({})); + + SpeakerManagerConfigHelper helper(m_stubStorage); + + ON_CALL(*m_stubStorage, loadState(_)).WillByDefault(Return(false)); + + SpeakerManagerStorageState state = {{255, false}, {255, false}}; + helper.loadState(state); + + EXPECT_EQ(DEFAULT_SPEAKER_VOLUME, state.speakerChannelState.channelVolume); + EXPECT_FALSE(state.speakerChannelState.channelMuteStatus); + EXPECT_EQ(DEFAULT_ALERTS_VOLUME, state.alertsChannelState.channelVolume); + EXPECT_FALSE(state.alertsChannelState.channelMuteStatus); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_saveState) { + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); + EXPECT_CALL(*m_stubStorage, saveState(_)).Times(1); + + SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerStorageState saved = {{0, true}, {0, true}}; + + ON_CALL(*m_stubStorage, saveState(_)).WillByDefault(Invoke([&saved](const SpeakerManagerStorageState& state) { + saved = state; + return true; + })); + + SpeakerManagerStorageState state = {{255, false}, {255, false}}; + ASSERT_TRUE(helper.saveState(state)); + + ASSERT_EQ(255, saved.speakerChannelState.channelVolume); + ASSERT_FALSE(saved.speakerChannelState.channelMuteStatus); + ASSERT_EQ(255, saved.alertsChannelState.channelVolume); + ASSERT_FALSE(saved.alertsChannelState.channelMuteStatus); +} + +} // namespace test +} // namespace speakerManager +} // namespace capabilityAgents +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp b/CapabilityAgents/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp new file mode 100644 index 0000000000..b672df5a81 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp @@ -0,0 +1,291 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "SpeakerManager/SpeakerManagerMiscStorage.h" + +namespace alexaClientSDK { +namespace capabilityAgents { +namespace speakerManager { +namespace test { + +using namespace avsCommon::avs; +using namespace avsCommon::avs::speakerConstants; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::storage; +using namespace rapidjson; +using namespace ::testing; +using namespace alexaClientSDK::capabilityAgents::speakerManager; + +class MockMiscStorageInterface : public MiscStorageInterface { +public: + MOCK_METHOD0(createDatabase, bool()); + MOCK_METHOD0(open, bool()); + MOCK_METHOD0(isOpened, bool()); + MOCK_METHOD0(close, void()); + MOCK_METHOD4(createTable, bool(const std::string&, const std::string&, KeyType, ValueType)); + MOCK_METHOD2(deleteTable, bool(const std::string&, const std::string&)); + MOCK_METHOD2(clearTable, bool(const std::string&, const std::string&)); + MOCK_METHOD4(get, bool(const std::string&, const std::string&, const std::string&, std::string*)); + MOCK_METHOD4(add, bool(const std::string&, const std::string&, const std::string&, const std::string&)); + MOCK_METHOD4(update, bool(const std::string&, const std::string&, const std::string&, const std::string&)); + MOCK_METHOD4(put, bool(const std::string&, const std::string&, const std::string&, const std::string&)); + MOCK_METHOD3(remove, bool(const std::string&, const std::string&, const std::string&)); + MOCK_METHOD4(tableEntryExists, bool(const std::string&, const std::string&, const std::string&, bool*)); + MOCK_METHOD3(tableExists, bool(const std::string&, const std::string&, bool*)); + MOCK_METHOD3( + load, + bool(const std::string&, const std::string&, std::unordered_map* valueContainer)); +}; + +/// The @c MessageId identifer. +static const std::string JSON_PAYLOAD = + "" + "{" + " \"speakerChannelState\": {" + " \"channelVolume\": 10," + " \"channelMuteStatus\": false" + " }," + " \"alertsChannelState\": {" + " \"channelVolume\": 15," + " \"channelMuteStatus\": true" + " }" + "}"; + +class SpeakerManagerMiscStorageTest : public ::testing::TestWithParam> { +public: + /// SetUp before each test. + void SetUp() override; + + /// TearDown after each test. + void TearDown() override; + +protected: + // Upstream interface mock + std::shared_ptr> m_stubMiscStorage; +}; + +void SpeakerManagerMiscStorageTest::SetUp() { + m_stubMiscStorage = std::make_shared>(); +} + +void SpeakerManagerMiscStorageTest::TearDown() { + m_stubMiscStorage.reset(); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_nullMiscStorage) { + ASSERT_EQ(nullptr, SpeakerManagerMiscStorage::create(nullptr)); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_FailedOpen) { + ON_CALL(*m_stubMiscStorage, isOpened()).WillByDefault(Return(false)); + ON_CALL(*m_stubMiscStorage, open()).WillByDefault(Return(false)); + + EXPECT_CALL(*m_stubMiscStorage, isOpened()).Times(1); + EXPECT_CALL(*m_stubMiscStorage, open()).Times(1); + + ASSERT_EQ(nullptr, SpeakerManagerMiscStorage::create(m_stubMiscStorage)); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_OpenAndFailedCheckTableStatus) { + ON_CALL(*m_stubMiscStorage, isOpened()).WillByDefault(Return(true)); + ON_CALL(*m_stubMiscStorage, tableExists(_, _, _)).WillByDefault(Return(false)); + + EXPECT_CALL(*m_stubMiscStorage, isOpened()).Times(1); + EXPECT_CALL(*m_stubMiscStorage, open()).Times(0); + EXPECT_CALL(*m_stubMiscStorage, tableExists(_, _, _)).Times(1); + EXPECT_CALL(*m_stubMiscStorage, createTable(_, _, _, _)).Times(0); + + ASSERT_EQ(nullptr, SpeakerManagerMiscStorage::create(m_stubMiscStorage)); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_OpenAndFailedCreateTable) { + ON_CALL(*m_stubMiscStorage, isOpened()).WillByDefault(Return(true)); + ON_CALL(*m_stubMiscStorage, tableExists(_, _, _)) + .WillByDefault(Invoke([](const std::string&, const std::string&, bool* res) { + *res = false; + return true; + })); + ON_CALL(*m_stubMiscStorage, createTable(_, _, _, _)).WillByDefault(Return(false)); + + EXPECT_CALL(*m_stubMiscStorage, isOpened()).Times(1); + EXPECT_CALL(*m_stubMiscStorage, open()).Times(0); + EXPECT_CALL(*m_stubMiscStorage, tableExists(_, _, _)).Times(1); + EXPECT_CALL(*m_stubMiscStorage, createTable(_, _, _, _)).Times(1); + + ASSERT_EQ(nullptr, SpeakerManagerMiscStorage::create(m_stubMiscStorage)); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_CreateTable) { + ON_CALL(*m_stubMiscStorage, isOpened()).WillByDefault(Return(true)); + ON_CALL(*m_stubMiscStorage, tableExists(_, _, _)) + .WillByDefault(Invoke([](const std::string&, const std::string&, bool* res) { + *res = false; + return true; + })); + ON_CALL(*m_stubMiscStorage, createTable(_, _, _, _)).WillByDefault(Return(true)); + + EXPECT_CALL(*m_stubMiscStorage, isOpened()).Times(1); + EXPECT_CALL(*m_stubMiscStorage, open()).Times(0); + EXPECT_CALL(*m_stubMiscStorage, tableExists(_, _, _)).Times(1); + EXPECT_CALL(*m_stubMiscStorage, createTable(_, _, _, _)).Times(1); + + ASSERT_NE(nullptr, SpeakerManagerMiscStorage::create(m_stubMiscStorage)); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_OpenedAndTableExists) { + ON_CALL(*m_stubMiscStorage, isOpened()).WillByDefault(Return(true)); + ON_CALL(*m_stubMiscStorage, tableExists(_, _, _)) + .WillByDefault(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + EXPECT_CALL(*m_stubMiscStorage, isOpened()).Times(1); + EXPECT_CALL(*m_stubMiscStorage, open()).Times(0); + EXPECT_CALL(*m_stubMiscStorage, tableExists(_, _, _)).Times(1); + EXPECT_CALL(*m_stubMiscStorage, createTable(_, _, _, _)).Times(0); + + ASSERT_NE(nullptr, SpeakerManagerMiscStorage::create(m_stubMiscStorage)); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_GetPut) { + ON_CALL(*m_stubMiscStorage, isOpened()).WillByDefault(Return(true)); + ON_CALL(*m_stubMiscStorage, tableExists(_, _, _)) + .WillByDefault(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + std::string jsonString; + ON_CALL(*m_stubMiscStorage, put(_, _, _, _)) + .WillByDefault( + Invoke([&jsonString](const std::string&, const std::string&, const std::string&, const std::string& data) { + jsonString = data; + return true; + })); + ON_CALL(*m_stubMiscStorage, get(_, _, _, _)) + .WillByDefault( + Invoke([&jsonString](const std::string&, const std::string&, const std::string&, std::string* data) { + *data = jsonString; + return true; + })); + + EXPECT_CALL(*m_stubMiscStorage, isOpened()).Times(1); + EXPECT_CALL(*m_stubMiscStorage, tableExists(_, _, _)).Times(1); + + EXPECT_CALL(*m_stubMiscStorage, put(_, _, _, _)).Times(1); + EXPECT_CALL(*m_stubMiscStorage, get(_, _, _, _)).Times(1); + + SpeakerManagerStorageState state1 = {{10, false}, {20, true}}; + SpeakerManagerStorageState state2 = {{0, true}, {0, false}}; + + auto storage = SpeakerManagerMiscStorage::create(m_stubMiscStorage); + ASSERT_TRUE(storage->saveState(state1)); + ASSERT_TRUE(storage->loadState(state2)); + + EXPECT_EQ(state1.speakerChannelState.channelVolume, state2.speakerChannelState.channelVolume); + EXPECT_EQ(state1.speakerChannelState.channelMuteStatus, state2.speakerChannelState.channelMuteStatus); + EXPECT_EQ(state1.alertsChannelState.channelVolume, state2.alertsChannelState.channelVolume); + EXPECT_EQ(state1.alertsChannelState.channelMuteStatus, state2.alertsChannelState.channelMuteStatus); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_failedGet) { + ON_CALL(*m_stubMiscStorage, isOpened()).WillByDefault(Return(true)); + ON_CALL(*m_stubMiscStorage, tableExists(_, _, _)) + .WillByDefault(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + ON_CALL(*m_stubMiscStorage, get(_, _, _, _)) + .WillByDefault(Invoke( + [](const std::string&, const std::string&, const std::string&, std::string* data) { return false; })); + + EXPECT_CALL(*m_stubMiscStorage, isOpened()).Times(1); + EXPECT_CALL(*m_stubMiscStorage, tableExists(_, _, _)).Times(1); + + EXPECT_CALL(*m_stubMiscStorage, get(_, _, _, _)).Times(1); + + SpeakerManagerStorageState state1 = {{10, false}, {20, true}}; + + auto storage = SpeakerManagerMiscStorage::create(m_stubMiscStorage); + ASSERT_FALSE(storage->loadState(state1)); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_FailedPut) { + ON_CALL(*m_stubMiscStorage, isOpened()).WillByDefault(Return(true)); + ON_CALL(*m_stubMiscStorage, tableExists(_, _, _)) + .WillByDefault(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + ON_CALL(*m_stubMiscStorage, put(_, _, _, _)) + .WillByDefault(Invoke( + [](const std::string&, const std::string&, const std::string&, const std::string& data) { return false; })); + + EXPECT_CALL(*m_stubMiscStorage, isOpened()).Times(1); + EXPECT_CALL(*m_stubMiscStorage, tableExists(_, _, _)).Times(1); + + EXPECT_CALL(*m_stubMiscStorage, put(_, _, _, _)).Times(1); + + SpeakerManagerStorageState state1 = {{10, false}, {20, true}}; + + auto storage = SpeakerManagerMiscStorage::create(m_stubMiscStorage); + ASSERT_FALSE(storage->saveState(state1)); +} + +TEST_F(SpeakerManagerMiscStorageTest, test_parseJson) { + ON_CALL(*m_stubMiscStorage, isOpened()).WillByDefault(Return(true)); + ON_CALL(*m_stubMiscStorage, tableExists(_, _, _)) + .WillByDefault(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + ON_CALL(*m_stubMiscStorage, get(_, _, _, _)) + .WillByDefault(Invoke([](const std::string&, const std::string&, const std::string&, std::string* data) { + *data = JSON_PAYLOAD; + return true; + })); + + EXPECT_CALL(*m_stubMiscStorage, isOpened()).Times(1); + EXPECT_CALL(*m_stubMiscStorage, tableExists(_, _, _)).Times(1); + + EXPECT_CALL(*m_stubMiscStorage, get(_, _, _, _)).Times(1); + + SpeakerManagerStorageState state1 = {{10, true}, {20, false}}; + + auto storage = SpeakerManagerMiscStorage::create(m_stubMiscStorage); + ASSERT_TRUE(storage->loadState(state1)); + + EXPECT_EQ(10, state1.speakerChannelState.channelVolume); + EXPECT_FALSE(state1.speakerChannelState.channelMuteStatus); + EXPECT_EQ(15, state1.alertsChannelState.channelVolume); + EXPECT_TRUE(state1.alertsChannelState.channelMuteStatus); +} + +} // namespace test +} // namespace speakerManager +} // namespace capabilityAgents +} // namespace alexaClientSDK \ No newline at end of file diff --git a/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp b/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp index 858abacfde..1fb1a8799a 100644 --- a/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp +++ b/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -#include +#include #include #include #include @@ -32,10 +32,10 @@ #include #include #include -#include #include #include +#include "SpeakerManager/SpeakerManagerStorageInterface.h" #include "SpeakerManager/SpeakerManager.h" namespace alexaClientSDK { @@ -103,6 +103,37 @@ class MockObserver : public SpeakerManagerObserverInterface { void(const Source&, const ChannelVolumeInterface::Type&, const SpeakerInterface::SpeakerSettings&)); }; +class MockSpeakerManagerStorage : public SpeakerManagerStorageInterface { +public: + MOCK_METHOD1(loadState, bool(SpeakerManagerStorageState&)); + MOCK_METHOD1(saveState, bool(const SpeakerManagerStorageState&)); + + MockSpeakerManagerStorage() : + m_state{.speakerChannelState = {.channelVolume = AVS_SET_VOLUME_MIN, .channelMuteStatus = UNMUTE}, + .alertsChannelState = {.channelVolume = AVS_SET_VOLUME_MIN, .channelMuteStatus = UNMUTE}} { + ON_CALL(*this, loadState(_)).WillByDefault(Invoke([this](SpeakerManagerStorageState& state) { + state = this->m_state; + return true; + })); + ON_CALL(*this, saveState(_)).WillByDefault(Invoke([this](const SpeakerManagerStorageState& state) { + this->m_state = state; + return true; + })); + } + + void setDefaults() { + m_state = {.speakerChannelState = {.channelVolume = AVS_SET_VOLUME_MIN, .channelMuteStatus = UNMUTE}, + .alertsChannelState = {.channelVolume = AVS_SET_VOLUME_MIN, .channelMuteStatus = UNMUTE}}; + } + + void setFailureMode() { + ON_CALL(*this, loadState(_)).WillByDefault(Return(false)); + ON_CALL(*this, saveState(_)).WillByDefault(Return(false)); + } + + SpeakerManagerStorageState m_state; +}; + class SpeakerManagerTest : public ::testing::TestWithParam> { public: /// SetUp before each test. @@ -158,6 +189,8 @@ class SpeakerManagerTest : public ::testing::TestWithParam> m_mockContextManager; + std::shared_ptr m_mockStorage; + /// A strict mock that allows the test to strictly monitor the messages sent. std::shared_ptr> m_mockMessageSender; @@ -175,6 +208,8 @@ class SpeakerManagerTest : public ::testing::TestWithParam>(); + m_metricRecorder = std::make_shared>(); m_mockContextManager = std::make_shared>(); m_mockMessageSender = std::make_shared>(); @@ -260,7 +295,7 @@ TEST_F(SpeakerManagerTest, test_nullContextManager) { auto channelVolumeInterfaces = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - channelVolumeInterfaces, nullptr, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, channelVolumeInterfaces, nullptr, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); ASSERT_EQ(m_speakerManager, nullptr); } @@ -271,7 +306,7 @@ TEST_F(SpeakerManagerTest, test_nullContextManager) { TEST_F(SpeakerManagerTest, test_nullMessageSender) { auto channelVolumeInterfaces = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - channelVolumeInterfaces, m_mockContextManager, nullptr, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, channelVolumeInterfaces, m_mockContextManager, nullptr, m_mockExceptionSender, m_metricRecorder); ASSERT_EQ(m_speakerManager, nullptr); } @@ -282,7 +317,7 @@ TEST_F(SpeakerManagerTest, test_nullMessageSender) { TEST_F(SpeakerManagerTest, test_nullExceptionSender) { auto channelVolumeInterfaces = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - channelVolumeInterfaces, m_mockContextManager, m_mockMessageSender, nullptr, m_metricRecorder); + m_mockStorage, channelVolumeInterfaces, m_mockContextManager, m_mockMessageSender, nullptr, m_metricRecorder); ASSERT_EQ(m_speakerManager, nullptr); } @@ -291,8 +326,8 @@ TEST_F(SpeakerManagerTest, test_nullExceptionSender) { * Tests creating the SpeakerManager with no channelVolumeInterfaces. */ TEST_F(SpeakerManagerTest, test_noChannelVolumeInterfaces) { - m_speakerManager = - SpeakerManager::create({}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_speakerManager = SpeakerManager::create( + m_mockStorage, {}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); ASSERT_NE(m_speakerManager, nullptr); } @@ -308,7 +343,7 @@ TEST_F(SpeakerManagerTest, test_contextManagerSetStateConstructor) { auto groups = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - groups, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groups, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); } /* @@ -317,12 +352,23 @@ TEST_F(SpeakerManagerTest, test_contextManagerSetStateConstructor) { TEST_F(SpeakerManagerTest, test_setVolumeUnderBounds) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); - EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + + // Expect call on initialization + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( - {channelVolumeInterface}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + // Expect no more calls + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + m_speakerManager->addSpeakerManagerObserver(m_observer); SpeakerManagerInterface::NotificationProperties properties; std::future future = m_speakerManager->setVolume( @@ -336,11 +382,21 @@ TEST_F(SpeakerManagerTest, test_setVolumeUnderBounds) { TEST_F(SpeakerManagerTest, test_setVolumeOverBounds) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); - EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + + // Expect call on initialization. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( - {channelVolumeInterface}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + // Expect no more calls. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); SpeakerManagerInterface::NotificationProperties properties; @@ -355,11 +411,21 @@ TEST_F(SpeakerManagerTest, test_setVolumeOverBounds) { TEST_F(SpeakerManagerTest, test_adjustVolumeUnderBounds) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); - EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + + // Expect call on initialization. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( - {channelVolumeInterface}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + // Expect no more calls. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -375,11 +441,20 @@ TEST_F(SpeakerManagerTest, test_adjustVolumeUnderBounds) { TEST_F(SpeakerManagerTest, test_adjustVolumeOverBounds) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); - EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + // Expect call on initialization. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( - {channelVolumeInterface}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + // Expect no more calls. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); SpeakerManagerInterface::NotificationProperties properties; @@ -401,6 +476,7 @@ TEST_F(SpeakerManagerTest, test_getCachedSettings) { EXPECT_CALL(*channelVolumeInterface1, getSpeakerSettings(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( + m_mockStorage, {channelVolumeInterface1, channelVolumeInterface2}, m_mockContextManager, m_mockMessageSender, @@ -433,7 +509,7 @@ TEST_F(SpeakerManagerTest, test_eventNotSentWhenAdjustVolumeUnchanged) { auto groupVec = std::vector>{channelVolumeInterface}; m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); // The test adjusts the volume by AVS_ADJUST_VOLUME_MIN, which results in the lowest volume possible. SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; @@ -446,6 +522,7 @@ TEST_F(SpeakerManagerTest, test_eventNotSentWhenAdjustVolumeUnchanged) { onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) .Times(Exactly(1)); if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(0)); EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) .Times(AnyNumber()); @@ -466,11 +543,11 @@ TEST_F(SpeakerManagerTest, test_eventNotSentWhenAdjustVolumeUnchanged) { TEST_F(SpeakerManagerTest, test_eventNotSentWhenSetVolumeUnchanged) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); - EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(AVS_SET_VOLUME_MIN)).Times(Exactly(1)); + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(AVS_SET_VOLUME_MIN)).Times(Exactly(2)); auto groupVec = std::vector>{channelVolumeInterface}; m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -481,6 +558,7 @@ TEST_F(SpeakerManagerTest, test_eventNotSentWhenSetVolumeUnchanged) { *m_observer, onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) .Times(Exactly(1)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(0)); EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) @@ -503,7 +581,12 @@ TEST_F(SpeakerManagerTest, test_getConfiguration) { auto channelVolumeInterfaceVec = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - channelVolumeInterfaceVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + channelVolumeInterfaceVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); auto configuration = m_speakerManager->getConfiguration(); auto neitherNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); @@ -521,7 +604,12 @@ TEST_F(SpeakerManagerTest, test_addDuplicatedChannelVolumeInterfaces) { std::vector> channelVolumeInterfaceVec = {channelVolumeInterface, channelVolumeInterface}; m_speakerManager = SpeakerManager::create( - channelVolumeInterfaceVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + channelVolumeInterfaceVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); SpeakerManagerInterface::NotificationProperties properties; @@ -537,7 +625,12 @@ TEST_F(SpeakerManagerTest, test_addNullObserver) { auto channelVolumeInterfaceVec = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - channelVolumeInterfaceVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + channelVolumeInterfaceVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); m_speakerManager->addSpeakerManagerObserver(nullptr); EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(2)); SpeakerManagerInterface::NotificationProperties properties; @@ -559,7 +652,12 @@ TEST_F(SpeakerManagerTest, test_removeSpeakerManagerObserver) { EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(2)); m_speakerManager = SpeakerManager::create( - channelVolumeInterfaceVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + channelVolumeInterfaceVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); m_speakerManager->addSpeakerManagerObserver(m_observer); m_speakerManager->removeSpeakerManagerObserver(m_observer); SpeakerManagerInterface::NotificationProperties properties; @@ -580,7 +678,12 @@ TEST_F(SpeakerManagerTest, test_removeNullObserver) { EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(2)); m_speakerManager = SpeakerManager::create( - channelVolumeInterfaceVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + channelVolumeInterfaceVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); m_speakerManager->removeSpeakerManagerObserver(nullptr); SpeakerManagerInterface::NotificationProperties properties; @@ -599,7 +702,12 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForSetVolume) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); m_speakerManager = SpeakerManager::create( - {channelVolumeInterface}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); auto retryTimes = 0; EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).WillRepeatedly(InvokeWithoutArgs([&retryTimes] { @@ -623,6 +731,7 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForAdjustVolume) { channelVolumeInterface2->DelegateToReal(); m_speakerManager = SpeakerManager::create( + m_mockStorage, {channelVolumeInterface1, channelVolumeInterface2}, m_mockContextManager, m_mockMessageSender, @@ -661,7 +770,12 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForSetMute) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); m_speakerManager = SpeakerManager::create( - {channelVolumeInterface}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); auto retryTimes = 0; EXPECT_CALL(*channelVolumeInterface, setMute(_)).WillRepeatedly(InvokeWithoutArgs([&retryTimes] { @@ -685,7 +799,12 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsFails) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); m_speakerManager = SpeakerManager::create( - {channelVolumeInterface}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).WillRepeatedly(Return(false)); EXPECT_CALL(*channelVolumeInterface, setMute(_)).WillRepeatedly(Return(false)); @@ -737,8 +856,10 @@ TEST_F(SpeakerManagerTest, test_setMaximumVolumeLimit) { EXPECT_CALL(*avsChannelVolumeInterface, setUnduckedVolume(_)).Times(AtLeast(1)); EXPECT_CALL(*alertsChannelVolumeInterface, setUnduckedVolume(_)).Times(AtLeast(1)); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(0); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( + m_mockStorage, {avsChannelVolumeInterface, alertsChannelVolumeInterface}, m_mockContextManager, m_mockMessageSender, @@ -792,7 +913,10 @@ TEST_F(SpeakerManagerTest, testSetMaximumVolumeLimitWhileVolumeIsHigher) { // Expect volumeChanged event. EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); + m_speakerManager = SpeakerManager::create( + m_mockStorage, {avsChannelVolumeInterface, alertsChannelVolumeInterface}, m_mockContextManager, m_mockMessageSender, @@ -819,12 +943,14 @@ TEST_F(SpeakerManagerTest, testAVSSetVolumeHigherThanLimit) { avsChannelVolumeInterface->DelegateToReal(); alertsChannelVolumeInterface->DelegateToReal(); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); EXPECT_TRUE(avsChannelVolumeInterface->setUnduckedVolume(VALID_MAXIMUM_VOLUME_LIMIT - 1)); EXPECT_TRUE(alertsChannelVolumeInterface->setUnduckedVolume(VALID_MAXIMUM_VOLUME_LIMIT - 1)); m_speakerManager = SpeakerManager::create( + m_mockStorage, {avsChannelVolumeInterface, alertsChannelVolumeInterface}, m_mockContextManager, m_mockMessageSender, @@ -846,7 +972,12 @@ TEST_F(SpeakerManagerTest, testSetMaximumVolumeLimitWithInvalidValue) { auto avsChannelVolumeInterface = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - avsChannelVolumeInterface, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, + avsChannelVolumeInterface, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); EXPECT_FALSE(m_speakerManager->setMaximumVolumeLimit(INVALID_MAXIMUM_VOLUME_LIMIT).get()); } @@ -891,18 +1022,20 @@ TEST_P(SpeakerManagerTest, test_setVolume) { for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); group->DelegateToReal(); + EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(1)); EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(1)); groupVec.push_back(group); } m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); SpeakerManagerInterface::NotificationProperties properties(SpeakerManagerObserverInterface::Source::DIRECTIVE); for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); EXPECT_CALL( *m_observer, onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::DIRECTIVE, type, expectedSettings)) @@ -935,7 +1068,7 @@ TEST_P(SpeakerManagerTest, test_adjustVolume) { } m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); // The test adjusts the volume by AVS_ADJUST_VOLUME_MAX, which results in the lowest volume possible. SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; @@ -943,6 +1076,7 @@ TEST_P(SpeakerManagerTest, test_adjustVolume) { SpeakerManagerInterface::NotificationProperties properties(SpeakerManagerObserverInterface::Source::DIRECTIVE); for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); EXPECT_CALL( *m_observer, onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::DIRECTIVE, type, expectedSettings)) @@ -971,12 +1105,13 @@ TEST_P(SpeakerManagerTest, test_setMute) { for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); group->DelegateToReal(); + EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); EXPECT_CALL(*group, setMute(MUTE)).Times(Exactly(1)); groupVec.push_back(group); } m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); SpeakerInterface::SpeakerSettings expectedSettings{DEFAULT_SETTINGS.volume, MUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -1023,7 +1158,7 @@ TEST_P(SpeakerManagerTest, test_getSpeakerSettings) { } m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -1068,8 +1203,10 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirective) { SpeakerInterface::SpeakerSettings temp; group->getSpeakerSettings(&temp); if (temp.mute) { + EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); EXPECT_CALL(*group, setMute(UNMUTE)).Times(Exactly(timesCalled)); } + EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(1)); EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(timesCalled)); groupVec.push_back(group); @@ -1103,7 +1240,7 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirective) { .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); m_speakerManager->addSpeakerManagerObserver(m_observer); // Create Directive. @@ -1139,8 +1276,10 @@ TEST_P(SpeakerManagerTest, test_adjustVolumeDirective) { SpeakerInterface::SpeakerSettings temp; group->getSpeakerSettings(&temp); if (temp.mute) { + EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); EXPECT_CALL(*group, setMute(UNMUTE)).Times(Exactly(timesCalled)); } + EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(1)); EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(timesCalled)); groupVec.push_back(group); @@ -1174,7 +1313,7 @@ TEST_P(SpeakerManagerTest, test_adjustVolumeDirective) { .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); m_speakerManager->addSpeakerManagerObserver(m_observer); // Create Directive. @@ -1207,6 +1346,7 @@ TEST_P(SpeakerManagerTest, test_setMuteDirective) { timesCalled = 1; } + EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); EXPECT_CALL(*group, setMute(MUTE)).Times(Exactly(timesCalled)); groupVec.push_back(group); @@ -1241,7 +1381,7 @@ TEST_P(SpeakerManagerTest, test_setMuteDirective) { .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); m_speakerManager->addSpeakerManagerObserver(m_observer); // Create Directive. @@ -1267,18 +1407,23 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirectiveWhenMuted) { for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); group->DelegateToReal(); - EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MIN)).Times(1); - EXPECT_CALL(*group, setMute(MUTE)).Times(1); + groupVec.push_back(group); + } + + m_speakerManager = SpeakerManager::create( + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + for (auto& group : groupVec) { + auto mockGroup = std::dynamic_pointer_cast>(group); + EXPECT_CALL(*mockGroup, setUnduckedVolume(AVS_SET_VOLUME_MIN)).Times(1); + EXPECT_CALL(*mockGroup, setMute(MUTE)).Times(1); + auto typeOfSpeaker = mockGroup->getSpeakerType(); if (typeOfSpeaker == ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME) { - EXPECT_CALL(*group, setMute(UNMUTE)).Times(1); - EXPECT_CALL(*group, setUnduckedVolume(MIN_UNMUTE_VOLUME)).Times(1); + EXPECT_CALL(*mockGroup, setMute(UNMUTE)).Times(1); + EXPECT_CALL(*mockGroup, setUnduckedVolume(MIN_UNMUTE_VOLUME)).Times(1); } - groupVec.push_back(group); } - m_speakerManager = SpeakerManager::create( - groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); m_speakerManager->addSpeakerManagerObserver(m_observer); SpeakerManagerInterface::NotificationProperties properties( SpeakerManagerObserverInterface::Source::LOCAL_API, false, false); @@ -1340,6 +1485,107 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirectiveWhenMuted) { m_wakeSetCompletedFuture.wait_for(TIMEOUT); } +/** + * Parameterized test for getSpeakerSettings. Operation should succeed with default speaker settings. + */ +TEST_P(SpeakerManagerTest, test_getSpeakerConfigDefaults) { + std::vector> groupVec; + std::set uniqueTypes; + + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + + // There should be one call to getSpeakerSettings for the first speaker of each type. + if (uniqueTypes.find(typeOfSpeaker) == uniqueTypes.end()) { + EXPECT_CALL(*group, getSpeakerSettings(_)).Times(AtLeast(1)); + uniqueTypes.insert(typeOfSpeaker); + } + + groupVec.push_back(group); + } + + m_mockStorage->setFailureMode(); + + m_speakerManager = SpeakerManager::create( + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + + for (auto speaker : groupVec) { + // SpeakerManager attempts to cache speaker settings initially. No getSpeakerSettings() call should be made to + // each speaker. + auto mockSpeaker = std::dynamic_pointer_cast>(speaker); + ASSERT_TRUE(mockSpeaker); + EXPECT_CALL(*mockSpeaker, getSpeakerSettings(_)).Times(0); + } + + for (auto type : uniqueTypes) { + SpeakerInterface::SpeakerSettings settings; + // Query SpeakerMananger for speaker settings, value should be cached and not queried from each speaker. + std::future future = m_speakerManager->getSpeakerSettings(type, &settings); + ASSERT_TRUE(future.get()); + + switch (type) { + case avsCommon::sdkInterfaces::ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME: + EXPECT_EQ(settings.volume, alexaClientSDK::avsCommon::avs::speakerConstants::DEFAULT_SPEAKER_VOLUME); + break; + case avsCommon::sdkInterfaces::ChannelVolumeInterface::Type::AVS_ALERTS_VOLUME: + EXPECT_EQ(settings.volume, alexaClientSDK::avsCommon::avs::speakerConstants::DEFAULT_ALERTS_VOLUME); + break; + } + ASSERT_EQ(settings.mute, false); + } +} + +/** + * Parameterized test for getSpeakerSettings. Operation should succeed with speaker settings from storage. + */ +TEST_P(SpeakerManagerTest, test_getSpeakerConfigFromStorage) { + std::vector> groupVec; + std::set uniqueTypes; + + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + + // There should be one call to getSpeakerSettings for the first speaker of each type. + if (uniqueTypes.find(typeOfSpeaker) == uniqueTypes.end()) { + EXPECT_CALL(*group, getSpeakerSettings(_)).Times(AtLeast(1)); + uniqueTypes.insert(typeOfSpeaker); + } + + groupVec.push_back(group); + } + + m_mockStorage->setDefaults(); + + m_speakerManager = SpeakerManager::create( + m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + + for (auto speaker : groupVec) { + // SpeakerManager attempts to cache speaker settings initially. No getSpeakerSettings() call should be made to + // each speaker. + auto mockSpeaker = std::dynamic_pointer_cast>(speaker); + ASSERT_TRUE(mockSpeaker); + EXPECT_CALL(*mockSpeaker, getSpeakerSettings(_)).Times(0); + } + + for (auto type : uniqueTypes) { + SpeakerInterface::SpeakerSettings settings; + // Query SpeakerMananger for speaker settings, value should be cached and not queried from each speaker. + std::future future = m_speakerManager->getSpeakerSettings(type, &settings); + ASSERT_TRUE(future.get()); + + EXPECT_EQ(settings.volume, AVS_SET_VOLUME_MIN); + EXPECT_EQ(settings.mute, false); + } +} + } // namespace test } // namespace speakerManager } // namespace capabilityAgents diff --git a/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h b/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h index c9ca2a597f..8270a00857 100644 --- a/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h +++ b/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h @@ -383,6 +383,17 @@ class SpeechSynthesizer */ void submitMetric(avsCommon::utils::metrics::MetricEventBuilder& metricEventBuilder); + /** + * Creates and records an instance entry metric with the given identifiers and metadata. + * @param segmentId The segmentId corresponding to this metric event. + * @param name The name of this metric + * @param metadata Any metadata to be associated with this metric; default is empty + */ + void submitInstanceEntryMetric( + const std::string& segmentId, + const std::string& name, + const std::map& metadata = {}); + /** * This function is called whenever the AVS UX dialog state of the system changes. This function will block * processing of other state changes, so any implementation of this should return quickly. @@ -656,6 +667,9 @@ class SpeechSynthesizer /// Set of capability configurations that will get published using the Capabilities API std::unordered_set> m_capabilityConfigurations; + /// A @c PowerResourceId used for wakelock logic. + std::shared_ptr m_powerResourceId; + /// The power resource manager std::shared_ptr m_powerResourceManager; diff --git a/CapabilityAgents/SpeechSynthesizer/src/CMakeLists.txt b/CapabilityAgents/SpeechSynthesizer/src/CMakeLists.txt index c37acf17ea..a4cc1d93ad 100644 --- a/CapabilityAgents/SpeechSynthesizer/src/CMakeLists.txt +++ b/CapabilityAgents/SpeechSynthesizer/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=speechSynthesizer") -add_library(SpeechSynthesizer SHARED +add_library(SpeechSynthesizer SpeechSynthesizer.cpp) target_include_directories(SpeechSynthesizer PUBLIC diff --git a/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp b/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp index 847dd2ce7f..dfb53ab5cf 100644 --- a/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp +++ b/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp @@ -157,6 +157,15 @@ static const std::string DIALOG_REQUEST_ID_KEY = "DIALOG_REQUEST_ID"; /// Metric to emit on TTS buffer underrrun static const std::string BUFFER_UNDERRUN = "ERROR.TTS_BUFFER_UNDERRUN"; +/// Keys for instance entry metric specific fields +static const std::string ENTRY_METRIC_ACTOR_NAME = "SpeechSynthesizer"; +static const std::string ENTRY_METRIC_ACTIVITY_NAME = SPEECH_SYNTHESIZER_METRIC_PREFIX + ENTRY_METRIC_ACTOR_NAME; +static const std::string ENTRY_METRIC_KEY_SEGMENT_ID = "segment_id"; +static const std::string ENTRY_METRIC_KEY_ACTOR = "actor"; +static const std::string ENTRY_METRIC_KEY_ENTRY_TYPE = "entry_type"; +static const std::string ENTRY_METRIC_KEY_ENTRY_NAME = "entry_name"; +static const std::string ENTRY_METRIC_NAME_STATE_CHANGE = "StateChange"; + /** * Creates the SpeechSynthesizer capability configuration. * @@ -410,6 +419,7 @@ void SpeechSynthesizer::onPlaybackStarted(SourceId id, const MediaPlayerState&) .setName(DIALOG_REQUEST_ID_KEY) .setValue(m_currentInfo->directive->getDialogRequestId()) .build())); + submitInstanceEntryMetric(m_currentInfo->directive->getDialogRequestId(), TTS_STARTED); executePlaybackStarted(); } }); @@ -434,6 +444,7 @@ void SpeechSynthesizer::onPlaybackFinished(SourceId id, const MediaPlayerState&) .setName(DIALOG_REQUEST_ID_KEY) .setValue(m_currentInfo->directive->getDialogRequestId()) .build())); + submitInstanceEntryMetric(m_currentInfo->directive->getDialogRequestId(), TTS_FINISHED); executePlaybackFinished(); } }); @@ -510,6 +521,14 @@ SpeechSynthesizer::SpeechSynthesizer( m_initialDialogUXStateReceived{false}, m_powerResourceManager{powerResourceManager} { m_capabilityConfigurations.insert(getSpeechSynthesizerCapabilityConfiguration()); + + if (m_powerResourceManager) { + m_powerResourceId = m_powerResourceManager->create( + POWER_RESOURCE_COMPONENT_NAME, false, PowerResourceManagerInterface::PowerResourceLevel::STANDBY_MED); + if (!m_powerResourceId) { + ACSDK_ERROR(LX(__func__).d("reason", "createPowerResourceFailed").d("name", POWER_RESOURCE_COMPONENT_NAME)); + } + } } std::shared_ptr getSpeechSynthesizerCapabilityConfiguration() { @@ -566,6 +585,12 @@ void SpeechSynthesizer::doShutdown() { m_focusManager.reset(); m_contextManager.reset(); m_observers.clear(); + + if (m_powerResourceManager && m_powerResourceId) { + m_powerResourceManager->close(m_powerResourceId); + } + m_powerResourceManager.reset(); + m_powerResourceId.reset(); } void SpeechSynthesizer::init() { @@ -1424,19 +1449,56 @@ void SpeechSynthesizer::submitMetric(MetricEventBuilder& metricEventBuilder) { } } +void SpeechSynthesizer::submitInstanceEntryMetric( + const std::string& segmentId, + const std::string& name, + const std::map& metadata) { + if (!m_metricRecorder) { + return; + } + if (segmentId.empty() || name.empty()) { + ACSDK_ERROR(LX(__FUNCTION__).m("Unable to create instance metric").d("segmentId", segmentId).d("name", name)); + return; + } + + auto metricBuilder = MetricEventBuilder{}.setActivityName(ENTRY_METRIC_ACTIVITY_NAME); + metricBuilder.addDataPoint( + DataPointStringBuilder{}.setName(ENTRY_METRIC_KEY_SEGMENT_ID).setValue(segmentId).build()); + metricBuilder.addDataPoint( + DataPointStringBuilder{}.setName(ENTRY_METRIC_KEY_ACTOR).setValue(ENTRY_METRIC_ACTOR_NAME).build()); + metricBuilder.addDataPoint(DataPointStringBuilder{}.setName(ENTRY_METRIC_KEY_ENTRY_NAME).setValue(name).build()); + metricBuilder.addDataPoint( + DataPointStringBuilder{}.setName(ENTRY_METRIC_KEY_ENTRY_TYPE).setValue("INSTANCE").build()); + for (auto const& pair : metadata) { + metricBuilder.addDataPoint(DataPointStringBuilder{}.setName(pair.first).setValue(pair.second).build()); + } + if (m_currentInfo) { + metricBuilder.addDataPoint(DataPointStringBuilder{} + .setName("DIRECTIVE_MESSAGE_ID") + .setValue(m_currentInfo->directive->getMessageId()) + .build()); + } + auto metric = metricBuilder.build(); + if (metric == nullptr) { + ACSDK_ERROR(LX(__FUNCTION__).m("Error creating instance entry metric.")); + return; + } + recordMetric(m_metricRecorder, metric); +} + void SpeechSynthesizer::managePowerResource(SpeechSynthesizerObserverInterface::SpeechSynthesizerState newState) { - if (!m_powerResourceManager) { + if (!m_powerResourceId || !m_powerResourceManager) { return; } ACSDK_DEBUG5(LX(__func__).d("state", newState)); switch (newState) { case SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING: - m_powerResourceManager->acquirePowerResource(POWER_RESOURCE_COMPONENT_NAME); + m_powerResourceManager->acquire(m_powerResourceId); break; case SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED: case SpeechSynthesizerObserverInterface::SpeechSynthesizerState::INTERRUPTED: - m_powerResourceManager->releasePowerResource(POWER_RESOURCE_COMPONENT_NAME); + m_powerResourceManager->release(m_powerResourceId); break; default: // no-op for focus change diff --git a/CapabilityAgents/SpeechSynthesizer/test/SpeechSynthesizerTest.cpp b/CapabilityAgents/SpeechSynthesizer/test/SpeechSynthesizerTest.cpp index 36116e8426..0234a6f2ec 100644 --- a/CapabilityAgents/SpeechSynthesizer/test/SpeechSynthesizerTest.cpp +++ b/CapabilityAgents/SpeechSynthesizer/test/SpeechSynthesizerTest.cpp @@ -462,6 +462,18 @@ void SpeechSynthesizerTest::SetUp() { m_mockAudioPipelineFactory = std::make_shared(); + EXPECT_CALL( + *m_mockPowerResourceManager, + create( + COMPONENT_NAME, + false, + avsCommon::sdkInterfaces::PowerResourceManagerInterface::PowerResourceLevel::STANDBY_MED)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke([](const std::string& resourceId, bool isRefCounted, const PowerResourceLevel level) { + return std::make_shared( + resourceId); + })); + bool equalizerAvailable = false; bool enableLiveMode = false; bool isCaptionable = true; @@ -602,8 +614,7 @@ TEST_F(SpeechSynthesizerTest, test_callingHandleImmediately) { .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); EXPECT_CALL(*m_mockCaptionManager, isEnabled()).WillRepeatedly(Return(true)); EXPECT_CALL(*m_mockCaptionManager, onCaption(_, _)).Times(1); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); std::vector data; EXPECT_CALL( @@ -661,8 +672,7 @@ TEST_F(SpeechSynthesizerTest, test_callingHandle) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetFailed)); EXPECT_CALL(*m_mockCaptionManager, isEnabled()).WillRepeatedly(Return(true)); EXPECT_CALL(*m_mockCaptionManager, onCaption(_, _)).Times(1); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); @@ -731,8 +741,7 @@ TEST_F(SpeechSynthesizerTest, test_callingCancelAfterHandle) { .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnReleaseChannel)); EXPECT_CALL(*(m_mockDirHandlerResult.get()), setFailed(_)).Times(0); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); @@ -748,7 +757,7 @@ TEST_F(SpeechSynthesizerTest, test_callingCancelAfterHandle) { m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(IsInterruptedEvent())) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStopped()); ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(MY_WAIT_TIMEOUT)); @@ -809,8 +818,7 @@ TEST_F(SpeechSynthesizerTest, test_callingProvideStateWhenPlaying) { EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_)) .Times(AtLeast(1)) .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); @@ -871,8 +879,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_bargeInWhilePlaying) { EXPECT_CALL(*(m_mockFocusManager.get()), releaseChannel(CHANNEL_NAME, _)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnReleaseChannel)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); @@ -888,7 +895,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_bargeInWhilePlaying) { m_wakeSendMessagePromise = std::promise(); m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future(); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); m_speechSynthesizer->handleDirectiveImmediately(directive2); ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStopped()); @@ -955,8 +962,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_notCallStopTwice) { })) .WillRepeatedly(Return(true)); EXPECT_CALL(*(m_mockDirHandlerResult.get()), setCompleted()).Times(AtLeast(0)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); // send Speak directive and getting focus and wait until playback started m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); @@ -973,7 +979,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_notCallStopTwice) { m_wakeSendMessagePromise = std::promise(); m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future(); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); // cancel directive, this should result in calling stop() m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(MY_WAIT_TIMEOUT)); @@ -992,8 +998,7 @@ TEST_F(SpeechSynthesizerTest, testTimer_notCallStopTwice) { m_wakeReleaseChannelPromise = std::promise(); m_wakeReleaseChannelFuture = m_wakeReleaseChannelPromise.get_future(); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); // send second speak directive and make sure it working m_speechSynthesizer->handleDirectiveImmediately(directive2); ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); @@ -1054,8 +1059,7 @@ TEST_F(SpeechSynthesizerTest, testSlow_callingCancelBeforeOnFocusChanged) { attachmentSetSource(A>(), nullptr)); EXPECT_CALL(*m_mockSpeechPlayer, play(_)); EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); // send second speak directive and make sure it working m_speechSynthesizer->handleDirectiveImmediately(directive2); @@ -1104,8 +1108,7 @@ TEST_F(SpeechSynthesizerTest, test_callingCancelBeforeOnExecuteStateChanged) { attachmentSetSource(A>(), nullptr)); EXPECT_CALL(*m_mockSpeechPlayer, play(_)); EXPECT_CALL(*m_mockSpeechPlayer, getOffset(_)).WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); // send second speak directive and make sure it working m_speechSynthesizer->handleDirectiveImmediately(directive2); @@ -1167,8 +1170,7 @@ TEST_F(SpeechSynthesizerTest, test_mediaPlayerFailedToStop) { return false; })); EXPECT_CALL(*(m_mockDirHandlerResult.get()), setFailed(_)).Times(AtLeast(0)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); // send Speak directive and getting focus and wait until playback started m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); @@ -1185,7 +1187,7 @@ TEST_F(SpeechSynthesizerTest, test_mediaPlayerFailedToStop) { m_wakeSendMessagePromise = std::promise(); m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future(); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); // cancel directive, this should result in calling stop() m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST); ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(MY_WAIT_TIMEOUT)); @@ -1205,8 +1207,7 @@ TEST_F(SpeechSynthesizerTest, test_mediaPlayerFailedToStop) { m_wakeReleaseChannelPromise = std::promise(); m_wakeReleaseChannelFuture = m_wakeReleaseChannelPromise.get_future(); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); // send second speak directive and make sure it working m_speechSynthesizer->handleDirectiveImmediately(directive2); ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); @@ -1301,9 +1302,8 @@ TEST_F(SpeechSynthesizerTest, testSlow_setStateTimeout) { EXPECT_CALL(*(m_mockDirHandlerResult.get()), setFailed(_)) .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetFailed)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); // Send Speak directive and getting focus and wait until state change timeout. m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); @@ -1355,15 +1355,14 @@ TEST_F(SpeechSynthesizerTest, test_givenPlayingStateFocusBecomesNone) { .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetFailed)); EXPECT_CALL(*(m_mockDirHandlerResult.get()), setCompleted()).Times(0); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); EXPECT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); m_speechSynthesizer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); EXPECT_TRUE(std::future_status::ready == m_wakeSetFailedFuture.wait_for(STATE_CHANGE_TIMEOUT)); } @@ -1391,15 +1390,14 @@ TEST_F(SpeechSynthesizerTest, testTimer_onPlayedStopped) { .Times(1) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetFailed)); EXPECT_CALL(*(m_mockDirHandlerResult.get()), setCompleted()).Times(0); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult)); m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); EXPECT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); EXPECT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); m_speechSynthesizer->onPlaybackStopped(m_mockSpeechPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); EXPECT_TRUE(std::future_status::ready == m_wakeSetFailedFuture.wait_for(STATE_CHANGE_TIMEOUT)); } @@ -1425,9 +1423,8 @@ bool SpeechSynthesizerTest::setupActiveSpeech( .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState)); EXPECT_CALL(*m_mockMessageSender, sendMessage(IsStartedEvent())) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(resultHandler)); m_speechSynthesizer->CapabilityAgent::handleDirective(info.messageId); @@ -1457,9 +1454,8 @@ bool SpeechSynthesizerTest::setupPendingSpeech( AVSDirective::create("", avsMessageHeader, info.payload, m_attachmentManager, CONTEXT_ID_TEST); EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(resultHandler)); m_speechSynthesizer->CapabilityAgent::handleDirective(info.messageId); @@ -1618,8 +1614,7 @@ TEST_F(SpeechSynthesizerTest, test_replaceAllStopActiveSpeech) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState)); EXPECT_CALL(*m_mockMessageSender, sendMessage(IsStartedEvent())) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); } { @@ -1650,7 +1645,7 @@ TEST_F(SpeechSynthesizerTest, test_replaceAllStopActiveSpeech) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState)); EXPECT_CALL(*m_mockMessageSender, sendMessage(IsFinishedEvent())) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); @@ -1720,8 +1715,7 @@ TEST_F(SpeechSynthesizerTest, test_enqueueWithActiveSpeech) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState)); EXPECT_CALL(*m_mockMessageSender, sendMessage(IsStartedEvent())) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); m_speechSynthesizer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); @@ -1746,7 +1740,7 @@ TEST_F(SpeechSynthesizerTest, test_enqueueWithActiveSpeech) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState)); EXPECT_CALL(*m_mockMessageSender, sendMessage(IsFinishedEvent())) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); @@ -1822,8 +1816,7 @@ TEST_F(SpeechSynthesizerTest, test_replaceEnqueuedWithAnotherEnqueuedItem) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState)); EXPECT_CALL(*m_mockMessageSender, sendMessage(IsStartedEvent())) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); m_speechSynthesizer->onFocusChanged(FocusState::NONE, MixingBehavior::MUST_STOP); m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); @@ -1846,7 +1839,7 @@ TEST_F(SpeechSynthesizerTest, test_replaceEnqueuedWithAnotherEnqueuedItem) { .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState)); EXPECT_CALL(*m_mockMessageSender, sendMessage(IsFinishedEvent())) .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); - EXPECT_CALL(*m_mockPowerResourceManager, releasePowerResource(COMPONENT_NAME)).Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, release(_)).Times(AtLeast(1)); m_mockSpeechPlayer->mockFinished(m_mockSpeechPlayer->getCurrentSourceId()); EXPECT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(MY_WAIT_TIMEOUT)); EXPECT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); @@ -1885,8 +1878,7 @@ TEST_F(SpeechSynthesizerTest, test_parsingSingleAnalyzerConfig) { .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); EXPECT_CALL(*m_mockCaptionManager, isEnabled()).WillRepeatedly(Return(true)); EXPECT_CALL(*m_mockCaptionManager, onCaption(_, _)).Times(1); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); std::vector data; data.push_back(audioAnalyzer::AudioAnalyzerState("analyzername", "YES")); EXPECT_CALL( @@ -1936,8 +1928,7 @@ TEST_F(SpeechSynthesizerTest, test_parsingMultipleAnalyzerConfig) { .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); EXPECT_CALL(*m_mockCaptionManager, isEnabled()).WillRepeatedly(Return(true)); EXPECT_CALL(*m_mockCaptionManager, onCaption(_, _)).Times(1); - EXPECT_CALL(*m_mockPowerResourceManager, acquirePowerResource(COMPONENT_NAME, PowerResourceLevel::STANDBY_MED)) - .Times(AtLeast(1)); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); std::vector data; data.push_back(audioAnalyzer::AudioAnalyzerState("analyzername1", "YES")); data.push_back(audioAnalyzer::AudioAnalyzerState("analyzername2", "NO")); diff --git a/CapabilityAgents/System/src/CMakeLists.txt b/CapabilityAgents/System/src/CMakeLists.txt index 70d7e1dd16..5f2ef1b3cc 100644 --- a/CapabilityAgents/System/src/CMakeLists.txt +++ b/CapabilityAgents/System/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=system") -add_library(AVSSystem SHARED +add_library(AVSSystem "${CMAKE_CURRENT_LIST_DIR}/LocaleHandler.cpp" "${CMAKE_CURRENT_LIST_DIR}/ReportStateHandler.cpp" "${CMAKE_CURRENT_LIST_DIR}/SoftwareInfoSender.cpp" diff --git a/CapabilityAgents/System/src/UserInactivityMonitor.cpp b/CapabilityAgents/System/src/UserInactivityMonitor.cpp index a32ab0812c..3d8ca625bf 100644 --- a/CapabilityAgents/System/src/UserInactivityMonitor.cpp +++ b/CapabilityAgents/System/src/UserInactivityMonitor.cpp @@ -207,7 +207,10 @@ void UserInactivityMonitor::onUserActive() { std::unique_lock timeLock(m_mutex, std::defer_lock); m_eventTimer.stop(); + ACSDK_DEBUG5(LX(__func__).m("Timer stopped")); + startTimer(); + ACSDK_DEBUG5(LX(__func__).m("Timer started")); if (timeLock.try_lock()) { m_lastTimeActive = std::chrono::steady_clock::now(); diff --git a/CapabilityAgents/System/test/LocaleHandlerTest.cpp b/CapabilityAgents/System/test/LocaleHandlerTest.cpp index 28815cb54b..b14fae38f2 100644 --- a/CapabilityAgents/System/test/LocaleHandlerTest.cpp +++ b/CapabilityAgents/System/test/LocaleHandlerTest.cpp @@ -57,18 +57,24 @@ constexpr char MESSAGE_ID[] = "1"; /// The value of the payload key for locales static const std::string LOCALES_PAYLOAD_KEY = "locales"; -/// A list of test locales. +/// A set of test locales. static const std::set TEST_LOCALES = {"en-US"}; -/// A list of test supported wake words. +/// A set of test supported wake words. static const std::set SUPPORTED_WAKE_WORDS = {"ALEXA", "ECHO"}; -/// A list of test supported locales. -static const std::set SUPPORTED_LOCALES = {"en-CA", "en-US"}; +/// A set of test supported locales. +static const std::set SUPPORTED_LOCALES = {"en-CA", "en-US", "fr-CA"}; + +/// A set of test multilingual supported locales. +static const std::set> SUPPORTED_MULTILINGUAL_LOCALES = {{"en-CA", "fr-CA"}}; /// Default locale. static const std::string DEFAULT_LOCALE = "en-CA"; +/// Default multilingual locale. +static const std::vector DEFAULT_MULTILINGUAL_LOCALE = {"en-CA", "fr-CA"}; + /// The SetLocales directive signature. static const avsCommon::avs::NamespaceAndName SET_WAKE_WORDS{NAMESPACE, "SetLocales"}; @@ -127,7 +133,13 @@ void LocaleHandlerTest::SetUp() { ON_CALL(*m_mockAssetsManager, getSupportedLocales()).WillByDefault(InvokeWithoutArgs([] { return SUPPORTED_LOCALES; })); + ON_CALL(*m_mockAssetsManager, getSupportedLocaleCombinations()).WillByDefault(InvokeWithoutArgs([] { + return SUPPORTED_MULTILINGUAL_LOCALES; + })); ON_CALL(*m_mockAssetsManager, getDefaultLocale()).WillByDefault(InvokeWithoutArgs([] { return DEFAULT_LOCALE; })); + ON_CALL(*m_mockAssetsManager, getDefaultLocales()).WillByDefault(InvokeWithoutArgs([] { + return DEFAULT_MULTILINGUAL_LOCALE; + })); ON_CALL(*m_mockAssetsManager, changeAssets(_, _)).WillByDefault(InvokeWithoutArgs([] { return true; })); EXPECT_CALL(*m_mockDeviceSettingStorage, loadSetting("System.locales")) @@ -142,7 +154,7 @@ void LocaleHandlerTest::SetUp() { return retPromise.get_future(); }; - // By default, all events can be sent succesfully. + // By default, all events can be sent successfully. ON_CALL(*m_mockWakeWordSettingMessageSender, sendChangedEvent(_)).WillByDefault(Invoke(settingSendEventSuccess)); ON_CALL(*m_mockWakeWordSettingMessageSender, sendReportEvent(_)).WillByDefault(Invoke(settingSendEventSuccess)); ON_CALL(*m_mockLocaleSettingMessageSender, sendChangedEvent(_)).WillByDefault(Invoke(settingSendEventSuccess)); diff --git a/CapabilityAgents/TemplateRuntime/src/CMakeLists.txt b/CapabilityAgents/TemplateRuntime/src/CMakeLists.txt index 8d35cd7483..8755e32d20 100644 --- a/CapabilityAgents/TemplateRuntime/src/CMakeLists.txt +++ b/CapabilityAgents/TemplateRuntime/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=templateRuntime") -add_library(TemplateRuntime SHARED +add_library(TemplateRuntime "${CMAKE_CURRENT_LIST_DIR}/TemplateRuntime.cpp" "${CMAKE_CURRENT_LIST_DIR}/RenderPlayerInfoCardsProviderRegistrar.cpp") diff --git a/CapabilityAgents/ToggleController/src/CMakeLists.txt b/CapabilityAgents/ToggleController/src/CMakeLists.txt index 0d34afd6de..5ab8a238b5 100644 --- a/CapabilityAgents/ToggleController/src/CMakeLists.txt +++ b/CapabilityAgents/ToggleController/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=toggleController") -add_library(ToggleController SHARED +add_library(ToggleController ToggleControllerAttributeBuilder.cpp ToggleControllerCapabilityAgent.cpp) diff --git a/Captions/Component/src/CMakeLists.txt b/Captions/Component/src/CMakeLists.txt index 1b70b95b36..e795840ca6 100644 --- a/Captions/Component/src/CMakeLists.txt +++ b/Captions/Component/src/CMakeLists.txt @@ -4,7 +4,7 @@ set(CaptionsComponent_SOURCES) list(APPEND CaptionsComponent_SOURCES CaptionsComponent.cpp) -add_library(CaptionsComponent SHARED ${CaptionsComponent_SOURCES}) +add_library(CaptionsComponent ${CaptionsComponent_SOURCES}) target_include_directories(CaptionsComponent PUBLIC "${Captions_SOURCE_DIR}/Component/include") diff --git a/Captions/Implementation/src/CMakeLists.txt b/Captions/Implementation/src/CMakeLists.txt index 6f0c4800b1..84b6dd76f8 100644 --- a/Captions/Implementation/src/CMakeLists.txt +++ b/Captions/Implementation/src/CMakeLists.txt @@ -11,7 +11,7 @@ if (CAPTIONS) list(APPEND CaptionsLib_SOURCES LibwebvttParserAdapter.cpp) endif() -add_library(CaptionsLib SHARED ${CaptionsLib_SOURCES}) +add_library(CaptionsLib ${CaptionsLib_SOURCES}) target_include_directories(CaptionsLib PUBLIC "${Captions_SOURCE_DIR}/Implementation/include") diff --git a/Captions/Implementation/test/CMakeLists.txt b/Captions/Implementation/test/CMakeLists.txt index 5acd63bd6a..4ac06dfd58 100644 --- a/Captions/Implementation/test/CMakeLists.txt +++ b/Captions/Implementation/test/CMakeLists.txt @@ -20,7 +20,6 @@ if (BUILD_TESTING) CaptionsLib UtilsCommonTestLib SDKInterfacesTests - gtest_main gmock_main) set(INCLUDE_PATH diff --git a/Captions/Interface/src/CMakeLists.txt b/Captions/Interface/src/CMakeLists.txt index 07ed007d46..4858ede56b 100644 --- a/Captions/Interface/src/CMakeLists.txt +++ b/Captions/Interface/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=captions") -add_library(Captions SHARED +add_library(Captions CaptionData.cpp CaptionLine.cpp CaptionFrame.cpp diff --git a/Captions/Interface/test/CMakeLists.txt b/Captions/Interface/test/CMakeLists.txt index ba69cf0b8a..72920628e2 100644 --- a/Captions/Interface/test/CMakeLists.txt +++ b/Captions/Interface/test/CMakeLists.txt @@ -19,7 +19,6 @@ if (BUILD_TESTING) AVSCommon Captions UtilsCommonTestLib - gtest_main gmock_main) set(INCLUDE_PATH diff --git a/CertifiedSender/src/CMakeLists.txt b/CertifiedSender/src/CMakeLists.txt index 6a4c206a1c..f3d52e28f2 100644 --- a/CertifiedSender/src/CMakeLists.txt +++ b/CertifiedSender/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=certifiedSender") -add_library(CertifiedSender SHARED +add_library(CertifiedSender CertifiedSender.cpp SQLiteMessageStorage.cpp) diff --git a/CertifiedSender/test/Common/CMakeLists.txt b/CertifiedSender/test/Common/CMakeLists.txt index b5777c4c78..756f8dfd93 100644 --- a/CertifiedSender/test/Common/CMakeLists.txt +++ b/CertifiedSender/test/Common/CMakeLists.txt @@ -14,6 +14,5 @@ if (BUILD_TESTING) SDKInterfacesTests CertifiedSender RegistrationManagerTestUtils - gtest_main gmock_main) endif() diff --git a/ContextManager/src/CMakeLists.txt b/ContextManager/src/CMakeLists.txt index ad9c7b9280..623b0b848d 100644 --- a/ContextManager/src/CMakeLists.txt +++ b/ContextManager/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=contextManager") -add_library(ContextManager SHARED +add_library(ContextManager ContextManager.cpp) target_include_directories(ContextManager PUBLIC diff --git a/ContextManager/src/ContextManager.cpp b/ContextManager/src/ContextManager.cpp index 2a517aa5ce..6631034a55 100644 --- a/ContextManager/src/ContextManager.cpp +++ b/ContextManager/src/ContextManager.cpp @@ -391,7 +391,9 @@ ContextRequestToken ContextManager::getContextInternal( bool requestState = false; if (stateInfo.legacyCapability && stateInfo.refreshPolicy != StateRefreshPolicy::NEVER) { requestState = true; - } else if (!stateInfo.legacyCapability && stateProvider->canStateBeRetrieved()) { + } else if ( + !stateInfo.legacyCapability && stateProvider->canStateBeRetrieved() && + stateProvider->shouldQueryState()) { if (stateProvider->hasReportableStateProperties()) { /// Check if the reportable state properties should be skipped. if (!bSkipReportableStateProperties) { diff --git a/ContextManager/test/ContextManagerTest.cpp b/ContextManager/test/ContextManagerTest.cpp index ed661b0703..54a6b4b1da 100644 --- a/ContextManager/test/ContextManagerTest.cpp +++ b/ContextManager/test/ContextManagerTest.cpp @@ -48,6 +48,7 @@ class MockStateProvider : public StateProviderInterface { provideState, void(const avs::CapabilityTag& stateProviderName, const ContextRequestToken stateRequestToken)); MOCK_METHOD0(hasReportableStateProperties, bool()); + MOCK_METHOD0(shouldQueryState, bool()); }; /// Mock legacy state provider. @@ -347,12 +348,14 @@ TEST_F(ContextManagerTest, test_getEndpointContextShouldIncludeOnlyRelevantState auto capabilityForTarget = CapabilityTag("TargetNamespace", "TargetName", "TargetEndpointId"); CapabilityState stateForTarget{R"({"state":"target"})"}; EXPECT_CALL(*providerForTargetEndpoint, hasReportableStateProperties()).WillRepeatedly(Return(false)); + EXPECT_CALL(*providerForTargetEndpoint, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capabilityForTarget, providerForTargetEndpoint); // Capability that belongs to another endpoint. auto providerForOtherEndpoint = std::make_shared>(); auto capabilityForOther = CapabilityTag("OtherNamespace", "OtherName", "OtherEndpointId"); EXPECT_CALL(*providerForOtherEndpoint, hasReportableStateProperties()).WillRepeatedly(Return(false)); + EXPECT_CALL(*providerForOtherEndpoint, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capabilityForOther, providerForOtherEndpoint); utils::WaitEvent provideStateEvent; @@ -387,6 +390,7 @@ TEST_F(ContextManagerTest, test_getContextWhenStateAndCacheAreUnavailableShouldF auto provider = std::make_shared(); auto capability = CapabilityTag("Namespace", "Name", "EndpointId"); EXPECT_CALL(*provider, hasReportableStateProperties()).WillRepeatedly(Return(false)); + EXPECT_CALL(*provider, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capability, provider); utils::WaitEvent provideStateEvent; @@ -418,6 +422,7 @@ TEST_F(ContextManagerTest, test_getContextWhenStateUnavailableShouldReturnCache) auto capability = CapabilityTag("Namespace", "Name", "EndpointId"); CapabilityState state{R"({"state":"target"})"}; EXPECT_CALL(*provider, hasReportableStateProperties()).WillRepeatedly(Return(false)); + EXPECT_CALL(*provider, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capability, provider); // Set value in the cache. @@ -481,6 +486,7 @@ TEST_F(ContextManagerTest, test_getContextInParallelShouldSucceed) { auto capabilityForEndpoint1 = CapabilityTag("Namespace", "Name", "EndpointId1"); CapabilityState stateForEndpoint1{R"({"state":1})"}; EXPECT_CALL(*providerForEndpoint1, hasReportableStateProperties()).WillRepeatedly(Return(false)); + EXPECT_CALL(*providerForEndpoint1, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capabilityForEndpoint1, providerForEndpoint1); // Capability that belongs to the second endpoint. @@ -488,6 +494,7 @@ TEST_F(ContextManagerTest, test_getContextInParallelShouldSucceed) { auto capabilityForEndpoint2 = CapabilityTag("Namespace", "Name", "EndpointId2"); CapabilityState stateForEndpoint2{R"({"state":2})"}; EXPECT_CALL(*providerForEndpoint2, hasReportableStateProperties()).WillRepeatedly(Return(false)); + EXPECT_CALL(*providerForEndpoint2, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capabilityForEndpoint2, providerForEndpoint2); // Expect both provide state calls @@ -546,6 +553,7 @@ TEST_F(ContextManagerTest, test_getContextWithoutReportableStateProperties) { auto capability1 = CapabilityTag("Namespace", "Name1", ""); CapabilityState state1{R"({"state1":"target1"})"}; EXPECT_CALL(*providerWithReportableStateProperties, hasReportableStateProperties()).WillRepeatedly(Return(true)); + EXPECT_CALL(*providerWithReportableStateProperties, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capability1, providerWithReportableStateProperties); auto providerWithoutReportableStateProperties = std::make_shared(); @@ -553,6 +561,7 @@ TEST_F(ContextManagerTest, test_getContextWithoutReportableStateProperties) { CapabilityState state2{R"({"state2":"target2"})"}; EXPECT_CALL(*providerWithoutReportableStateProperties, hasReportableStateProperties()) .WillRepeatedly(Return(false)); + EXPECT_CALL(*providerWithoutReportableStateProperties, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capability2, providerWithoutReportableStateProperties); EXPECT_CALL(*providerWithReportableStateProperties, provideState(_, _)).Times(0); @@ -589,6 +598,7 @@ TEST_F(ContextManagerTest, test_getContextWithReportableStateProperties) { auto capability1 = CapabilityTag("Namespace", "Name1", ""); CapabilityState state1{R"({"state1":"target1"})"}; EXPECT_CALL(*providerWithReportableStateProperties, hasReportableStateProperties()).WillRepeatedly(Return(true)); + EXPECT_CALL(*providerWithReportableStateProperties, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capability1, providerWithReportableStateProperties); auto providerWithoutReportableStateProperties = std::make_shared(); @@ -596,6 +606,7 @@ TEST_F(ContextManagerTest, test_getContextWithReportableStateProperties) { CapabilityState state2{R"({"state2":"target2"})"}; EXPECT_CALL(*providerWithoutReportableStateProperties, hasReportableStateProperties()) .WillRepeatedly(Return(false)); + EXPECT_CALL(*providerWithoutReportableStateProperties, shouldQueryState()).WillRepeatedly(Return(true)); m_contextManager->setStateProvider(capability2, providerWithoutReportableStateProperties); utils::WaitEvent provideStateEvent1; @@ -630,6 +641,33 @@ TEST_F(ContextManagerTest, test_getContextWithReportableStateProperties) { EXPECT_EQ(states[capability1].valuePayload, state1.valuePayload); } +/// Test that requester will get cached value when provider set shouldQueryState to false. +TEST_F(ContextManagerTest, test_getContextShouldReturnCache) { + auto provider = std::make_shared(); + auto capability = CapabilityTag("Namespace", "Name", "EndpointId"); + CapabilityState state{R"({"state":"target"})"}; + EXPECT_CALL(*provider, shouldQueryState()).WillRepeatedly(Return(false)); + m_contextManager->setStateProvider(capability, provider); + + // Set value in the cache. + m_contextManager->reportStateChange(capability, state, AlexaStateChangeCauseType::APP_INTERACTION); + // will not query + EXPECT_CALL(*provider, provideState(_, _)).Times(0); + + // Get context. + auto requester = std::make_shared(); + m_contextManager->getContext(requester, capability.endpointId); + + std::promise contextStatesPromise; + EXPECT_CALL(*requester, onContextAvailable(_, _, _)) + .WillOnce(WithArg<1>(Invoke([&contextStatesPromise](const AVSContext& context) { + contextStatesPromise.set_value(context.getStates()); + }))); + + auto statesFuture = contextStatesPromise.get_future(); + EXPECT_EQ(statesFuture.get()[capability].valuePayload, state.valuePayload); +} + } // namespace test } // namespace contextManager } // namespace alexaClientSDK diff --git a/Diagnostics/include/Diagnostics/DevicePropertyAggregator.h b/Diagnostics/include/Diagnostics/DevicePropertyAggregator.h index 0426e15424..875b2d38a0 100644 --- a/Diagnostics/include/Diagnostics/DevicePropertyAggregator.h +++ b/Diagnostics/include/Diagnostics/DevicePropertyAggregator.h @@ -55,11 +55,7 @@ class DevicePropertyAggregator /// @name AlertObserverInterface Functions /// @{ - void onAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - AlertObserverInterface::State state, - const std::string& reason = "") override; + void onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) override; /// @} /// @name AuthObserverInterface Functions diff --git a/Diagnostics/src/CMakeLists.txt b/Diagnostics/src/CMakeLists.txt index f16fb2521f..1c47e45510 100644 --- a/Diagnostics/src/CMakeLists.txt +++ b/Diagnostics/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=diagnostics") -add_library(Diagnostics SHARED +add_library(Diagnostics DevicePropertyAggregator.cpp DiagnosticsUtils.cpp DeviceProtocolTracer.cpp diff --git a/Diagnostics/src/DevicePropertyAggregator.cpp b/Diagnostics/src/DevicePropertyAggregator.cpp index f3895779f0..f6a2001092 100644 --- a/Diagnostics/src/DevicePropertyAggregator.cpp +++ b/Diagnostics/src/DevicePropertyAggregator.cpp @@ -258,15 +258,11 @@ Optional DevicePropertyAggregator::getDeviceContextJson() { return m_deviceContext; } -void DevicePropertyAggregator::onAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - AlertObserverInterface::State state, - const std::string& reason) { +void DevicePropertyAggregator::onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, alertType, state]() { + m_executor.submit([this, alertInfo]() { std::stringstream ss; - ss << alertType << ":" << state; + ss << alertInfo.type << ":" << alertInfo.state; m_asyncPropertyMap[DevicePropertyAggregatorInterface::ALERT_TYPE_AND_STATE] = ss.str(); }); } diff --git a/Diagnostics/test/DevicePropertyAggregatorTest.cpp b/Diagnostics/test/DevicePropertyAggregatorTest.cpp index 096f564b7f..806a570843 100644 --- a/Diagnostics/test/DevicePropertyAggregatorTest.cpp +++ b/Diagnostics/test/DevicePropertyAggregatorTest.cpp @@ -375,10 +375,16 @@ TEST_F(DevicePropertyAggregatorTest, test_getTTSPlayerStateProperty) { * Test if the Alarm status gets updated if the observer method is called. */ TEST_F(DevicePropertyAggregatorTest, test_getAlarmStatusProperty) { - m_devicePropertyAggregator->onAlertStateChange( - "TEST_TOKEN", "TEST_ALERT_TYPE", AlertObserverInterface::State::STARTED, "TEST_ALERT_REASON"); - - ASSERT_TRUE(validatePropertyValue(DevicePropertyAggregator::ALERT_TYPE_AND_STATE, "TEST_ALERT_TYPE:STARTED")); + m_devicePropertyAggregator->onAlertStateChange(AlertObserverInterface::AlertInfo( + "TEST_TOKEN", + AlertObserverInterface::Type::ALARM, + AlertObserverInterface::State::STARTED, + std::chrono::system_clock::now(), + avsCommon::utils::Optional(), + avsCommon::utils::Optional(), + "TEST_ALERT_REASON")); + + ASSERT_TRUE(validatePropertyValue(DevicePropertyAggregator::ALERT_TYPE_AND_STATE, "ALARM:STARTED")); } /** diff --git a/Endpoints/include/Endpoints/DefaultEndpointBuilder.h b/Endpoints/include/Endpoints/DefaultEndpointBuilder.h index be67cc1e3a..6c57aea123 100644 --- a/Endpoints/include/Endpoints/DefaultEndpointBuilder.h +++ b/Endpoints/include/Endpoints/DefaultEndpointBuilder.h @@ -97,6 +97,7 @@ class DefaultEndpointBuilder : public avsCommon::sdkInterfaces::endpoints::Endpo /// @name @c EndpointBuilderInterface methods. /// @{ DefaultEndpointBuilder& withDerivedEndpointId(const std::string& suffix) override; + DefaultEndpointBuilder& withDeviceRegistration() override; DefaultEndpointBuilder& withEndpointId(const EndpointIdentifier& endpointId) override; DefaultEndpointBuilder& withFriendlyName(const std::string& friendlyName) override; DefaultEndpointBuilder& withDescription(const std::string& description) override; diff --git a/Endpoints/include/Endpoints/EndpointBuilder.h b/Endpoints/include/Endpoints/EndpointBuilder.h index 5ae5fda2d9..6eb57a10dd 100644 --- a/Endpoints/include/Endpoints/EndpointBuilder.h +++ b/Endpoints/include/Endpoints/EndpointBuilder.h @@ -99,6 +99,7 @@ class EndpointBuilder : public avsCommon::sdkInterfaces::endpoints::EndpointBuil /// @name @c EndpointBuilderInterface methods. /// @{ EndpointBuilder& withDerivedEndpointId(const std::string& suffix) override; + EndpointBuilder& withDeviceRegistration() override; EndpointBuilder& withEndpointId(const EndpointIdentifier& endpointId) override; EndpointBuilder& withFriendlyName(const std::string& friendlyName) override; EndpointBuilder& withDescription(const std::string& description) override; diff --git a/Endpoints/src/CMakeLists.txt b/Endpoints/src/CMakeLists.txt index 98dfbeaaa1..5cfa947338 100644 --- a/Endpoints/src/CMakeLists.txt +++ b/Endpoints/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=endpoints") -add_library(Endpoints SHARED +add_library(Endpoints Endpoint.cpp EndpointAttributeValidation.cpp EndpointBuilder.cpp diff --git a/Endpoints/src/DefaultEndpointBuilder.cpp b/Endpoints/src/DefaultEndpointBuilder.cpp index fce95e970c..7f56656529 100644 --- a/Endpoints/src/DefaultEndpointBuilder.cpp +++ b/Endpoints/src/DefaultEndpointBuilder.cpp @@ -83,6 +83,12 @@ DefaultEndpointBuilder& DefaultEndpointBuilder::withDerivedEndpointId(const std: return *this; } +DefaultEndpointBuilder& DefaultEndpointBuilder::withDeviceRegistration() { + ACSDK_DEBUG5(LX(__func__)); + m_builder->withDeviceRegistration(); + return *this; +} + DefaultEndpointBuilder& DefaultEndpointBuilder::withEndpointId(const EndpointIdentifier& endpointId) { ACSDK_DEBUG5(LX(__func__)); m_builder->withEndpointId(endpointId); diff --git a/Endpoints/src/Endpoint.cpp b/Endpoints/src/Endpoint.cpp index 5d91e3635e..79a896e6ba 100644 --- a/Endpoints/src/Endpoint.cpp +++ b/Endpoints/src/Endpoint.cpp @@ -103,7 +103,9 @@ bool Endpoint::update(const std::shared_ptr& endpointM for (const auto& capabilityConfiguration : updatedConfigurations) { auto capabilities = getCapabilities(); for (const auto& currentCapability : capabilities) { - if (currentCapability.first.interfaceName.compare(capabilityConfiguration.interfaceName) == 0) { + if (currentCapability.first.interfaceName.compare(capabilityConfiguration.interfaceName) == 0 && + currentCapability.first.instanceName.valueOr("").compare( + capabilityConfiguration.instanceName.valueOr("")) == 0) { auto handler = currentCapability.second; if (!removeCapability(currentCapability.first)) { return false; diff --git a/Endpoints/src/EndpointBuilder.cpp b/Endpoints/src/EndpointBuilder.cpp index 9d8bfc0aad..ca2e6e29b2 100644 --- a/Endpoints/src/EndpointBuilder.cpp +++ b/Endpoints/src/EndpointBuilder.cpp @@ -154,6 +154,21 @@ EndpointBuilder& EndpointBuilder::withDerivedEndpointId(const std::string& suffi return *this; } +EndpointBuilder& EndpointBuilder::withDeviceRegistration() { + if (m_isConfigurationFinalized) { + ACSDK_ERROR(LX(std::string(__func__) + "Failed").d("reason", "operationNotAllowed")); + return *this; + } + + m_attributes.registration.set(EndpointAttributes::Registration( + m_deviceInfo->getProductId(), + m_deviceInfo->getDeviceSerialNumber(), + m_deviceInfo->getRegistrationKey(), + m_deviceInfo->getProductIdKey())); + + return *this; +} + EndpointBuilder& EndpointBuilder::withEndpointId(const EndpointIdentifier& endpointId) { if (m_isConfigurationFinalized) { ACSDK_ERROR(LX(std::string(__func__) + "Failed").d("reason", "operationNotAllowed")); @@ -574,6 +589,7 @@ std::unique_ptr EndpointBuilder::buildImplementation() { .sensitive("endpointId", m_attributes.endpointId) .sensitive("friendlyName", m_attributes.friendlyName)); + m_hasBeenBuilt = true; return std::move(endpoint); } diff --git a/Endpoints/test/CMakeLists.txt b/Endpoints/test/CMakeLists.txt index eedfe5b8a2..5b296c18ad 100644 --- a/Endpoints/test/CMakeLists.txt +++ b/Endpoints/test/CMakeLists.txt @@ -5,7 +5,8 @@ set(INCLUDE_PATH $ $ $ + $ $ $) -discover_unit_tests("${INCLUDE_PATH}" Endpoints) +discover_unit_tests("${INCLUDE_PATH}" "AlexaCATestUtils;UtilsCommonTestLib;Endpoints") diff --git a/Endpoints/test/DefaultEndpointBuilderTest.cpp b/Endpoints/test/DefaultEndpointBuilderTest.cpp new file mode 100644 index 0000000000..c3e31f2479 --- /dev/null +++ b/Endpoints/test/DefaultEndpointBuilderTest.cpp @@ -0,0 +1,295 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 DefaultEndpointBuilderTest.cpp + +#include + +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace endpoints { +namespace test { + +static const std::shared_ptr VALID_CAPABILITY_CONFIGURATION = + std::make_shared("TEST_TYPE", "TEST_INTERFACE_NAME", "2.0"); +static const std::unordered_set> + VALID_CAPABILITY_CONFIGURATION_SET{VALID_CAPABILITY_CONFIGURATION}; + +using namespace acsdkManufactory; +using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::endpoints; +using namespace avsCommon::sdkInterfaces::test; +using namespace ::testing; + +/// Test harness for @c EndpointBuilder class. +class DefaultEndpointBuilderTest : public Test { +public: + /// Setup the test harness for running the test. + void SetUp() override; + + /// Clean up the test harness after running the test. + void TearDown() override; + + /// Create a DefaultEndpointBuilder. + Annotated createDefaultEndpointBuilder(); + + /// Test instance of device info. + std::shared_ptr m_deviceInfo; + /// Mock of @c ContextManagerInterface. + std::shared_ptr m_mockContextManager; + /// Mock of @c ExceptionEncounteredSenderInterface. + std::shared_ptr m_mockExceptionSender; + /// Mock of @c AlexaInterfaceMessageSenderInternalInterface. + std::shared_ptr m_mockResponseSender; + /// Mock of @c CapabilityConfigurationInterface. + std::shared_ptr m_mockCapabilityConfigurationInterface; + /// Mock of @c DirectiveHandlerInterface. + std::shared_ptr m_mockDirectiveHandler; +}; + +void DefaultEndpointBuilderTest::SetUp() { + m_deviceInfo = avsCommon::utils::DeviceInfo::create( + "TEST_CLIENT_ID", "TEST_PRODUCT_ID", "1234", "TEST_MANUFACTURER_NAME", "TEST_DESCRIPTION"); + m_mockContextManager = std::make_shared(); + m_mockExceptionSender = std::make_shared(); + m_mockResponseSender = std::make_shared(); + m_mockCapabilityConfigurationInterface = std::make_shared(); + m_mockDirectiveHandler = std::make_shared(); +} + +void DefaultEndpointBuilderTest::TearDown() { +} + +Annotated DefaultEndpointBuilderTest:: + createDefaultEndpointBuilder() { + return DefaultEndpointBuilder::createDefaultEndpointBuilderInterface( + m_deviceInfo, m_mockContextManager, m_mockExceptionSender, m_mockResponseSender); +} + +/** + * Tests @c createDefaultEndpointBuilderInterface with valid parameters, expecting to successfully configure and create + * default endpoint. + */ +TEST_F(DefaultEndpointBuilderTest, test_createDefaultEndpointBuilderInterface) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + ASSERT_NE(defaultEndpointBuilder->build(), nullptr); +} + +/** + * Tests @c createDefaultEndpointBuilderInterface with null parameters, expecting to return @c nullptr. + */ +TEST_F(DefaultEndpointBuilderTest, test_createDefaultEndpointBuilderInterfaceInvalidBuilder) { + auto defaultEndpointBuilder = + DefaultEndpointBuilder::createDefaultEndpointBuilderInterface(nullptr, nullptr, nullptr, nullptr); + ASSERT_EQ(defaultEndpointBuilder.get(), nullptr); +} + +/** + * Tests that endpoint id, friendly name, description, manufacturer name, and additional attributes cannot be updated + * after a default endpoint builder is created. + * + * @note This test also tests @c withDerivedEndpointId, @c withEndpointId, @c withFriendlyName, @c withDescription, + * @c withManufacturerName, and @c withAdditionalAttributes. + */ +TEST_F(DefaultEndpointBuilderTest, test_cannotUpdateDefaultAttributes) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + defaultEndpointBuilder->withDerivedEndpointId("TEST_DERIVED_ENDPOINT_ID") + .withEndpointId("TEST_ENDPOINT_ID") + .withFriendlyName("TEST_FRIENDLY_NAME") + .withDescription("TEST_DESCRIPTION_OVERWRITE") + .withManufacturerName("TEST_MANUFACTURER_NAME_OVERWRITE") + .withAdditionalAttributes( + "TEST_MANUFACTURER_NAME", + "TEST_MODEL", + "TEST_SERIAL_NUMBER", + "TEST_FIRMWARE_VERSION", + "TEST_SOFTWARE_VERSION", + "TEST_CUSTOM_IDENTIFIER"); + + auto defaultEndpoint = defaultEndpointBuilder->build(); + auto derivedEndpointId = m_deviceInfo->getDefaultEndpointId() + "::" + "TEST_DERIVED_ENDPOINT_ID"; + ASSERT_NE(defaultEndpoint->getEndpointId(), derivedEndpointId); + ASSERT_NE(defaultEndpoint->getEndpointId(), "TEST_ENDPOINT_ID"); + ASSERT_TRUE(defaultEndpoint->getAttributes().friendlyName.empty()); + ASSERT_NE(defaultEndpoint->getAttributes().description, "TEST_DESCRIPTION_OVERWRITE"); + ASSERT_NE(defaultEndpoint->getAttributes().manufacturerName, "TEST_MANUFACTURER_NAME_OVERWRITE"); + ASSERT_FALSE(defaultEndpoint->getAttributes().additionalAttributes.hasValue()); +} + +/** + * Tests @c withDisplayCategory with valid parameters, expecting that display category is successfully updated. + */ +TEST_F(DefaultEndpointBuilderTest, test_withDisplayCategory) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + defaultEndpointBuilder->withDisplayCategory({"TEST_DISPLAY_CATEGORY"}); + + auto defaultEndpoint = defaultEndpointBuilder->build(); + ASSERT_FALSE(defaultEndpoint->getAttributes().displayCategories.empty()); +} + +/** + * Tests @c withConnections with valid parameters, expecting that connections are updated successfully. + */ +TEST_F(DefaultEndpointBuilderTest, test_withConnections) { + std::map connection; + connection.insert({"testKey", "textValue"}); + + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + defaultEndpointBuilder->withConnections({connection}); + auto defaultEndpoint = defaultEndpointBuilder->build(); + ASSERT_FALSE(defaultEndpoint->getAttributes().connections.empty()); +} + +/** + * Tests @c withCookies with valid parameters, expecting that cookies are updated successfully. + */ +TEST_F(DefaultEndpointBuilderTest, test_withCookies) { + std::map cookie; + cookie.insert({"testKey", "testValue"}); + + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + defaultEndpointBuilder->withCookies(cookie); + auto defaultEndpoint = defaultEndpointBuilder->build(); + ASSERT_FALSE(defaultEndpoint->getAttributes().cookies.empty()); +} + +#ifndef POWER_CONTROLLER +/** + * Tests @c withPowerController without being enabled, expecting @c build to fail and to return @c nullptr. + * + * @note This function is deprecated use the new @c withEndpointCapabilitiesBuilder() method instead. + */ +TEST_F(DefaultEndpointBuilderTest, test_withPowerControllerNotEnabled) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + auto powerController = std::shared_ptr(); + defaultEndpointBuilder->withPowerController(powerController, true, true); + ASSERT_EQ(defaultEndpointBuilder->build(), nullptr); +} +#endif + +#ifndef TOGGLE_CONTROLLER +/** + * Tests @c withToggleController without being enabled, expecting @c build to fail and to return @c nullptr. + * + * @note This function is deprecated use the new @c withEndpointCapabilitiesBuilder() method instead. + */ +TEST_F(DefaultEndpointBuilderTest, test_withToggleControllerNotEnabled) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + auto toggleController = std::shared_ptr(); + toggleController::ToggleControllerAttributes toggleControllerAttributes; + defaultEndpointBuilder->withToggleController( + toggleController, "TEST_INSTANCE", toggleControllerAttributes, true, true, false); + ASSERT_EQ(defaultEndpointBuilder->build(), nullptr); +} +#endif + +#ifndef MODE_CONTROLLER +/** + * Tests @c withModeController without being enabled, expecting @c build to fail and to return @c nullptr. + * + * @note This function is deprecated use the new @c withEndpointCapabilitiesBuilder() method instead. + */ +TEST_F(DefaultEndpointBuilderTest, test_withModeControllerNotEnabled) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + auto modeController = std::shared_ptr(); + modeController::ModeControllerAttributes modeControllerAttributes; + defaultEndpointBuilder->withModeController( + modeController, "TEST_INSTANCE", modeControllerAttributes, true, true, false); + ASSERT_EQ(defaultEndpointBuilder->build(), nullptr); +} +#endif + +#ifndef RANGE_CONTROLLER +/** + * Tests @c withRangeController without being enabled, expecting @c build to fail and to return @c nullptr. + * + * @note This function is deprecated use the new @c withEndpointCapabilitiesBuilder() method instead. + */ +TEST_F(DefaultEndpointBuilderTest, test_withRangeControllerNotEnabled) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + auto rangeController = std::shared_ptr(); + rangeController::RangeControllerAttributes rangeControllerAttributes; + defaultEndpointBuilder->withRangeController( + rangeController, "TEST_INSTANCE", rangeControllerAttributes, true, true, false); + ASSERT_EQ(defaultEndpointBuilder->build(), nullptr); +} +#endif + +/** + * Tests @c withCapability with valid parameters, expecting capabilities to be updated successfully. + */ +TEST_F(DefaultEndpointBuilderTest, test_withCapability) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + defaultEndpointBuilder->withCapability(*VALID_CAPABILITY_CONFIGURATION, m_mockDirectiveHandler); + auto defaultEndpoint = defaultEndpointBuilder->build(); + ASSERT_FALSE(defaultEndpoint->getCapabilities().empty()); +} + +/** + * Tests @c withCapability with valid parameters, expecting capabilities to be updated successfully. + */ +TEST_F(DefaultEndpointBuilderTest, test_withCapabilityInterface) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + EXPECT_CALL(*m_mockCapabilityConfigurationInterface, getCapabilityConfigurations()) + .WillOnce(Return(VALID_CAPABILITY_CONFIGURATION_SET)); + defaultEndpointBuilder->withCapability(m_mockCapabilityConfigurationInterface, m_mockDirectiveHandler); + + auto defaultEndpoint = defaultEndpointBuilder->build(); + ASSERT_FALSE(defaultEndpoint->getCapabilities().empty()); +} + +/** + * Tests @c withCapabilityConfiguration with valid parameters, expecting capabilities to be updated successfully. + */ +TEST_F(DefaultEndpointBuilderTest, test_withCapabilityConfiguration) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + EXPECT_CALL(*m_mockCapabilityConfigurationInterface, getCapabilityConfigurations()) + .WillOnce(Return(VALID_CAPABILITY_CONFIGURATION_SET)); + defaultEndpointBuilder->withCapabilityConfiguration(m_mockCapabilityConfigurationInterface); + + auto defaultEndpoint = defaultEndpointBuilder->build(); + ASSERT_FALSE(defaultEndpoint->getCapabilityConfigurations().empty()); +} + +/** + * Tests @c build with minimal required valid parameters, expecting to successfully build a default endpoint. + */ +TEST_F(DefaultEndpointBuilderTest, test_buildSuccess) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + ASSERT_NE(defaultEndpointBuilder->build(), nullptr); +} + +/** + * Tests @c build with an already built default endpoint, expecting the build to fail, since only one default endpoint + * can be built from a single default endpoint builder. Expecting the second call to create a duplicate default endpoint + * to return @c nullptr. + */ +TEST_F(DefaultEndpointBuilderTest, test_buildDuplicate) { + auto defaultEndpointBuilder = createDefaultEndpointBuilder(); + auto defaultEndpoint = defaultEndpointBuilder->build(); + ASSERT_NE(defaultEndpoint, nullptr); + ASSERT_EQ(defaultEndpointBuilder->build(), nullptr); +} + +} // namespace test +} // namespace endpoints +} // namespace alexaClientSDK \ No newline at end of file diff --git a/Endpoints/test/EndpointBuilderTest.cpp b/Endpoints/test/EndpointBuilderTest.cpp new file mode 100644 index 0000000000..c1c4834963 --- /dev/null +++ b/Endpoints/test/EndpointBuilderTest.cpp @@ -0,0 +1,497 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 EndpointBuilderTest.cpp + +#include + +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace endpoints { +namespace test { + +const static std::string EMPTY_STRING = ""; +static const std::shared_ptr VALID_CAPABILITY_CONFIGURATION = + std::make_shared("TEST_TYPE", "TEST_INTERFACE_NAME", "2.0"); +static const std::unordered_set> + VALID_CAPABILITY_CONFIGURATION_SET{VALID_CAPABILITY_CONFIGURATION}; +static const std::unordered_set> + INVALID_CAPABILITY_CONFIGURATION_SET{nullptr}; + +using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::test; +using namespace ::testing; + +/// Test harness for @c EndpointBuilder class. +class EndpointBuilderTest : public Test { +public: + /// Setup the test harness for running the test. + void SetUp() override; + + /// Clean up the test harness after running the test. + void TearDown() override; + + /// Setup used for most but not all test cases. + EndpointBuilder createEndpointBuilder(); + + /// Test instance of device info. + std::shared_ptr m_deviceInfo; + /// Mock of @c ContextManagerInterface. + std::shared_ptr m_mockContextManager; + /// Mock of @c ExceptionEncounteredSenderInterface. + std::shared_ptr m_mockExceptionSender; + /// Mock of @c AlexaInterfaceMessageSenderInternalInterface. + std::shared_ptr m_mockResponseSender; + /// Mock of @c CapabilityConfigurationInterface. + std::shared_ptr m_mockCapabilityConfigurationInterface; + /// Mock of @c DirectiveHandlerInterface. + std::shared_ptr m_mockDirectiveHandler; +}; + +void EndpointBuilderTest::SetUp() { + m_deviceInfo = avsCommon::utils::DeviceInfo::create( + "TEST_CLIENT_ID", "TEST_PRODUCT_ID", "1234", "TEST_MANUFACTURER_NAME", "TEST_DESCRIPTION"); + m_mockContextManager = std::make_shared(); + m_mockExceptionSender = std::make_shared(); + m_mockResponseSender = std::make_shared(); + m_mockCapabilityConfigurationInterface = std::make_shared(); + m_mockDirectiveHandler = std::make_shared(); +} + +void EndpointBuilderTest::TearDown() { +} + +EndpointBuilder EndpointBuilderTest::createEndpointBuilder() { + auto endpointBuilder = + EndpointBuilder::create(m_deviceInfo, m_mockContextManager, m_mockExceptionSender, m_mockResponseSender); + /// EndpointBuilder requires friendly name to successfully build non-default endpoints + return endpointBuilder->withFriendlyName("TEST_FRIENDLY_NAME"); +} + +/** + * Tests @c create with null device info, expecting @c create to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_createEndpointBuilderNullDeviceInfo) { + ASSERT_EQ( + EndpointBuilder::create(nullptr, m_mockContextManager, m_mockExceptionSender, m_mockResponseSender), nullptr); +} + +/** + * Tests @c create with null context manager, expecting @c create to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_createEndpointBuilderNullContextManager) { + ASSERT_EQ(EndpointBuilder::create(m_deviceInfo, nullptr, m_mockExceptionSender, m_mockResponseSender), nullptr); +} + +/** + * Tests @c create with null exception sender, expecting @c create to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_createEndpointBuilderNullExceptionSender) { + ASSERT_EQ(EndpointBuilder::create(m_deviceInfo, m_mockContextManager, nullptr, m_mockResponseSender), nullptr); +} + +/** + * Tests @c create with null response sender, expecting @c create to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_createEndpointBuilderNullAlexaMessageSender) { + ASSERT_EQ(EndpointBuilder::create(m_deviceInfo, m_mockContextManager, m_mockExceptionSender, nullptr), nullptr); +} + +/** + * Tests @c create with correct parameters, expecting to return @c EndpointBuilder. + */ +TEST_F(EndpointBuilderTest, test_createEndpointBuilderSuccess) { + ASSERT_NE( + EndpointBuilder::create(m_deviceInfo, m_mockContextManager, m_mockExceptionSender, m_mockResponseSender), + nullptr); +} + +/** + * Tests @c create with correct parameters (using non-shared ptr deviceInfo), expecting to return @c EndpointBuilder. + */ +TEST_F(EndpointBuilderTest, test_createEndpointBuilderWithoutPointerSuccess) { + ASSERT_NE( + EndpointBuilder::create(*m_deviceInfo, m_mockContextManager, m_mockExceptionSender, m_mockResponseSender), + nullptr); +} + +/** + * Tests @c finalizeAttributes, expecting that each attribute remains unchanged. + * + * @note This test also tests @c withDerivedEndpointId, @c withEndpointId, @c withFriendlyName, @c withDescription, + * @c withManufacturerName, @c withDisplayCategory, and @c withAdditionalAttributes. + */ +TEST_F(EndpointBuilderTest, test_withConfigurationFinalized) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.finalizeAttributes(); + endpointBuilder.withDerivedEndpointId("TEST_DERIVED_ENDPOINT_ID") + .withEndpointId("TEST_ENDPOINT_ID") + .withFriendlyName("TEST_FRIENDLY_NAME_OVERWRITE") + .withDescription("TEST_DESCRIPTION") + .withManufacturerName("TEST_MANUFACTURER_NAME") + .withDisplayCategory({"TEST_DISPLAY_CATEGORY"}) + .withAdditionalAttributes( + "TEST_MANUFACTURER_NAME", + "TEST_MODEL", + "TEST_SERIAL_NUMBER", + "TEST_FIRMWARE_VERSION", + "TEST_SOFTWARE_VERSION", + "TEST_CUSTOM_IDENTIFIER"); + + auto endpoint = endpointBuilder.build(); + ASSERT_TRUE(endpoint->getEndpointId().empty()); + ASSERT_EQ(endpoint->getAttributes().friendlyName, "TEST_FRIENDLY_NAME"); + ASSERT_TRUE(endpoint->getAttributes().description.empty()); + ASSERT_TRUE(endpoint->getAttributes().manufacturerName.empty()); + ASSERT_TRUE(endpoint->getAttributes().displayCategories.empty()); + ASSERT_FALSE(endpoint->getAttributes().additionalAttributes.hasValue()); +} + +/** + * Tests @c withDerivedEndpointId with an invalid length, expecting endpoint id to remain unchanged. + */ +TEST_F(EndpointBuilderTest, test_withDerivedEndpointIdLengthExceeded) { + auto endpointBuilder = createEndpointBuilder(); + std::string invalid_length(300, 'c'); + endpointBuilder.withDerivedEndpointId(invalid_length); + auto endpoint = endpointBuilder.build(); + ASSERT_TRUE(endpoint->getEndpointId().empty()); +} + +/** + * Tests @c withDerivedEndpointId, expecting to update endpoint id successfully. + */ +TEST_F(EndpointBuilderTest, test_withDerivedEndpointIdSuccess) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withDerivedEndpointId("TEST_DERIVED_ENDPOINT_ID"); + auto endpoint = endpointBuilder.build(); + auto derivedEndpointId = m_deviceInfo->getDefaultEndpointId() + "::" + "TEST_DERIVED_ENDPOINT_ID"; + ASSERT_EQ(endpoint->getEndpointId(), derivedEndpointId); +} + +/** + * Tests that each attribute can be successfully updated when provided with valid input. + * + * @note This test also tests @c withEndpointId, @c withFriendlyName, @c withDescription, @c withManufacturerName, + * @c withDisplayCategory, @c withAdditionalAttributes, @c withConnections, and @c withCookies. + * @c withDerivedEndpointId, is tested separately so that it does not interfere with @c withEndpointId. + */ +TEST_F(EndpointBuilderTest, test_attributesUpdatedSuccess) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withEndpointId("TEST_ENDPOINT_ID") + .withFriendlyName("TEST_FRIENDLY_NAME") + .withFriendlyName("TEST_FRIENDLY_NAME_OVERWRITE") + .withDescription("TEST_DESCRIPTION") + .withManufacturerName("TEST_MANUFACTURER_NAME") + .withDisplayCategory({"TEST_DISPLAY_CATEGORY"}) + .withAdditionalAttributes( + "TEST_MANUFACTURER_NAME", + "TEST_MODEL", + "TEST_SERIAL_NUMBER", + "TEST_FIRMWARE_VERSION", + "TEST_SOFTWARE_VERSION", + "TEST_CUSTOM_IDENTIFIER"); + + std::map validConnection; + validConnection.insert({"testKey", "testValue"}); + endpointBuilder.withConnections({validConnection}); + + std::map validCookie; + validCookie.insert({"testKey", "testValue"}); + endpointBuilder.withCookies(validCookie); + + auto endpoint = endpointBuilder.build(); + ASSERT_EQ(endpoint->getEndpointId(), "TEST_ENDPOINT_ID"); + ASSERT_EQ(endpoint->getAttributes().friendlyName, "TEST_FRIENDLY_NAME_OVERWRITE"); + ASSERT_EQ(endpoint->getAttributes().description, "TEST_DESCRIPTION"); + ASSERT_EQ(endpoint->getAttributes().manufacturerName, "TEST_MANUFACTURER_NAME"); + ASSERT_FALSE(endpoint->getAttributes().displayCategories.empty()); + ASSERT_EQ(endpoint->getAttributes().displayCategories.front(), "TEST_DISPLAY_CATEGORY"); + ASSERT_TRUE(endpoint->getAttributes().additionalAttributes.hasValue()); + ASSERT_EQ(endpoint->getAttributes().additionalAttributes.value().manufacturer, "TEST_MANUFACTURER_NAME"); + ASSERT_EQ(endpoint->getAttributes().additionalAttributes.value().model, "TEST_MODEL"); + ASSERT_EQ(endpoint->getAttributes().additionalAttributes.value().serialNumber, "TEST_SERIAL_NUMBER"); + ASSERT_EQ(endpoint->getAttributes().additionalAttributes.value().firmwareVersion, "TEST_FIRMWARE_VERSION"); + ASSERT_EQ(endpoint->getAttributes().additionalAttributes.value().softwareVersion, "TEST_SOFTWARE_VERSION"); + ASSERT_EQ(endpoint->getAttributes().additionalAttributes.value().customIdentifier, "TEST_CUSTOM_IDENTIFIER"); + ASSERT_FALSE(endpoint->getAttributes().connections.empty()); + ASSERT_EQ(endpoint->getAttributes().connections.front(), validConnection); + ASSERT_FALSE(endpoint->getAttributes().cookies.empty()); + ASSERT_EQ(endpoint->getAttributes().cookies, validCookie); +} + +/** + * Tests @c withEndpointId with an invalid length, expecting @c build to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_withEndpointIdInvalid) { + auto endpointBuilder = createEndpointBuilder(); + std::string invalid_length(300, 'c'); + endpointBuilder.withEndpointId(invalid_length); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withFriendlyName with an invalid friendly name, expecting @c build to fail and to return @c nullptr. + * + * @note This test specifically does not use createEndpointBuilder. + */ +TEST_F(EndpointBuilderTest, test_withFriendlyNameInvalid) { + auto endpointBuilder = + EndpointBuilder::create(m_deviceInfo, m_mockContextManager, m_mockExceptionSender, m_mockResponseSender); + endpointBuilder->withFriendlyName(EMPTY_STRING); + ASSERT_EQ(endpointBuilder->build(), nullptr); +} + +/** + * Tests @c withDescription with an invalid description, expecting @c build to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_withDescriptionInvalid) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withDescription(EMPTY_STRING); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withManufacturerName with an invalid manufacturer name, expecting @c build to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_withManufacturerNameInvalid) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withManufacturerName(EMPTY_STRING); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withDisplayCategory with an invalid display category, expecting @c build to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_withDisplayCategoryInvalid) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withDisplayCategory({}); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withAdditionalAttributes with invalid additional attributes, expecting @c build to fail and to return @c + * nullptr. + */ +TEST_F(EndpointBuilderTest, test_withAdditionalAttributesInvalid) { + auto endpointBuilder = createEndpointBuilder(); + std::string invalid_length(300, 'c'); + endpointBuilder.withAdditionalAttributes( + invalid_length, + "TEST_MODEL", + "TEST_SERIAL_NUMBER", + "TEST_FIRMWARE_VERSION", + "TEST_SOFTWARE_VERSION", + "TEST_CUSTOM_IDENTIFIER"); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withConnections with an invalid connection (ex: every connection key requires a value), expecting @c build + * to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_withConnectionsInvalid) { + auto endpointBuilder = createEndpointBuilder(); + std::map invalidConnection; + invalidConnection.insert({"testKey", EMPTY_STRING}); + endpointBuilder.withConnections({invalidConnection}); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withCookies with invalid cookies, expecting @c build to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_withCookiesInvalid) { + auto endpointBuilder = createEndpointBuilder(); + std::map invalidCookie; + std::string invalid_length(6000, 'c'); + invalidCookie.insert({invalid_length, invalid_length}); + endpointBuilder.withCookies(invalidCookie); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +#ifndef POWER_CONTROLLER +/** + * Tests @c withPowerController without being enabled, expecting @c build to fail and to return @c nullptr. + * + * @note This function is deprecated use the new @c withEndpointCapabilitiesBuilder() method instead. + */ +TEST_F(EndpointBuilderTest, test_withPowerControllerNotEnabled) { + auto endpointBuilder = createEndpointBuilder(); + auto powerController = std::shared_ptr(); + endpointBuilder.withPowerController(powerController, true, true); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} +#endif + +#ifndef TOGGLE_CONTROLLER +/** + * Tests @c withToggleController without being enabled, expecting @c build to fail and to return @c nullptr. + * + * @note This function is deprecated use the new @c withEndpointCapabilitiesBuilder() method instead. + */ +TEST_F(EndpointBuilderTest, test_withToggleControllerNotEnabled) { + auto endpointBuilder = createEndpointBuilder(); + auto toggleController = std::shared_ptr(); + toggleController::ToggleControllerAttributes toggleControllerAttributes; + endpointBuilder.withToggleController( + toggleController, "TEST_INSTANCE", toggleControllerAttributes, true, true, false); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} +#endif + +#ifndef MODE_CONTROLLER +/** + * Tests @c withModeController without being enabled, expecting @c build to fail and to return @c nullptr. + * + * @note This function is deprecated use the new @c withEndpointCapabilitiesBuilder() method instead. + */ +TEST_F(EndpointBuilderTest, test_withModeControllerNotEnabled) { + auto endpointBuilder = createEndpointBuilder(); + auto modeController = std::shared_ptr(); + modeController::ModeControllerAttributes modeControllerAttributes; + endpointBuilder.withModeController(modeController, "TEST_INSTANCE", modeControllerAttributes, true, true, false); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} +#endif + +#ifndef RANGE_CONTROLLER +/** + * Tests @c withRangeController without being enabled, expecting @c build to fail and to return @c nullptr. + * + * @note This function is deprecated use the new @c withEndpointCapabilitiesBuilder() method instead. + */ +TEST_F(EndpointBuilderTest, test_withRangeControllerNotEnabled) { + auto endpointBuilder = createEndpointBuilder(); + auto rangeController = std::shared_ptr(); + rangeController::RangeControllerAttributes rangeControllerAttributes; + endpointBuilder.withRangeController(rangeController, "TEST_INSTANCE", rangeControllerAttributes, true, true, false); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} +#endif + +/** + * Tests @c withCapability with a valid capability configuration, expecting to update capabilities successfully. + */ +TEST_F(EndpointBuilderTest, test_withCapabilitySuccess) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withCapability(*VALID_CAPABILITY_CONFIGURATION, m_mockDirectiveHandler); + auto endpoint = endpointBuilder.build(); + ASSERT_FALSE(endpoint->getCapabilities().empty()); +} + +/** + * Tests @c withCapability with a null configuration interface, expecting @c build to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_withCapabilityNullConfigurationInterface) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withCapability(nullptr, m_mockDirectiveHandler); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withCapability with a null directive handler, expecting @c build to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_withCapabilityNullDirectiveHandler) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withCapability(m_mockCapabilityConfigurationInterface, nullptr); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withCapability with an invalid capability configuration, expecting @c build to fail and to return @c + * nullptr. + */ +TEST_F(EndpointBuilderTest, test_withCapabilityInvalidConfiguration) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withCapability(m_mockCapabilityConfigurationInterface, m_mockDirectiveHandler); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withCapability with a valid capability configuration, expecting to update capabilities successfully. + */ +TEST_F(EndpointBuilderTest, test_withCapabilityUsingInterfaceSuccess) { + auto endpointBuilder = createEndpointBuilder(); + EXPECT_CALL(*m_mockCapabilityConfigurationInterface, getCapabilityConfigurations()) + .WillOnce(Return(VALID_CAPABILITY_CONFIGURATION_SET)); + endpointBuilder.withCapability(m_mockCapabilityConfigurationInterface, m_mockDirectiveHandler); + auto endpoint = endpointBuilder.build(); + ASSERT_FALSE(endpoint->getCapabilities().empty()); +} + +/** + * Tests @c withCapabilityConfiguration with a null configuration interface, expecting @c build to fail and to return @c + * nullptr. + */ +TEST_F(EndpointBuilderTest, test_withCapabilityConfigurationNullConfigurationInterface) { + auto endpointBuilder = createEndpointBuilder(); + endpointBuilder.withCapabilityConfiguration(nullptr); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withCapabilityConfiguration with a null configuration, expecting @c build to fail and to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_withCapabilityConfigurationNullConfiguration) { + auto endpointBuilder = createEndpointBuilder(); + EXPECT_CALL(*m_mockCapabilityConfigurationInterface, getCapabilityConfigurations()) + .WillOnce(Return(INVALID_CAPABILITY_CONFIGURATION_SET)); + endpointBuilder.withCapabilityConfiguration(m_mockCapabilityConfigurationInterface); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c withCapabilityConfiguration with a valid configuration, expecting to update capability configurations + * successfully. + */ +TEST_F(EndpointBuilderTest, test_withCapabilityConfigurationSuccess) { + auto endpointBuilder = createEndpointBuilder(); + EXPECT_CALL(*m_mockCapabilityConfigurationInterface, getCapabilityConfigurations()) + .WillOnce(Return(VALID_CAPABILITY_CONFIGURATION_SET)); + endpointBuilder.withCapabilityConfiguration(m_mockCapabilityConfigurationInterface); + auto endpoint = endpointBuilder.build(); + ASSERT_FALSE(endpoint->getCapabilityConfigurations().empty()); +} + +/** + * Tests @c build with minimal required valid parameters, expecting to successfully build an endpoint. + */ +TEST_F(EndpointBuilderTest, test_buildSuccess) { + auto endpointBuilder = createEndpointBuilder(); + ASSERT_NE(endpointBuilder.build(), nullptr); +} + +/** + * Tests @c build with an already built endpoint, expecting the build to fail, since only one endpoint can be built from + * a single endpoint builder. Expecting the second call to create a duplicate endpoint to return @c nullptr. + */ +TEST_F(EndpointBuilderTest, test_buildDuplicate) { + auto endpointBuilder = createEndpointBuilder(); + auto endpoint = endpointBuilder.build(); + ASSERT_NE(endpoint, nullptr); + ASSERT_EQ(endpointBuilder.build(), nullptr); +} + +} // namespace test +} // namespace endpoints +} // namespace alexaClientSDK \ No newline at end of file diff --git a/Endpoints/test/EndpointTest.cpp b/Endpoints/test/EndpointTest.cpp new file mode 100644 index 0000000000..c059d80495 --- /dev/null +++ b/Endpoints/test/EndpointTest.cpp @@ -0,0 +1,369 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 EndpointTest.cpp + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace endpoints { +namespace test { + +using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::endpoints; +using namespace avsCommon::sdkInterfaces::test; +using namespace avsCommon::utils; +using namespace avsCommon::utils::test; +using namespace ::testing; + +static const CapabilityConfiguration CAPABILITY_CONFIGURATION = + CapabilityConfiguration({"TEST_TYPE", "TEST_INTERFACE_NAME", "2.0"}); +static const std::string EMPTY_ID = ""; +static const std::string EMPTY_STRING = ""; + +/// Test harness for @c Endpoint class. +class EndpointTest : public Test { +public: + /// Setup the test harness for running the test. + void SetUp() override; + + /// Clean up the test harness after running the test. + void TearDown() override; + + /// Create valid attributes for testing. + AVSDiscoveryEndpointAttributes createValidAttributes(); + + /// Mock of @c DirectiveHandlerInterface. + std::shared_ptr m_mockDirectiveHandler; +}; + +void EndpointTest::SetUp() { + m_mockDirectiveHandler = std::make_shared(); +} + +void EndpointTest::TearDown() { +} + +AVSDiscoveryEndpointAttributes EndpointTest::createValidAttributes() { + AVSDiscoveryEndpointAttributes attributes; + attributes.endpointId = "TEST_ENDPOINT_ID"; + attributes.friendlyName = "TEST_FRIENDLY_NAME"; + attributes.description = "TEST_DESCRIPTION"; + attributes.manufacturerName = "TEST_MANUFACTURER_NAME"; + attributes.displayCategories = {"TEST_DISPLAY_CATEGORY"}; + return attributes; +} + +/** + * Tests @c Endpoint constructor, expecting to successfully create a new Endpoint. + */ +TEST_F(EndpointTest, test_endpointConstructor) { + auto attributes = createValidAttributes(); + auto endpoint = new Endpoint(attributes); + ASSERT_NE(endpoint, nullptr); +} + +/** + * Tests @c addRequireShutdownObjects, expecting to successfully update require shutdown objects without errors or + * crashing. Then expects that @c doShutdown is called from within the endpoint destructor to release resources. + */ +TEST_F(EndpointTest, test_addRequireShutdownObjects) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + std::shared_ptr mockRequiresShutdown = + std::make_shared("TEST_REQUIRE_SHUTDOWN_OBJECT"); + endpoint->addRequireShutdownObjects({mockRequiresShutdown}); + EXPECT_CALL(*mockRequiresShutdown, doShutdown()); +} + +/** + * Tests @c getEndpointId, expecting value to be correct. + */ +TEST_F(EndpointTest, test_getEndpointId) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_EQ(endpoint->getEndpointId(), "TEST_ENDPOINT_ID"); +} + +/** + * Tests @c update with an empty endpoint id, expecting @c update to fail and to return @c false. + */ +TEST_F(EndpointTest, test_updateWithValidAttributesAndInvalidEndpointId) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + attributes.endpointId = "TEST_ENDPOINT_ID_OVERWRITE"; + auto endpointModificationData = std::make_shared( + EndpointModificationData(EMPTY_ID, attributes, {CAPABILITY_CONFIGURATION}, {}, {}, {})); + ASSERT_FALSE(endpoint->update(endpointModificationData)); +} + +/** + * Tests @c update with invalid updated attributes (ex: friendly name being an empty string) and valid endpoint data, + * expecting @c update to fail and to return @c false. + */ +TEST_F(EndpointTest, test_updateWithInvalidAttributesAndValidEndpointAttributes) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + attributes.friendlyName = EMPTY_STRING; + auto endpointModificationData = std::make_shared( + EndpointModificationData("TEST_ENDPOINT_ID", attributes, {CAPABILITY_CONFIGURATION}, {}, {}, {})); + ASSERT_FALSE(endpoint->update(endpointModificationData)); +} + +/** + * Tests @c update with valid updated attributes and valid endpoint data, expecting @c update to succeed and to return + * @c true. + */ +TEST_F(EndpointTest, test_updateSuccess) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + auto endpointModificationData = std::make_shared( + EndpointModificationData("TEST_ENDPOINT_ID", attributes, {CAPABILITY_CONFIGURATION}, {}, {}, {})); + ASSERT_TRUE(endpoint->update(endpointModificationData)); +} + +/** + * Tests @c update by first adding a capability through @c addCapabilityConfiguration, then calling update with the same + * capability, expecting @c update to succeed by keeping one copy of the same capability and to return @c true. + * + * @note This test also tests @c addCapabilityConfiguration. + */ +TEST_F(EndpointTest, test_updateDuplicateCapabilities) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_TRUE(endpoint->getCapabilities().empty()); + endpoint->addCapabilityConfiguration(CAPABILITY_CONFIGURATION); + ASSERT_TRUE(endpoint->getCapabilities().size() == 1); + auto endpointModificationData = std::make_shared( + EndpointModificationData("TEST_ENDPOINT_ID", attributes, {CAPABILITY_CONFIGURATION}, {}, {}, {})); + ASSERT_TRUE(endpoint->update(endpointModificationData)); + ASSERT_TRUE(endpoint->getCapabilities().size() == 1); +} + +/** + * Tests @c update by first adding a capability through @c addCapabilityConfiguration, then calling update with the same + * capability, expecting @c update to succeed by still having the remaining copy in the currenCapabilities. + * Tests updates on both additions to ensure that order does not matter. + * Tests a remove to ensure that the correct instance is removed after update. + */ +TEST_F(EndpointTest, test_updateSameInterfaceDifferentInstances) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_TRUE(endpoint->getCapabilities().empty()); + + const auto instanceOne = avsCommon::utils::Optional("TV.1"); + const auto instanceTwo = avsCommon::utils::Optional("TV.2"); + const CapabilityConfiguration capabilityConfigurationOne = + CapabilityConfiguration({"TEST_TYPE", "TEST_INTERFACE_NAME", "2.0", instanceOne}); + const CapabilityConfiguration capabilityConfigurationTwo = + CapabilityConfiguration({"TEST_TYPE", "TEST_INTERFACE_NAME", "2.0", instanceTwo}); + ASSERT_EQ(capabilityConfigurationOne.instanceName.value(), "TV.1"); + ASSERT_EQ(capabilityConfigurationTwo.instanceName.value(), "TV.2"); + + endpoint->addCapabilityConfiguration(capabilityConfigurationOne); + ASSERT_TRUE(endpoint->getCapabilities().size() == 1); + endpoint->addCapabilityConfiguration(capabilityConfigurationTwo); + ASSERT_TRUE(endpoint->getCapabilities().size() == 2); + + auto endpointModificationDataOne = std::make_shared( + EndpointModificationData("TEST_ENDPOINT_ID", attributes, {capabilityConfigurationOne}, {}, {}, {})); + ASSERT_TRUE(endpoint->update(endpointModificationDataOne)); + ASSERT_TRUE(endpoint->getCapabilities().size() == 2); + + auto endpointModificationDataTwo = std::make_shared( + EndpointModificationData("TEST_ENDPOINT_ID", attributes, {capabilityConfigurationTwo}, {}, {}, {})); + ASSERT_TRUE(endpoint->update(endpointModificationDataTwo)); + ASSERT_TRUE(endpoint->getCapabilities().size() == 2); + + auto endpointModificationDataThree = std::make_shared( + EndpointModificationData("TEST_ENDPOINT_ID", attributes, {}, {}, {capabilityConfigurationTwo}, {})); + ASSERT_TRUE(endpoint->update(endpointModificationDataThree)); + ASSERT_TRUE(endpoint->getCapabilities().size() == 1); + ASSERT_TRUE(endpoint->getCapabilities().begin()->first.instanceName.value() == "TV.1"); +} + +/** + * Tests @c addCapability with a null directive handler, expecting @c addCapability to fail and to return @c false. + */ +TEST_F(EndpointTest, test_addCapabilityNullDirectiveHandler) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_FALSE(endpoint->addCapability(CAPABILITY_CONFIGURATION, nullptr)); +} + +/** + * Tests @c addCapability with a capability duplicate, expecting @c addCapability to return @c false, after attempting + * to add a duplicate. + */ +TEST_F(EndpointTest, test_addCapabilityDuplicate) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_TRUE(endpoint->getCapabilities().empty()); + ASSERT_TRUE(endpoint->addCapability(CAPABILITY_CONFIGURATION, m_mockDirectiveHandler)); + ASSERT_FALSE(endpoint->addCapability(CAPABILITY_CONFIGURATION, m_mockDirectiveHandler)); +} + +/** + * Tests @c addCapability with valid parameters, expecting @c addCapability to succeed and to return @c true. + */ +TEST_F(EndpointTest, test_addCapabilitySuccess) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_TRUE(endpoint->addCapability(CAPABILITY_CONFIGURATION, m_mockDirectiveHandler)); +} + +/** + * Tests @c removeCapability with a capability that does not exist, expecting @c removeCapability to fail and to return + * @c false. + */ +TEST_F(EndpointTest, test_removeCapabilityThatDoesNotExist) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_TRUE(endpoint->getCapabilities().empty()); + ASSERT_FALSE(endpoint->removeCapability(CAPABILITY_CONFIGURATION)); +} + +/** + * Tests @c removeCapability with a capability that does exist, expecting @c removeCapability to succeed and to return + * @c true. + * + * @note This test also tests @c addCapability. + */ +TEST_F(EndpointTest, test_removeCapabilitySuccess) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_TRUE(endpoint->addCapability(CAPABILITY_CONFIGURATION, m_mockDirectiveHandler)); + ASSERT_TRUE(endpoint->removeCapability(CAPABILITY_CONFIGURATION)); +} + +/** + * Tests @c addCapabilityConfiguration with a duplicate capability configuration. Specifically the first addition should + * succeed, but the second addition should fail, expecting @c removeCapability to return @c false, after attempting to + * add a duplicate. + */ +TEST_F(EndpointTest, test_addCapabilityConfigurationDuplicate) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_TRUE(endpoint->addCapabilityConfiguration(CAPABILITY_CONFIGURATION)); + ASSERT_FALSE(endpoint->addCapabilityConfiguration(CAPABILITY_CONFIGURATION)); +} + +/** + * Tests @c validateEndpointAttributes with an invalid/empty endpoint id, expecting @c validateEndpointAttributes to + * fail and to return @c false. + */ +TEST_F(EndpointTest, test_validateEndpointAttributesInvalidEndpointId) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + attributes.endpointId = EMPTY_STRING; + ASSERT_FALSE(endpoint->validateEndpointAttributes(attributes)); +} + +/** + * Tests @c validateEndpointAttributes with an invalid/empty friendly name, expecting @c validateEndpointAttributes to + * fail and to return @c false. + */ +TEST_F(EndpointTest, test_validateEndpointAttributesInvalidFriendlyName) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + attributes.friendlyName = EMPTY_STRING; + ASSERT_FALSE(endpoint->validateEndpointAttributes(attributes)); +} + +/** + * Tests @c validateEndpointAttributes with an invalid/empty description, expecting @c validateEndpointAttributes to + * fail and to return @c false. + */ +TEST_F(EndpointTest, test_validateEndpointAttributesInvalidDescription) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + attributes.description = EMPTY_STRING; + ASSERT_FALSE(endpoint->validateEndpointAttributes(attributes)); +} + +/** + * Tests @c validateEndpointAttributes with an invalid/empty manufacturer name, expecting @c validateEndpointAttributes + * to fail and to return @c false. + */ +TEST_F(EndpointTest, test_validateEndpointAttributesInvalidManufacturerName) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + attributes.manufacturerName = EMPTY_STRING; + ASSERT_FALSE(endpoint->validateEndpointAttributes(attributes)); +} + +/** + * Tests @c validateEndpointAttributes with invalid/oversized additional attributes, expecting @c + * validateEndpointAttributes to fail and to return @c false. + */ +TEST_F(EndpointTest, test_validateEndpointAttributesInvalidAdditionalAttributes) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + std::string invalidManufacturerNameLength(300, 'c'); + attributes.additionalAttributes = + Optional({invalidManufacturerNameLength, + "TEST_MODEL", + "TEST_SERIAL_NUMBER", + "TEST_FIRMWARE_VERSION", + "TEST_SOFTWARE_VERSION", + "TEST_CUSTOM_IDENTIFIER"}); + ASSERT_FALSE(endpoint->validateEndpointAttributes(attributes)); +} + +/** + * Tests @c validateEndpointAttributes with invalid connections, expecting @c validateEndpointAttributes to fail and to + * return @c false. + */ +TEST_F(EndpointTest, test_validateEndpointAttributesInvalidConnections) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + std::map invalidConnections; + invalidConnections.insert({"connectionKey", ""}); + attributes.connections = {invalidConnections}; + ASSERT_FALSE(endpoint->validateEndpointAttributes(attributes)); +} + +/** + * Tests @c validateEndpointAttributes with invalid/oversized cookies, expecting @c validateEndpointAttributes to fail + * and to return @c false. + */ +TEST_F(EndpointTest, test_validateEndpointAttributesInvalidCookies) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + std::map invalidCookies; + std::string invalidCookieLength(6000, 'c'); + invalidCookies.insert({"cookieKey", invalidCookieLength}); + attributes.cookies = invalidCookies; + ASSERT_FALSE(endpoint->validateEndpointAttributes(attributes)); +} + +/** + * Tests @c validateEndpointAttributes with valid parameters, expecting @c validateEndpointAttributes to succeed and to + * return @c true. + */ +TEST_F(EndpointTest, test_validateEndpointAttributesSuccess) { + auto attributes = createValidAttributes(); + auto endpoint = std::make_shared(attributes); + ASSERT_TRUE(endpoint->validateEndpointAttributes(attributes)); +} + +} // namespace test +} // namespace endpoints +} // namespace alexaClientSDK \ No newline at end of file diff --git a/Integration/AlexaClientSDKConfig.json b/Integration/AlexaClientSDKConfig.json index 0667c05425..da213f9dde 100644 --- a/Integration/AlexaClientSDKConfig.json +++ b/Integration/AlexaClientSDKConfig.json @@ -26,6 +26,17 @@ "lwaAuthorization":{ "databaseFilePath":"${SDK_LWA_AUTHORIZATION_ADAPTER_DATABASE_FILE_PATH}" }, + "speakerManagerCapabilityAgent":{ + "defaultSpeakerVolume":40, + "defaultAlertsVolume":40, + "restoreMuteState":true + }, + "pkcs11Module":{ + "libraryPath":"${SDK_PKCS11_MODULE_PATH}", + "tokenName":"${SDK_PKCS11_TOKEN_NAME}", + "userPin":"${SDK_PKCS11_USER_PIN}", + "defaultKeyName":"${SDK_PKCS11_KEY_NAME}" + }, "deviceSettings":{ "databaseFilePath":"${SDK_SQLITE_DEVICE_SETTINGS_DATABASE_FILE_PATH}", "locales":["en-US","en-GB","de-DE","en-IN","en-CA","ja-JP","en-AU","fr-FR","it-IT","es-ES","es-MX","fr-CA", @@ -64,6 +75,9 @@ "agentString": "CQCAFYNYDC" }, "sampleApp": { - "displayCardsSupported":true + "displayCardsSupported":true, + "sensory": { + "modelFilePath": "${SDK_SENSORY_MODEL_FILE_PATH}" + } } } diff --git a/Integration/include/Integration/TestAlertObserver.h b/Integration/include/Integration/TestAlertObserver.h index 2d722b9e90..b0f9c39d7a 100644 --- a/Integration/include/Integration/TestAlertObserver.h +++ b/Integration/include/Integration/TestAlertObserver.h @@ -30,11 +30,7 @@ namespace test { class TestAlertObserver : public acsdkAlertsInterfaces::AlertObserverInterface { public: - void onAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - State state, - const std::string& reason) override; + void onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) override; class changedAlert { public: diff --git a/Integration/src/AuthDelegateTestContext.cpp b/Integration/src/AuthDelegateTestContext.cpp index 66553c491a..662e36bea4 100644 --- a/Integration/src/AuthDelegateTestContext.cpp +++ b/Integration/src/AuthDelegateTestContext.cpp @@ -122,7 +122,7 @@ AuthDelegateTestContext::AuthDelegateTestContext(const std::string& filePath, co EXPECT_TRUE(m_authDelegate); #else - auto storage = SQLiteCBLAuthDelegateStorage::createCBLAuthDelegateStorageInterface(config); + auto storage = SQLiteCBLAuthDelegateStorage::createCBLAuthDelegateStorageInterface(config, nullptr, nullptr); EXPECT_TRUE(storage); if (!storage) { return; diff --git a/Integration/src/TestAlertObserver.cpp b/Integration/src/TestAlertObserver.cpp index feb87e96fb..82233cb52b 100644 --- a/Integration/src/TestAlertObserver.cpp +++ b/Integration/src/TestAlertObserver.cpp @@ -21,14 +21,10 @@ namespace alexaClientSDK { namespace integration { namespace test { -void TestAlertObserver::onAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - State state, - const std::string& reason) { +void TestAlertObserver::onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { std::unique_lock lock(m_mutex); TestAlertObserver::changedAlert ca; - ca.state = state; + ca.state = alertInfo.state; m_queue.push_back(ca); m_wakeTrigger.notify_all(); } diff --git a/Integration/test/AlertsIntegrationTest.cpp b/Integration/test/AlertsIntegrationTest.cpp index a27abd87a4..b9e276df52 100644 --- a/Integration/test/AlertsIntegrationTest.cpp +++ b/Integration/test/AlertsIntegrationTest.cpp @@ -439,7 +439,7 @@ class AlertsTest : public ::testing::Test { auto alertsAudioFactory = std::make_shared(); m_alertStorage = acsdkAlerts::storage::SQLiteAlertStorage::create( - avsCommon::utils::configuration::ConfigurationNode::getRoot(), alertsAudioFactory); + avsCommon::utils::configuration::ConfigurationNode::getRoot(), alertsAudioFactory, m_metricRecorder); m_alertObserver = std::make_shared(); diff --git a/Integration/test/AudioInputProcessorIntegrationTest.cpp b/Integration/test/AudioInputProcessorIntegrationTest.cpp index c2cbf1f4fd..d1743ae9f5 100644 --- a/Integration/test/AudioInputProcessorIntegrationTest.cpp +++ b/Integration/test/AudioInputProcessorIntegrationTest.cpp @@ -252,11 +252,7 @@ class wakeWordTrigger : public KeyWordObserverInterface { std::shared_ptr> KWDMetadata = nullptr) { keyWordDetected = true; ASSERT_NE(nullptr, stream); - bool alwaysReadable = true; - bool canOverride = false; - bool canBeOverridden = true; - auto audioProvider = AudioProvider( - stream, m_compatibleAudioFormat, ASRProfile::NEAR_FIELD, alwaysReadable, !canOverride, canBeOverridden); + auto audioProvider = AudioProvider::WakeAudioProvider(stream, m_compatibleAudioFormat); if (m_aip) { AudioInputStream::Index aipBegin = AudioInputProcessor::INVALID_INDEX; @@ -439,23 +435,10 @@ class AudioInputProcessorTest : public ::testing::Test { ASSERT_NE(nullptr, m_AudioBufferWriter); // Set up tap and hold to talk buttons. - bool alwaysReadable = true; - bool canOverride = true; - bool canBeOverridden = true; - m_HoldToTalkAudioProvider = std::make_shared( - m_AudioBuffer, - m_compatibleAudioFormat, - ASRProfile::CLOSE_TALK, - !alwaysReadable, - canOverride, - !canBeOverridden); - m_TapToTalkAudioProvider = std::make_shared( - m_AudioBuffer, - m_compatibleAudioFormat, - ASRProfile::NEAR_FIELD, - alwaysReadable, - canOverride, - !canBeOverridden); + m_HoldToTalkAudioProvider = + std::make_shared(AudioProvider::HoldAudioProvider(m_AudioBuffer, m_compatibleAudioFormat)); + m_TapToTalkAudioProvider = + std::make_shared(AudioProvider::TapAudioProvider(m_AudioBuffer, m_compatibleAudioFormat)); m_tapToTalkButton = std::make_shared(); m_holdToTalkButton = std::make_shared(); @@ -1033,12 +1016,8 @@ TEST_F(AudioInputProcessorTest, DISABLED_test_tapToTalkTimeOpus) { m_compatibleAudioFormat.endianness = COMPATIBLE_ENDIANNESS; m_compatibleAudioFormat.encoding = avsCommon::utils::AudioFormat::Encoding::OPUS; - bool alwaysReadable = true; - bool canOverride = true; - bool canBeOverridden = true; - std::shared_ptr tapToTalkAudioProvider; - tapToTalkAudioProvider = std::make_shared( - m_AudioBuffer, m_compatibleAudioFormat, ASRProfile::NEAR_FIELD, alwaysReadable, canOverride, !canBeOverridden); + std::shared_ptr tapToTalkAudioProvider = + std::make_shared(AudioProvider::TapAudioProvider(m_AudioBuffer, m_compatibleAudioFormat)); // Signal to the AIP to start recognizing. ASSERT_TRUE(m_tapToTalkButton->startRecognizing(m_AudioInputProcessor, tapToTalkAudioProvider)); diff --git a/Integration/test/AudioPlayerIntegrationTest.cpp b/Integration/test/AudioPlayerIntegrationTest.cpp index f0aad8f2af..bd3d00f86c 100644 --- a/Integration/test/AudioPlayerIntegrationTest.cpp +++ b/Integration/test/AudioPlayerIntegrationTest.cpp @@ -323,16 +323,8 @@ class AudioPlayerTest : public Test { ASSERT_NE(nullptr, m_AudioBufferWriter); // Set up hold to talk button. - bool alwaysReadable = true; - bool canOverride = true; - bool canBeOverridden = true; - m_HoldToTalkAudioProvider = std::make_shared( - m_AudioBuffer, - m_compatibleAudioFormat, - ASRProfile::CLOSE_TALK, - !alwaysReadable, - canOverride, - !canBeOverridden); + m_HoldToTalkAudioProvider = + std::make_shared(AudioProvider::HoldAudioProvider(m_AudioBuffer, m_compatibleAudioFormat)); m_holdToTalkButton = std::make_shared(); diff --git a/Integration/test/CMakeLists.txt b/Integration/test/CMakeLists.txt index c218526d95..81f7194963 100644 --- a/Integration/test/CMakeLists.txt +++ b/Integration/test/CMakeLists.txt @@ -30,7 +30,6 @@ set(LINK_PATH ACL Integration gtest gmock - KWD PlaybackController SpeechSynthesizer UtilsCommonTestLib @@ -38,6 +37,7 @@ set(LINK_PATH ACL acsdkAlerts acsdkAudioPlayer acsdkInteractionModel + acsdkKWDImplementations acsdkManufactory acsdkShutdownManager) diff --git a/InterruptModel/src/CMakeLists.txt b/InterruptModel/src/CMakeLists.txt index f383b8d389..c0284e0d65 100644 --- a/InterruptModel/src/CMakeLists.txt +++ b/InterruptModel/src/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_definitions("-DACSDK_LOG_MODULE=interruptModel") -add_library(InterruptModel SHARED +add_library(InterruptModel InterruptModel.cpp "${InterruptModel_SOURCE_DIR}/config/src/InterruptModelConfiguration.cpp") diff --git a/KWD/CMakeLists.txt b/KWD/CMakeLists.txt deleted file mode 100644 index d7ebe3b44d..0000000000 --- a/KWD/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -project(KWD LANGUAGES CXX) - -add_subdirectory("src") -add_subdirectory("test") - -if(AMAZON_KEY_WORD_DETECTOR) - add_subdirectory("Amazon") -endif() -if(AMAZONLITE_KEY_WORD_DETECTOR) - add_subdirectory("AmazonLite") -endif() -if(SENSORY_KEY_WORD_DETECTOR) - add_subdirectory("Sensory") -endif() - -if(KWD) - add_subdirectory("KWDProvider") -endif() diff --git a/KWD/KWDProvider/src/CMakeLists.txt b/KWD/KWDProvider/src/CMakeLists.txt deleted file mode 100644 index 7cdbd2ee97..0000000000 --- a/KWD/KWDProvider/src/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -add_library(KeywordDetectorProvider SHARED - KeywordDetectorProvider.cpp) - -target_include_directories(KeywordDetectorProvider PUBLIC - "${KeywordDetectorProvider_SOURCE_DIR}/include") - -target_link_libraries(KeywordDetectorProvider KWD AVSCommon) - -if(SENSORY_KEY_WORD_DETECTOR) - target_link_libraries(KeywordDetectorProvider SENSORY) -endif() - -# install target -asdk_install() \ No newline at end of file diff --git a/KWD/KWDProvider/src/KeywordDetectorProvider.cpp b/KWD/KWDProvider/src/KeywordDetectorProvider.cpp deleted file mode 100644 index 7ad92544ea..0000000000 --- a/KWD/KWDProvider/src/KeywordDetectorProvider.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "KWDProvider/KeywordDetectorProvider.h" - -#ifdef KWD_SENSORY -#include -#endif - -using namespace alexaClientSDK; -using namespace alexaClientSDK::kwd; - -std::unique_ptr KeywordDetectorProvider::create( - std::shared_ptr stream, - avsCommon::utils::AudioFormat audioFormat, - std::unordered_set> keyWordObservers, - std::unordered_set> - keyWordDetectorStateObservers, - const std::string& pathToInputFolder) { -#if defined(KWD_SENSORY) - return kwd::SensoryKeywordDetector::create( - stream, - audioFormat, - keyWordObservers, - keyWordDetectorStateObservers, - pathToInputFolder + "/spot-alexa-rpi-31000.snsr"); -#else - return nullptr; -#endif -} diff --git a/KWD/Sensory/src/CMakeLists.txt b/KWD/Sensory/src/CMakeLists.txt deleted file mode 100644 index d31f273a7d..0000000000 --- a/KWD/Sensory/src/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -add_definitions("-DACSDK_LOG_MODULE=sensoryKeywordDetector") -add_library(SENSORY SHARED - SensoryKeywordDetector.cpp) - -target_include_directories(SENSORY PUBLIC - "${SENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR}" - "${KWD_SOURCE_DIR}/include" - "${SENSORY_SOURCE_DIR}/include") - -target_link_libraries(SENSORY KWD AVSCommon "${SENSORY_KEY_WORD_DETECTOR_LIB_PATH}") - -# install target -asdk_install() \ No newline at end of file diff --git a/KWD/Sensory/test/CMakeLists.txt b/KWD/Sensory/test/CMakeLists.txt deleted file mode 100644 index 22aa0e0149..0000000000 --- a/KWD/Sensory/test/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -set(INCLUDES "${SENSORY_SOURCE_DIR}/include" "${KWD_SOURCE_DIR}/include" "${SENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR}") - -set(INPUTFOLDER "${KWD_SOURCE_DIR}/inputs") - -discover_unit_tests("${INCLUDES}" SENSORY "${INPUTFOLDER}") \ No newline at end of file diff --git a/KWD/src/CMakeLists.txt b/KWD/src/CMakeLists.txt deleted file mode 100644 index ff42adfb4f..0000000000 --- a/KWD/src/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -add_definitions("-DACSDK_LOG_MODULE=abstractKeywordDetector") -add_library(KWD SHARED - AbstractKeywordDetector.cpp) - -include_directories(KWD "${KWD_SOURCE_DIR}/include") -target_link_libraries(KWD AVSCommon) - -# install target -asdk_install() \ No newline at end of file diff --git a/KWD/test/AbstractKeywordDetectorTest.cpp b/KWD/test/AbstractKeywordDetectorTest.cpp deleted file mode 100644 index f7f42f341b..0000000000 --- a/KWD/test/AbstractKeywordDetectorTest.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "KWD/AbstractKeywordDetector.h" - -namespace alexaClientSDK { -namespace kwd { -namespace test { - -using ::testing::_; - -/// A test observer that mocks out the KeyWordObserverInterface##onKeyWordDetected() call. -class MockKeyWordObserver : public avsCommon::sdkInterfaces::KeyWordObserverInterface { -public: - MOCK_METHOD5( - onKeyWordDetected, - void( - std::shared_ptr stream, - std::string keyword, - avsCommon::avs::AudioInputStream::Index beginIndex, - avsCommon::avs::AudioInputStream::Index endIndex, - std::shared_ptr> KWDMetadata)); -}; - -/// A test observer that mocks out the KeyWordDetectorStateObserverInterface##onStateChanged() call. -class MockStateObserver : public avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface { -public: - MOCK_METHOD1( - onStateChanged, - void(avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState - keyWordDetectorState)); -}; - -/** - * A mock Keyword Detector that inherits from KeyWordDetector. - */ -class MockKeyWordDetector : public AbstractKeywordDetector { -public: - /** - * Notifies all KeyWordObservers with dummy values. - */ - void sendKeyWordCallToObservers() { - notifyKeyWordObservers(nullptr, "ALEXA", 0, 0); - }; - - /** - * Notifies all KeyWordDetectorStateObservers. - * - * @param state The state to notify observers of. - */ - void sendStateChangeCallObservers( - avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState state) { - notifyKeyWordDetectorStateObservers(state); - }; -}; - -class AbstractKeyWordDetectorTest : public ::testing::Test { -protected: - std::shared_ptr detector; - std::shared_ptr keyWordObserver1; - std::shared_ptr keyWordObserver2; - std::shared_ptr stateObserver1; - std::shared_ptr stateObserver2; - - virtual void SetUp() { - detector = std::make_shared(); - keyWordObserver1 = std::make_shared(); - keyWordObserver2 = std::make_shared(); - stateObserver1 = std::make_shared(); - stateObserver2 = std::make_shared(); - } -}; - -TEST_F(AbstractKeyWordDetectorTest, test_addKeyWordObserver) { - detector->addKeyWordObserver(keyWordObserver1); - - EXPECT_CALL(*keyWordObserver1, onKeyWordDetected(_, _, _, _, _)).Times(1); - detector->sendKeyWordCallToObservers(); -} - -TEST_F(AbstractKeyWordDetectorTest, test_addMultipleKeyWordObserver) { - detector->addKeyWordObserver(keyWordObserver1); - detector->addKeyWordObserver(keyWordObserver2); - - EXPECT_CALL(*keyWordObserver1, onKeyWordDetected(_, _, _, _, _)).Times(1); - EXPECT_CALL(*keyWordObserver2, onKeyWordDetected(_, _, _, _, _)).Times(1); - detector->sendKeyWordCallToObservers(); -} - -TEST_F(AbstractKeyWordDetectorTest, test_removeKeyWordObserver) { - detector->addKeyWordObserver(keyWordObserver1); - detector->addKeyWordObserver(keyWordObserver2); - - EXPECT_CALL(*keyWordObserver1, onKeyWordDetected(_, _, _, _, _)).Times(1); - EXPECT_CALL(*keyWordObserver2, onKeyWordDetected(_, _, _, _, _)).Times(1); - detector->sendKeyWordCallToObservers(); - - detector->removeKeyWordObserver(keyWordObserver1); - - EXPECT_CALL(*keyWordObserver1, onKeyWordDetected(_, _, _, _, _)).Times(0); - EXPECT_CALL(*keyWordObserver2, onKeyWordDetected(_, _, _, _, _)).Times(1); - detector->sendKeyWordCallToObservers(); -} - -TEST_F(AbstractKeyWordDetectorTest, test_addStateObserver) { - detector->addKeyWordDetectorStateObserver(stateObserver1); - - EXPECT_CALL(*stateObserver1, onStateChanged(_)).Times(1); - detector->sendStateChangeCallObservers( - avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); -} - -TEST_F(AbstractKeyWordDetectorTest, test_addMultipleStateObservers) { - detector->addKeyWordDetectorStateObserver(stateObserver1); - detector->addKeyWordDetectorStateObserver(stateObserver2); - - EXPECT_CALL(*stateObserver1, onStateChanged(_)).Times(1); - EXPECT_CALL(*stateObserver2, onStateChanged(_)).Times(1); - detector->sendStateChangeCallObservers( - avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); -} - -TEST_F(AbstractKeyWordDetectorTest, test_removeStateObserver) { - detector->addKeyWordDetectorStateObserver(stateObserver1); - detector->addKeyWordDetectorStateObserver(stateObserver2); - - EXPECT_CALL(*stateObserver1, onStateChanged(_)).Times(1); - EXPECT_CALL(*stateObserver2, onStateChanged(_)).Times(1); - detector->sendStateChangeCallObservers( - avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); - - detector->removeKeyWordDetectorStateObserver(stateObserver1); - - EXPECT_CALL(*stateObserver1, onStateChanged(_)).Times(0); - EXPECT_CALL(*stateObserver2, onStateChanged(_)).Times(1); - detector->sendStateChangeCallObservers( - avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED); -} - -TEST_F(AbstractKeyWordDetectorTest, test_observersDontGetNotifiedOfSameStateTwice) { - detector->addKeyWordDetectorStateObserver(stateObserver1); - - EXPECT_CALL(*stateObserver1, onStateChanged(_)).Times(1); - detector->sendStateChangeCallObservers( - avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); - - EXPECT_CALL(*stateObserver1, onStateChanged(_)).Times(0); - detector->sendStateChangeCallObservers( - avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); -} - -} // namespace test -} // namespace kwd -} // namespace alexaClientSDK diff --git a/KWD/test/CMakeLists.txt b/KWD/test/CMakeLists.txt deleted file mode 100644 index ae1f6a01ba..0000000000 --- a/KWD/test/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -discover_unit_tests("${KWD_SOURCE_DIR}/include" KWD) \ No newline at end of file diff --git a/MediaPlayer/AndroidSLESMediaPlayer/src/CMakeLists.txt b/MediaPlayer/AndroidSLESMediaPlayer/src/CMakeLists.txt index adf6099dba..46134ed8dd 100644 --- a/MediaPlayer/AndroidSLESMediaPlayer/src/CMakeLists.txt +++ b/MediaPlayer/AndroidSLESMediaPlayer/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=androidSLESMediaPlayer") -add_library(AndroidSLESMediaPlayer SHARED +add_library(AndroidSLESMediaPlayer AndroidSLESMediaQueue.cpp AndroidSLESMediaPlayer.cpp AndroidSLESSpeaker.cpp @@ -23,11 +23,12 @@ target_link_libraries(AndroidSLESMediaPlayer OpenSLES acsdkEqualizerImplementations # FFmpeg libraries - avcodec - avutil - avformat - avfilter - swresample) + ${FFMPEG_LIB_PATH}/libavcodec.so + ${FFMPEG_LIB_PATH}/libavutil.so + ${FFMPEG_LIB_PATH}/libavformat.so + ${FFMPEG_LIB_PATH}/libavfilter.so + ${FFMPEG_LIB_PATH}/libswresample.so + ) # install target asdk_install() diff --git a/MediaPlayer/GStreamerMediaPlayer/src/CMakeLists.txt b/MediaPlayer/GStreamerMediaPlayer/src/CMakeLists.txt index c8b92d587f..fa52798379 100644 --- a/MediaPlayer/GStreamerMediaPlayer/src/CMakeLists.txt +++ b/MediaPlayer/GStreamerMediaPlayer/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=mediaPlayer") -add_library(MediaPlayer SHARED +add_library(MediaPlayer AttachmentReaderSource.cpp BaseStreamSource.cpp ErrorTypeConversion.cpp diff --git a/Metrics/MetricRecorder/src/CMakeLists.txt b/Metrics/MetricRecorder/src/CMakeLists.txt index 3e00a3c269..68e1bfe4d7 100644 --- a/Metrics/MetricRecorder/src/CMakeLists.txt +++ b/Metrics/MetricRecorder/src/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(MetricRecorder SHARED MetricRecorder.cpp) +add_library(MetricRecorder MetricRecorder.cpp) target_include_directories(MetricRecorder PUBLIC "${MetricRecorder_SOURCE_DIR}/include" diff --git a/Metrics/SampleMetricSink/src/CMakeLists.txt b/Metrics/SampleMetricSink/src/CMakeLists.txt index 08fe042321..2ee010c050 100644 --- a/Metrics/SampleMetricSink/src/CMakeLists.txt +++ b/Metrics/SampleMetricSink/src/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(SampleMetricSink SHARED SampleMetricSink.cpp) +add_library(SampleMetricSink SampleMetricSink.cpp) target_include_directories(SampleMetricSink PUBLIC "${SampleMetricSink_SOURCE_DIR}/include" diff --git a/Metrics/UplCalculator/include/Metrics/MediaUplCalculator.h b/Metrics/UplCalculator/include/Metrics/MediaUplCalculator.h index 62ef897362..1ca4691723 100644 --- a/Metrics/UplCalculator/include/Metrics/MediaUplCalculator.h +++ b/Metrics/UplCalculator/include/Metrics/MediaUplCalculator.h @@ -130,7 +130,7 @@ class MediaUplCalculator : public avsCommon::utils::metrics::UplCalculatorInterf void inhibitSubmission(); /// MetricRecorder to publish UPL metrics. - std::shared_ptr m_metricRecorder; + std::weak_ptr m_metricRecorder; /// Time point for the start of the TTS when media plays after TTS UplTimePoint m_ttsStarted; @@ -146,4 +146,4 @@ class MediaUplCalculator : public avsCommon::utils::metrics::UplCalculatorInterf } // namespace metrics } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_METRICS_UPLCALCULATOR_INCLUDE_METRICS_MEDIAUPLCALCULATOR_H_ \ No newline at end of file +#endif // ALEXA_CLIENT_SDK_METRICS_UPLCALCULATOR_INCLUDE_METRICS_MEDIAUPLCALCULATOR_H_ diff --git a/Metrics/UplCalculator/include/Metrics/TtsUplCalculator.h b/Metrics/UplCalculator/include/Metrics/TtsUplCalculator.h index e9747b630e..75a92dd308 100644 --- a/Metrics/UplCalculator/include/Metrics/TtsUplCalculator.h +++ b/Metrics/UplCalculator/include/Metrics/TtsUplCalculator.h @@ -91,7 +91,7 @@ class TtsUplCalculator : public avsCommon::utils::metrics::UplCalculatorInterfac void inhibitSubmission(); /// MetricRecorder to publish UPL metrics. - std::shared_ptr m_metricRecorder; + std::weak_ptr m_metricRecorder; /// Stop UPL calculations for unwanted cases bool m_uplInhibited; @@ -101,4 +101,4 @@ class TtsUplCalculator : public avsCommon::utils::metrics::UplCalculatorInterfac } // namespace metrics } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_METRICS_UPLCALCULATOR_INCLUDE_METRICS_TTSUPLCALCULATOR_H_ \ No newline at end of file +#endif // ALEXA_CLIENT_SDK_METRICS_UPLCALCULATOR_INCLUDE_METRICS_TTSUPLCALCULATOR_H_ diff --git a/Metrics/UplCalculator/include/Metrics/UplMetricSink.h b/Metrics/UplCalculator/include/Metrics/UplMetricSink.h index 0577686ccd..04a02d3c7c 100644 --- a/Metrics/UplCalculator/include/Metrics/UplMetricSink.h +++ b/Metrics/UplCalculator/include/Metrics/UplMetricSink.h @@ -64,11 +64,11 @@ class UplMetricSink : public avsCommon::utils::metrics::MetricSinkInterface { uplCalculators; /// MetricRecorder to publish UPL metrics. - std::shared_ptr m_metricRecorder; + std::weak_ptr m_metricRecorder; }; } // namespace implementations } // namespace metrics } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_METRICS_UPLCALCULATOR_INCLUDE_METRICS_UPLMETRICSINK_H_ \ No newline at end of file +#endif // ALEXA_CLIENT_SDK_METRICS_UPLCALCULATOR_INCLUDE_METRICS_UPLMETRICSINK_H_ diff --git a/Metrics/UplCalculator/src/CMakeLists.txt b/Metrics/UplCalculator/src/CMakeLists.txt index 01ae3c72ce..d899b16dec 100644 --- a/Metrics/UplCalculator/src/CMakeLists.txt +++ b/Metrics/UplCalculator/src/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(UplCalculator SHARED +add_library(UplCalculator BaseUplCalculator.cpp MediaUplCalculator.cpp TtsUplCalculator.cpp diff --git a/Metrics/UplCalculator/src/MediaUplCalculator.cpp b/Metrics/UplCalculator/src/MediaUplCalculator.cpp index e0fb7a0bd4..1817b390c4 100644 --- a/Metrics/UplCalculator/src/MediaUplCalculator.cpp +++ b/Metrics/UplCalculator/src/MediaUplCalculator.cpp @@ -295,8 +295,12 @@ void MediaUplCalculator::calculateMediaUpl(MediaUplType type) { .setName(DIALOG_REQUEST_ID_TAG) .setValue(m_uplData->getStringData(DIALOG_REQUEST_ID_TAG)) .build()); - - m_metricRecorder->recordMetric(metricEventBuilder.build()); + auto metricRecorder = m_metricRecorder.lock(); + if (metricRecorder) { + metricRecorder->recordMetric(metricEventBuilder.build()); + } else { + ACSDK_ERROR(LX("calculateMediaUplFailed").d("reason", "nullMetricRecorder")); + } inhibitSubmission(); } @@ -306,4 +310,4 @@ void MediaUplCalculator::inhibitSubmission() { } // namespace implementations } // namespace metrics -} // namespace alexaClientSDK \ No newline at end of file +} // namespace alexaClientSDK diff --git a/Metrics/UplCalculator/src/TtsUplCalculator.cpp b/Metrics/UplCalculator/src/TtsUplCalculator.cpp index 7f12ff6bc3..97174c3cc8 100644 --- a/Metrics/UplCalculator/src/TtsUplCalculator.cpp +++ b/Metrics/UplCalculator/src/TtsUplCalculator.cpp @@ -174,8 +174,12 @@ void TtsUplCalculator::calculateTtsUpl() { .setName(DIALOG_REQUEST_ID_TAG) .setValue(m_uplData->getStringData(DIALOG_REQUEST_ID_TAG)) .build()); - - m_metricRecorder->recordMetric(metricEventBuilder.build()); + auto metricsRecorder = m_metricRecorder.lock(); + if (metricsRecorder) { + metricsRecorder->recordMetric(metricEventBuilder.build()); + } else { + ACSDK_ERROR(LX("calculateTtsUplFailed").d("reason", "nullMetricRecorder")); + } inhibitSubmission(); } @@ -185,4 +189,4 @@ void TtsUplCalculator::inhibitSubmission() { } // namespace implementations } // namespace metrics -} // namespace alexaClientSDK \ No newline at end of file +} // namespace alexaClientSDK diff --git a/Metrics/UplCalculator/src/UplMetricSink.cpp b/Metrics/UplCalculator/src/UplMetricSink.cpp index 00e2da68af..2193a6099e 100644 --- a/Metrics/UplCalculator/src/UplMetricSink.cpp +++ b/Metrics/UplCalculator/src/UplMetricSink.cpp @@ -68,13 +68,15 @@ void UplMetricSink::consumeMetric(std::shared_ptr uplData = - std::make_shared(); - for (auto& kv : uplCalculators) { - kv.second->setUplData(uplData); + auto metricRecorder = m_metricRecorder.lock(); + if (metricRecorder) { + uplCalculators[TTS_UPL_NAME] = TtsUplCalculator::createTtsUplCalculator(metricRecorder); + uplCalculators[MEDIA_UPL_NAME] = MediaUplCalculator::createMediaUplCalculator(metricRecorder); + std::shared_ptr uplData = + std::make_shared(); + for (auto& kv : uplCalculators) { + kv.second->setUplData(uplData); + } } } @@ -87,4 +89,4 @@ void UplMetricSink::consumeMetric(std::shared_ptr +#include #include +#include #include #include -#include namespace alexaClientSDK { namespace playlistParser { @@ -36,7 +37,7 @@ static const std::string TAG("PlaylistUtils"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -143,11 +144,9 @@ bool readFromContentFetcher( std::vector buffer(CHUNK_SIZE, 0); bool streamClosed = false; AttachmentReader::ReadStatus previousStatus = AttachmentReader::ReadStatus::OK_TIMEDOUT; - ssize_t bytesReadSoFar = 0; - size_t bytesRead = -1; + std::size_t bytesRead = static_cast(-1); while (!streamClosed && bytesRead != 0) { bytesRead = reader->read(buffer.data(), buffer.size(), &readStatus); - bytesReadSoFar += bytesRead; if (previousStatus != readStatus) { ACSDK_DEBUG9(LX(__func__).d("readStatus", readStatus)); previousStatus = readStatus; diff --git a/SampleApp/Authorization/CBLAuthDelegate/include/CBLAuthDelegate/SQLiteCBLAuthDelegateStorage.h b/SampleApp/Authorization/CBLAuthDelegate/include/CBLAuthDelegate/SQLiteCBLAuthDelegateStorage.h index 5764a58507..b851346be4 100644 --- a/SampleApp/Authorization/CBLAuthDelegate/include/CBLAuthDelegate/SQLiteCBLAuthDelegateStorage.h +++ b/SampleApp/Authorization/CBLAuthDelegate/include/CBLAuthDelegate/SQLiteCBLAuthDelegateStorage.h @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include "CBLAuthDelegate/CBLAuthDelegateStorageInterface.h" @@ -40,20 +42,15 @@ class SQLiteCBLAuthDelegateStorage : public CBLAuthDelegateStorageInterface { * Factory method for creating a storage object for CBLAuthDelegate based on an SQLite database. * * @param configurationRoot The global config object. - * @return Pointer to the SQLiteCBLAuthDelegate object, nullptr if there's an error creating it. - */ - static std::shared_ptr createCBLAuthDelegateStorageInterface( - const std::shared_ptr& configurationRoot); - - /** - * Factory method for creating a storage object for CBLAuthDelegate based on an SQLite database. + * @param cryptoFactory Crypto factory interface. + * @param keyStore Key store interface. * - * @deprecated - * @param configurationRoot The global config object. * @return Pointer to the SQLiteCBLAuthDelegate object, nullptr if there's an error creating it. */ - static std::shared_ptr create( - const avsCommon::utils::configuration::ConfigurationNode& configurationRoot); + static std::shared_ptr createCBLAuthDelegateStorageInterface( + const std::shared_ptr& configurationRoot, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore); /** * Destructor diff --git a/SampleApp/Authorization/CBLAuthDelegate/src/CMakeLists.txt b/SampleApp/Authorization/CBLAuthDelegate/src/CMakeLists.txt index d1c5451226..cb20a1c962 100644 --- a/SampleApp/Authorization/CBLAuthDelegate/src/CMakeLists.txt +++ b/SampleApp/Authorization/CBLAuthDelegate/src/CMakeLists.txt @@ -2,9 +2,19 @@ find_package(Threads ${THREADS_PACKAGE_CONFIG}) add_definitions("-DACSDK_LOG_MODULE=cblAuthDelegate") -message(WARNING "\nWARNING! Default SQLiteCBLAuthDelegateStorage is NOT encrypted. Your application's auth token storage MUST be encrypted for certification.\n") +if (CRYPTO_FOUND AND PKCS11) + message(STATUS + "\nIMPORTANT! Default SQLiteCBLAuthDelegateStorage uses encryption with hardware security module. " + "Check \"pkcs11Module\" section in configuration file.\n" + "Your application's auth token storage MUST be encrypted and protected by hardware security module for " + "certification.\n") +else() + message(WARNING + "\nWARNING! Default SQLiteCBLAuthDelegateStorage is NOT encrypted. Your application's auth token storage " + "MUST be encrypted and protected by hardware security module for certification.\n") +endif() -add_library(CBLAuthDelegate SHARED +add_library(CBLAuthDelegate CBLAuthDelegate.cpp SQLiteCBLAuthDelegateStorage.cpp) target_include_directories(CBLAuthDelegate PUBLIC diff --git a/SampleApp/Authorization/CBLAuthDelegate/src/SQLiteCBLAuthDelegateStorage.cpp b/SampleApp/Authorization/CBLAuthDelegate/src/SQLiteCBLAuthDelegateStorage.cpp index 89993c966e..f5d128f512 100644 --- a/SampleApp/Authorization/CBLAuthDelegate/src/SQLiteCBLAuthDelegateStorage.cpp +++ b/SampleApp/Authorization/CBLAuthDelegate/src/SQLiteCBLAuthDelegateStorage.cpp @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -#include +#include #include #include "CBLAuthDelegate/SQLiteCBLAuthDelegateStorage.h" @@ -40,14 +40,16 @@ static const std::string TAG("SQLiteCBLAuthDelegateStorage"); static const std::string CONFIG_KEY_CBL_AUTH_DELEGATE = "cblAuthDelegate"; std::shared_ptr SQLiteCBLAuthDelegateStorage::createCBLAuthDelegateStorageInterface( - const std::shared_ptr& configurationRootPtr) { - if (!configurationRootPtr) { + const std::shared_ptr& configurationRoot, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) { + if (!configurationRoot) { ACSDK_ERROR(LX("createCBLAuthDelegateStorageInterfaceFailed").d("reason", "nullConfigurationRoot")); return nullptr; } - auto lwaStorage = SQLiteLWAAuthorizationStorage::createLWAAuthorizationStorageInterface( - configurationRootPtr, CONFIG_KEY_CBL_AUTH_DELEGATE); + auto lwaStorage = LWAAuthorizationStorage::createLWAAuthorizationStorageInterface( + configurationRoot, CONFIG_KEY_CBL_AUTH_DELEGATE, cryptoFactory, keyStore); if (!lwaStorage) { ACSDK_ERROR(LX("createFailed").d("reason", "createLWAStorageFailed")); @@ -57,12 +59,6 @@ std::shared_ptr SQLiteCBLAuthDelegateStorage::c return std::shared_ptr(new SQLiteCBLAuthDelegateStorage(lwaStorage)); } -std::shared_ptr SQLiteCBLAuthDelegateStorage::create( - const avsCommon::utils::configuration::ConfigurationNode& configurationRoot) { - return createCBLAuthDelegateStorageInterface( - std::make_shared(configurationRoot)); -} - SQLiteCBLAuthDelegateStorage::~SQLiteCBLAuthDelegateStorage() { ACSDK_DEBUG5(LX("~SQLiteCBLAuthDelegateStorage")); m_lwaStorage.reset(); diff --git a/SampleApp/include/SampleApp/ExternalCapabilitiesBuilder.h b/SampleApp/include/SampleApp/ExternalCapabilitiesBuilder.h index 97cd5400d7..616631b918 100644 --- a/SampleApp/include/SampleApp/ExternalCapabilitiesBuilder.h +++ b/SampleApp/include/SampleApp/ExternalCapabilitiesBuilder.h @@ -86,7 +86,9 @@ class ExternalCapabilitiesBuilder : public alexaClientSDK::defaultClient::Extern #endif std::shared_ptr powerResourceManager, std::shared_ptr softwareComponentReporter, - std::shared_ptr playbackRouter) override; + std::shared_ptr playbackRouter, + std::shared_ptr + endpointRegistrationManager) override; /// @} private: diff --git a/SampleApp/include/SampleApp/InputControllerHandler.h b/SampleApp/include/SampleApp/InputControllerHandler.h new file mode 100644 index 0000000000..d4a8fecb8a --- /dev/null +++ b/SampleApp/include/SampleApp/InputControllerHandler.h @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_SAMPLEAPP_INCLUDE_SAMPLEAPP_INPUTCONTROLLERHANDLER_H_ +#define ALEXA_CLIENT_SDK_SAMPLEAPP_INCLUDE_SAMPLEAPP_INPUTCONTROLLERHANDLER_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace sampleApp { + +/** + * Sample implementation of an @c InputControllerHandlerInterface. + */ +class InputControllerHandler : public acsdkInputControllerInterfaces::InputControllerHandlerInterface { +public: + /** + * Create a InputControllerHandler object. + * + * @return A pointer to a new InputControllerHandler object if it succeeds; otherwise, @c nullptr. + */ + static std::shared_ptr create(); + + /// @name InputControllerHandlerInterface methods + /// @{ + virtual InputConfigurations getConfiguration() override; + virtual bool onInputChange(const std::string& input) override; + /// @} + +private: + /** + * Constructor. + */ + InputControllerHandler() = default; +}; + +} // namespace sampleApp +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_SAMPLEAPP_INCLUDE_SAMPLEAPP_INPUTCONTROLLERHANDLER_H_ diff --git a/SampleApp/include/SampleApp/KeywordObserver.h b/SampleApp/include/SampleApp/KeywordObserver.h index 7822de674b..d40eab82e3 100644 --- a/SampleApp/include/SampleApp/KeywordObserver.h +++ b/SampleApp/include/SampleApp/KeywordObserver.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,20 @@ namespace sampleApp { */ class KeywordObserver : public avsCommon::sdkInterfaces::KeyWordObserverInterface { public: + /** + * Creates a KeywordObserver and registers as an observer to a KeywordDetector. + * + * @param client The default SDK client. + * @param audioProvider The audio provider from which to stream audio data from. + * @param keywordDetector The @c AbstractKeywordDetector to self register to as an observer. + * + * @return a @c KeywordObserver. + */ + static std::shared_ptr create( + std::shared_ptr client, + capabilityAgents::aip::AudioProvider audioProvider, + std::shared_ptr keywordDetector); + /** * Constructor. * diff --git a/SampleApp/include/SampleApp/LocaleAssetsManager.h b/SampleApp/include/SampleApp/LocaleAssetsManager.h index a6c7c1a416..1cc57e0cc7 100644 --- a/SampleApp/include/SampleApp/LocaleAssetsManager.h +++ b/SampleApp/include/SampleApp/LocaleAssetsManager.h @@ -78,6 +78,7 @@ class LocaleAssetsManager std::set getSupportedLocales() const override; LocaleCombinations getSupportedLocaleCombinations() const override; Locale getDefaultLocale() const override; + Locales getDefaultLocales() const override; void addLocaleAssetsObserver( const std::shared_ptr& observer) override; @@ -130,6 +131,9 @@ class LocaleAssetsManager /// The default locale. Locale m_defaultLocale; + /// The default multilingual locale. + Locales m_defaultLocales; + /// Mutex to synchronize access to observers. mutable std::mutex m_observersMutex; diff --git a/SampleApp/include/SampleApp/SampleApplication.h b/SampleApp/include/SampleApp/SampleApplication.h index 1aac27075a..902907c6e2 100644 --- a/SampleApp/include/SampleApp/SampleApplication.h +++ b/SampleApp/include/SampleApp/SampleApplication.h @@ -24,6 +24,8 @@ #include #include +#include +#include #include #include #include @@ -46,7 +48,7 @@ #include "UserInputManager.h" #ifdef KWD -#include +#include #endif #ifdef GSTREAMER_MEDIA_PLAYER @@ -70,16 +72,14 @@ class SampleApplication { * * @param consoleReader The @c ConsoleReader to read inputs from console. * @param configFiles The vector of configuration files. - * @param pathToInputFolder The path to the inputs folder containing data files needed by this application. * @param logLevel The level of logging to enable. If this parameter is an empty string, the SDK's default * logging level will be used. - * @param An optional @c DiagnosticsInterface object to provide diagnostics on the SDK. + * @param diagnostics An optional @c DiagnosticsInterface object to provide diagnostics on the SDK. * @return A new @c SampleApplication, or @c nullptr if the operation failed. */ static std::unique_ptr create( std::shared_ptr consoleReader, const std::vector& configFiles, - const std::string& pathToInputFolder, const std::string& logLevel = "", std::shared_ptr diagnostics = nullptr); @@ -90,9 +90,27 @@ class SampleApplication { */ SampleAppReturnCode run(); +#ifdef DIAGNOSTICS + /** + * Initiates application stop for restart sequence. This method notifies event loop that the application + * should be terminated with subsequent restart, in other words, if the application is running, it should + * return SampleAppReturnCode::RESTART code. + * + * @return True if restart has been successfully initiated, false on error or if operation is not supported. + */ + bool initiateRestart(); +#endif + /// Destructor which manages the @c SampleApplication shutdown sequence. ~SampleApplication(); + /** + * Exposes the default client. + * + * @return Returns a reference to the @c DefaultClient. + */ + std::shared_ptr getDefaultClient(); + /** * Method to create mediaPlayers for the optional music provider adapters plugged into the SDK. * @@ -146,16 +164,14 @@ class SampleApplication { * * @param consoleReader The @c ConsoleReader to read inputs from console. * @param configFiles The vector of configuration files. - * @param pathToInputFolder The path to the inputs folder containing data files needed by this application. * @param logLevel The level of logging to enable. If this parameter is an empty string, the SDK's default * logging level will be used. - * @param An optional @c DiagnosticsInterface object to provide diagnostics on the SDK. + * @param diagnostics An optional @c DiagnosticsInterface object to provide diagnostics on the SDK. * @return @c true if initialization succeeded, else @c false. */ bool initialize( std::shared_ptr consoleReader, const std::vector& configFiles, - const std::string& pathToInputFolder, const std::string& logLevel, std::shared_ptr diagnostics); @@ -205,6 +221,9 @@ class SampleApplication { /// Object to manage lifecycle of Alexa Client SDK initialization. std::shared_ptr m_sdkInit; + /// The @c DefaultClient which "glues" together all other modules. + std::shared_ptr m_client; + /// The @c InteractionManager which perform user requests. std::shared_ptr m_interactionManager; @@ -268,7 +287,7 @@ class SampleApplication { #ifdef KWD /// The Wakeword Detector which can wake up the client using audio input. - std::unique_ptr m_keywordDetector; + std::shared_ptr m_keywordDetector; #endif #if defined(ANDROID_MEDIA_PLAYER) || defined(ANDROID_MICROPHONE) diff --git a/SampleApp/include/SampleApp/SampleApplicationComponent.h b/SampleApp/include/SampleApp/SampleApplicationComponent.h index e8a6275f84..8f1feac653 100644 --- a/SampleApp/include/SampleApp/SampleApplicationComponent.h +++ b/SampleApp/include/SampleApp/SampleApplicationComponent.h @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include #include @@ -57,7 +59,9 @@ using SampleApplicationComponent = acsdkManufactory::Component< std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr>; + std::shared_ptr, + std::shared_ptr, + std::shared_ptr>; /** * Get the manufactory @c Component for acsdkSampleApp. diff --git a/SampleApp/include/SampleApp/UIManager.h b/SampleApp/include/SampleApp/UIManager.h index fc7ccf748c..7fccf5e19e 100644 --- a/SampleApp/include/SampleApp/UIManager.h +++ b/SampleApp/include/SampleApp/UIManager.h @@ -66,7 +66,7 @@ class UIManager * * @param localeAssetsManager The @c LocaleAssetsManagerInterface that provides the supported locales. * @param deviceInfo Information about the device. For example, the default endpoint. - * @return a new instyance of UIManager. + * @return a new instance of UIManager. */ static std::shared_ptr create( const std::shared_ptr& localeAssetsManager, diff --git a/SampleApp/src/CMakeLists.txt b/SampleApp/src/CMakeLists.txt index fd7e48e64b..9c8705ae07 100644 --- a/SampleApp/src/CMakeLists.txt +++ b/SampleApp/src/CMakeLists.txt @@ -55,11 +55,15 @@ if (ENDPOINT_CONTROLLERS_MODE_CONTROLLER) list(APPEND LibSampleApp_SOURCES DefaultEndpoint/DefaultEndpointModeControllerHandler.cpp) endif() +if (INPUT_CONTROLLER) + list(APPEND LibSampleApp_SOURCES InputControllerHandler.cpp) +endif() + if(EXTERNAL_MEDIA_ADAPTERS) list(APPEND LibSampleApp_SOURCES ${ALL_EMP_ADAPTER_REGISTRATION_FILES}) endif () -add_library(LibSampleApp SHARED ${LibSampleApp_SOURCES}) +add_library(LibSampleApp ${LibSampleApp_SOURCES}) target_include_directories(LibSampleApp PUBLIC # This is relative to project(SampleApp). @@ -67,19 +71,22 @@ target_include_directories(LibSampleApp PUBLIC "${PORTAUDIO_INCLUDE_DIR}") target_link_libraries(LibSampleApp + acsdkAudioInputStream acsdkCore + acsdkCrypto + acsdkEqualizerImplementations + acsdkKWDImplementations acsdkManufactory - acsdkAudioInputStream - DefaultClient + acsdkPkcs11 AVSCommon AVSGatewayManager CapabilitiesDelegate CaptionsComponent CBLAuthDelegate + DefaultClient + InterruptModel SQLiteStorage SynchronizeStateSender - acsdkEqualizerImplementations - InterruptModel "${PORTAUDIO_LIB_PATH}") if (ACS_UTILS) @@ -112,6 +119,10 @@ if (COMMS) target_link_libraries(LibSampleApp CallManager) endif() +if (RTCSC) + target_link_libraries(LibSampleApp RtcscCapabilityAgent) +endif() + if (MRM AND MRM_STANDALONE_APP) target_link_libraries(LibSampleApp acsdkMultiRoomMusic MRMHandlerProxy) elseif (MRM) @@ -142,6 +153,14 @@ if (AUTH_MANAGER) target_link_libraries(LibSampleApp acsdkAuthorization) endif() +if (INPUT_CONTROLLER) + target_link_libraries(LibSampleApp acsdkInputController) +endif() + +if (CRYPTO_FOUND AND PKCS11) + target_compile_definitions(LibSampleApp PRIVATE ENABLE_PKCS11) +endif() + add_rpath_to_target("LibSampleApp") add_executable(SampleApp diff --git a/SampleApp/src/ExternalCapabilitiesBuilder.cpp b/SampleApp/src/ExternalCapabilitiesBuilder.cpp index 036cfb62ae..e6f716392f 100644 --- a/SampleApp/src/ExternalCapabilitiesBuilder.cpp +++ b/SampleApp/src/ExternalCapabilitiesBuilder.cpp @@ -40,6 +40,15 @@ #define COMMS_NAMESPACE "com.amazon.avs-comms-adapter" #endif +#if ENABLE_INPUT_CONTROLLER +#include +#include "SampleApp/InputControllerHandler.h" +#endif + +#ifdef ENABLE_RTCSC +#include +#endif + namespace alexaClientSDK { namespace sampleApp { @@ -124,7 +133,9 @@ ExternalCapabilitiesBuilder::buildCapabilities( #endif std::shared_ptr powerResourceManager, std::shared_ptr softwareComponentReporter, - std::shared_ptr playbackRouter) { + std::shared_ptr playbackRouter, + std::shared_ptr + endpointRegistrationManager) { ACSDK_DEBUG5(LX(__func__)); std::pair< std::list, @@ -250,6 +261,46 @@ ExternalCapabilitiesBuilder::buildCapabilities( #endif // // ENABLE_MRM +#if ENABLE_INPUT_CONTROLLER + auto inputControllerHandler = InputControllerHandler::create(); + if (!inputControllerHandler) { + ACSDK_CRITICAL(LX("Failed to create input controller handler!")); + return retValue; + } + auto inputController = acsdkInputController::create(inputControllerHandler, exceptionSender); + if (!inputController.hasValue()) { + ACSDK_CRITICAL(LX("Failed to create input controller capability agent!")); + return retValue; + } + + Capability inputControllerCapability; + auto inputControllerConfigurations = + inputController.value().capabilityConfigurationInterface->getCapabilityConfigurations(); + inputControllerCapability.directiveHandler = std::move(inputController.value().directiveHandler); + for (auto& configurationPtr : inputControllerConfigurations) { + inputControllerCapability.configuration = *configurationPtr; + capabilities.push_back(inputControllerCapability); + } +#endif + +#ifdef ENABLE_RTCSC + auto rtcscCapabilityAgent = capabilityAgents::rtcscCapabilityAgent::RtcscCapabilityAgent::create( + messageSender, contextManager, exceptionSender); + if (!rtcscCapabilityAgent) { + ACSDK_ERROR(LX(__func__).m("Unable to create RTCSCCapabilityAgent")); + return retValue; + } + + Capability rtcscCapability; + auto rtcscConfigurations = rtcscCapabilityAgent->getCapabilityConfigurations(); + rtcscCapability.directiveHandler = std::move(rtcscCapabilityAgent); + for (auto& configurationPtr : rtcscConfigurations) { + rtcscCapability.configuration = *configurationPtr; + capabilities.push_back(rtcscCapability); + } + requireShutdownObjects.push_back(rtcscCapabilityAgent); +#endif // ENABLE_RTCSC + retValue.first.swap(capabilities); retValue.second.swap(requireShutdownObjects); diff --git a/SampleApp/src/InputControllerHandler.cpp b/SampleApp/src/InputControllerHandler.cpp new file mode 100644 index 0000000000..1125752f0b --- /dev/null +++ b/SampleApp/src/InputControllerHandler.cpp @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "SampleApp/ConsolePrinter.h" +#include "SampleApp/InputControllerHandler.h" + +/// String to identify log entries originating from this file. +static const std::string TAG("InputControllerHandler"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) avsCommon::utils::logger::LogEntry(TAG, event) + +namespace alexaClientSDK { +namespace sampleApp { + +const static acsdkInputControllerInterfaces::InputControllerHandlerInterface::InputFriendlyNameType inputs{ + {"HDMI1", {"TV", "Television"}}, + {"PHONO", {"Turntable"}}, + {"AUX1", {"Cassette"}}}; + +std::shared_ptr InputControllerHandler::create() { + auto inputControllerHandler = std::shared_ptr(new InputControllerHandler()); + return inputControllerHandler; +} + +acsdkInputControllerInterfaces::InputControllerHandlerInterface::InputConfigurations InputControllerHandler:: + getConfiguration() { + return {inputs}; +} + +bool InputControllerHandler::onInputChange(const std::string& input) { + ConsolePrinter::prettyPrint({"InputController Input: " + input}); + return true; +} + +} // namespace sampleApp +} // namespace alexaClientSDK diff --git a/SampleApp/src/KeywordObserver.cpp b/SampleApp/src/KeywordObserver.cpp index 4234844544..b5ee46978a 100644 --- a/SampleApp/src/KeywordObserver.cpp +++ b/SampleApp/src/KeywordObserver.cpp @@ -31,6 +31,18 @@ static const std::string TAG("KeywordObserver"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) +std::shared_ptr KeywordObserver::create( + std::shared_ptr client, + capabilityAgents::aip::AudioProvider audioProvider, + std::shared_ptr keywordDetector) { + auto keywordObserver = std::make_shared(client, audioProvider); + if (keywordDetector) { + keywordDetector->addKeyWordObserver(keywordObserver); + } + + return keywordObserver; +} + KeywordObserver::KeywordObserver( std::shared_ptr client, capabilityAgents::aip::AudioProvider audioProvider) : diff --git a/SampleApp/src/LocaleAssetsManager.cpp b/SampleApp/src/LocaleAssetsManager.cpp index 8a2ffa7b4d..4b220c3540 100644 --- a/SampleApp/src/LocaleAssetsManager.cpp +++ b/SampleApp/src/LocaleAssetsManager.cpp @@ -36,6 +36,7 @@ namespace sampleApp { using namespace acsdkShutdownManagerInterfaces; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::configuration; +using namespace avsCommon::utils::json::jsonUtils; /// The key in our config file to find the root of settings for this database. static const std::string SETTING_CONFIGURATION_ROOT_KEY = "deviceSettings"; @@ -52,6 +53,9 @@ static const std::string LOCALE_COMBINATION_CONFIGURATION_KEY = "localeCombinati /// The default locale value used if the locale configuration is not present. static const std::string DEFAULT_LOCALE_VALUE = "en-US"; +/// The index for primary locale in the default locales vector. +static const int PRIMARY_LOCALE_INDEX = 0; + /// The default supported wake word. static const std::string DEFAULT_SUPPORTED_WAKEWORD = "ALEXA"; @@ -136,19 +140,10 @@ bool LocaleAssetsManager::initialize(bool enableWakeWord, const ConfigurationNod return false; } - if (!settingsConfig.getString(DEFAULT_LOCALE_CONFIGURATION_KEY, &m_defaultLocale)) { - ACSDK_ERROR( - LX("initializeFailed") - .d("reason", "configurationKeyNotFound") - .d("configurationKey", SETTING_CONFIGURATION_ROOT_KEY + "." + DEFAULT_LOCALE_CONFIGURATION_KEY)); - return false; - } - auto combinationArray = settingsConfig.getArray(LOCALE_COMBINATION_CONFIGURATION_KEY); if (combinationArray) { for (std::size_t arrayIndex = 0; arrayIndex < combinationArray.getArraySize(); ++arrayIndex) { - auto stringVector = avsCommon::utils::json::jsonUtils::retrieveStringArray>( - combinationArray[arrayIndex].serialize()); + auto stringVector = retrieveStringArray>(combinationArray[arrayIndex].serialize()); // Make sure the combination is more than one locale. if (stringVector.size() <= 1) { @@ -169,22 +164,43 @@ bool LocaleAssetsManager::initialize(bool enableWakeWord, const ConfigurationNod } } + auto localesArray = settingsConfig.getArray(DEFAULT_LOCALE_CONFIGURATION_KEY); + if (!localesArray) { + if (!settingsConfig.getString(DEFAULT_LOCALE_CONFIGURATION_KEY, &m_defaultLocale)) { + ACSDK_ERROR( + LX("initializeFailed") + .d("reason", "configurationKeyNotFound") + .d("configurationKey", SETTING_CONFIGURATION_ROOT_KEY + "." + DEFAULT_LOCALE_CONFIGURATION_KEY)); + return false; + } + m_defaultLocales.push_back(m_defaultLocale); + } else { + m_defaultLocales = retrieveStringArray>(localesArray.serialize()); + } + if (m_supportedLocales.empty()) { ACSDK_ERROR(LX("initializeFailed").d("reason", "noSupportedLocalesInConfiguration")); return false; } - if (m_defaultLocale.empty()) { + if (m_defaultLocales.empty()) { ACSDK_ERROR(LX("initializeFailed").d("reason", "noDefaultLocaleInConfiguration")); return false; } - // Check if the default is in the supported locales - if (m_supportedLocales.find(m_defaultLocale) == m_supportedLocales.end()) { + // Check if the default is in the supported locales. + if (m_supportedLocales.find(m_defaultLocales[PRIMARY_LOCALE_INDEX]) == m_supportedLocales.end()) { ACSDK_ERROR(LX("initializeFailed").d("reason", "defaultLocaleNotInSupportedLocaleList")); return false; } + // Check if the default multilingual locale is in the supported locale combinations. + if (m_defaultLocales.size() > 1 && + m_supportedLocalesCombinations.find(m_defaultLocales) == m_supportedLocalesCombinations.end()) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "defaultLocalesNotInSupportedLocalesCombinationsList")); + return false; + } + WakeWords wakeWords; if (enableWakeWord) { m_supportedWakeWords.insert({DEFAULT_SUPPORTED_WAKEWORD}); @@ -222,9 +238,14 @@ LocaleAssetsManager::LocaleCombinations LocaleAssetsManager::getSupportedLocaleC } LocaleAssetsManager::Locale LocaleAssetsManager::getDefaultLocale() const { + ACSDK_ERROR(LX("getDefaultLocale").d("reason", "methodDeprecated")); return m_defaultLocale; } +LocaleAssetsManager::Locales LocaleAssetsManager::getDefaultLocales() const { + return m_defaultLocales; +} + void LocaleAssetsManager::addLocaleAssetsObserver( const std::shared_ptr& observer) { std::lock_guard lock{m_observersMutex}; diff --git a/SampleApp/src/SampleApplication.cpp b/SampleApp/src/SampleApplication.cpp index d6dded8151..f9bbc2f210 100644 --- a/SampleApp/src/SampleApplication.cpp +++ b/SampleApp/src/SampleApplication.cpp @@ -15,7 +15,11 @@ #include #include +#include +#include +#include #include +#include #include #include #include @@ -37,7 +41,7 @@ #ifdef AUTH_MANAGER #include #include -#include +#include #include #include #endif @@ -669,11 +673,10 @@ buildModeControllerAttributes( std::unique_ptr SampleApplication::create( std::shared_ptr consoleReader, const std::vector& configFiles, - const std::string& pathToInputFolder, const std::string& logLevel, std::shared_ptr diagnostics) { auto clientApplication = std::unique_ptr(new SampleApplication); - if (!clientApplication->initialize(consoleReader, configFiles, pathToInputFolder, logLevel, diagnostics)) { + if (!clientApplication->initialize(consoleReader, configFiles, logLevel, diagnostics)) { ACSDK_CRITICAL(LX("Failed to initialize SampleApplication")); return nullptr; } @@ -711,6 +714,13 @@ SampleAppReturnCode SampleApplication::run() { return m_userInputManager->run(); } +#ifdef DIAGNOSTICS +bool SampleApplication::initiateRestart() { + m_userInputManager->onLogout(); + return true; +} +#endif + SampleApplication::~SampleApplication() { if (m_shutdownManager) { m_shutdownManager->shutdown(); @@ -723,13 +733,18 @@ SampleApplication::~SampleApplication() { for (auto& shutdownable : m_shutdownRequiredList) { if (!shutdownable) { ACSDK_ERROR(LX("shutdownFailed").m("Component requiring shutdown was null")); + } else { + shutdownable->shutdown(); } - shutdownable->shutdown(); } m_sdkInit.reset(); } +std::shared_ptr SampleApplication::getDefaultClient() { + return m_client; +} + bool SampleApplication::createMediaPlayersForAdapters( const std::shared_ptr& httpContentFetcherFactory, @@ -757,7 +772,6 @@ bool SampleApplication::createMediaPlayersForAdapters( bool SampleApplication::initialize( std::shared_ptr consoleReader, const std::vector& configFiles, - const std::string& pathToInputFolder, const std::string& logLevel, std::shared_ptr diagnostics) { avsCommon::utils::logger::Level logLevelValue = avsCommon::utils::logger::Level::UNKNOWN; @@ -832,6 +846,8 @@ bool SampleApplication::initialize( * auto sampleAppComponent = acsdkSampleApplication::getComponent( * std::move(initParams), * m_shutdownRequiredList, + * m_cryptoFactory, + * m_keyStore, * myCustomAuthDelegate, * myCustomMetricRecorder, * myCustomLogger); @@ -848,8 +864,12 @@ bool SampleApplication::initialize( std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, + std::shared_ptr, std::shared_ptr>::create(sampleAppComponent); + auto metricRecorder = manufactory->get>(); + m_sdkInit = manufactory->get>(); if (!m_sdkInit) { ACSDK_CRITICAL(LX("Failed to get SDKInit!")); @@ -1052,8 +1072,8 @@ bool SampleApplication::initialize( auto audioFactory = std::make_shared(); // Creating the alert storage object to be used for rendering and storing alerts. - auto alertStorage = - alexaClientSDK::acsdkAlerts::storage::SQLiteAlertStorage::create(config, audioFactory->alerts()); + auto alertStorage = alexaClientSDK::acsdkAlerts::storage::SQLiteAlertStorage::create( + config, audioFactory->alerts(), metricRecorder); // Creating the message storage object to be used for storing message to be sent later. auto messageStorage = alexaClientSDK::certifiedSender::SQLiteMessageStorage::create(config); @@ -1294,122 +1314,103 @@ bool SampleApplication::initialize( */ // Creating tap to talk audio provider - bool tapAlwaysReadable = true; - bool tapCanOverride = true; - bool tapCanBeOverridden = true; - - alexaClientSDK::capabilityAgents::aip::AudioProvider tapToTalkAudioProvider( - sharedDataStream, - *compatibleAudioFormat, - alexaClientSDK::capabilityAgents::aip::ASRProfile::NEAR_FIELD, - tapAlwaysReadable, - tapCanOverride, - tapCanBeOverridden); + alexaClientSDK::capabilityAgents::aip::AudioProvider tapToTalkAudioProvider = + alexaClientSDK::capabilityAgents::aip::AudioProvider::AudioProvider::TapAudioProvider( + sharedDataStream, *compatibleAudioFormat); // Creating hold to talk audio provider - bool holdAlwaysReadable = false; - bool holdCanOverride = true; - bool holdCanBeOverridden = false; - - alexaClientSDK::capabilityAgents::aip::AudioProvider holdToTalkAudioProvider( - sharedDataStream, - *compatibleAudioFormat, - alexaClientSDK::capabilityAgents::aip::ASRProfile::CLOSE_TALK, - holdAlwaysReadable, - holdCanOverride, - holdCanBeOverridden); - - auto metricRecorder = manufactory->get>(); + alexaClientSDK::capabilityAgents::aip::AudioProvider holdToTalkAudioProvider = + alexaClientSDK::capabilityAgents::aip::AudioProvider::AudioProvider::HoldAudioProvider( + sharedDataStream, *compatibleAudioFormat); /* * Creating the DefaultClient - this component serves as an out-of-box default object that instantiates and "glues" * together all the modules. */ - std::shared_ptr client = - alexaClientSDK::defaultClient::DefaultClient::create( - deviceInfo, - customerDataManager, - m_externalMusicProviderMediaPlayersMap, - m_externalMusicProviderSpeakersMap, - m_adapterToCreateFuncMap, - m_speakMediaPlayer, - std::move(audioMediaPlayerFactory), - m_alertsMediaPlayer, - m_notificationsMediaPlayer, - m_bluetoothMediaPlayer, - m_ringtoneMediaPlayer, - m_systemSoundMediaPlayer, - speakerMediaInterfaces->speaker, - audioSpeakers, - alertsMediaInterfaces->speaker, - notificationMediaInterfaces->speaker, - bluetoothMediaInterfaces->speaker, - ringtoneMediaInterfaces->speaker, - systemSoundMediaInterfaces->speaker, - {}, + m_client = alexaClientSDK::defaultClient::DefaultClient::create( + deviceInfo, + customerDataManager, + m_externalMusicProviderMediaPlayersMap, + m_externalMusicProviderSpeakersMap, + m_adapterToCreateFuncMap, + m_speakMediaPlayer, + std::move(audioMediaPlayerFactory), + m_alertsMediaPlayer, + m_notificationsMediaPlayer, + m_bluetoothMediaPlayer, + m_ringtoneMediaPlayer, + m_systemSoundMediaPlayer, + speakerMediaInterfaces->speaker, + audioSpeakers, + alertsMediaInterfaces->speaker, + notificationMediaInterfaces->speaker, + bluetoothMediaInterfaces->speaker, + ringtoneMediaInterfaces->speaker, + systemSoundMediaInterfaces->speaker, + {}, #ifdef ENABLE_PCC - phoneSpeaker, - phoneCaller, + phoneSpeaker, + phoneCaller, #endif #ifdef ENABLE_MCC - meetingSpeaker, - meetingClient, - calendarClient, + meetingSpeaker, + meetingClient, + calendarClient, #endif #ifdef ENABLE_COMMS_AUDIO_PROXY - m_commsMediaPlayer, - commsSpeaker, - sharedDataStream, -#endif - equalizerRuntimeSetup, - audioFactory, - authDelegate, - std::move(alertStorage), - std::move(messageStorage), - std::move(notificationsStorage), - std::move(deviceSettingsStorage), - std::move(bluetoothStorage), - miscStorage, - {userInterfaceManager}, - {userInterfaceManager}, - std::move(internetConnectionMonitor), - displayCardsSupported, - m_capabilitiesDelegate, - contextManager, - transportFactory, - avsGatewayManager, - localeAssetsManager, - enabledConnectionRules, - /* systemTimezone*/ nullptr, - firmwareVersion, - true, - nullptr, - std::move(bluetoothDeviceManager), - metricRecorder, + m_commsMediaPlayer, + commsSpeaker, + sharedDataStream, +#endif + equalizerRuntimeSetup, + audioFactory, + authDelegate, + std::move(alertStorage), + std::move(messageStorage), + std::move(notificationsStorage), + std::move(deviceSettingsStorage), + std::move(bluetoothStorage), + miscStorage, + {userInterfaceManager}, + {userInterfaceManager}, + std::move(internetConnectionMonitor), + displayCardsSupported, + m_capabilitiesDelegate, + contextManager, + transportFactory, + avsGatewayManager, + localeAssetsManager, + enabledConnectionRules, + /* systemTimezone*/ nullptr, + firmwareVersion, + true, + nullptr, + std::move(bluetoothDeviceManager), + metricRecorder, #ifdef ENABLE_LPM - powerResourceManager, + powerResourceManager, #else - nullptr, -#endif - diagnostics, - std::make_shared(deviceInfo), - std::make_shared(), - true, - std::make_shared(), - nullptr, - tapToTalkAudioProvider); - if (!client) { + nullptr, +#endif + diagnostics, + std::make_shared(deviceInfo), + std::make_shared(), + true, + std::make_shared(), + nullptr, + tapToTalkAudioProvider); + if (!m_client) { ACSDK_CRITICAL(LX("Failed to create default SDK client!")); return false; } - client->addSpeakerManagerObserver(userInterfaceManager); + m_client->addSpeakerManagerObserver(userInterfaceManager); - client->addNotificationsObserver(userInterfaceManager); + m_client->addNotificationsObserver(userInterfaceManager); - client->addBluetoothDeviceObserver(userInterfaceManager); + m_client->addBluetoothDeviceObserver(userInterfaceManager); - m_shutdownManager = client->getShutdownManager(); + m_shutdownManager = m_client->getShutdownManager(); if (!m_shutdownManager) { ACSDK_CRITICAL(LX("Failed to get ShutdownManager!")); return false; @@ -1418,18 +1419,18 @@ bool SampleApplication::initialize( #ifdef ENABLE_CAPTIONS std::vector> captionableMediaSources = m_audioMediaPlayerPool; captionableMediaSources.emplace_back(m_speakMediaPlayer); - client->addCaptionPresenter(captionPresenter); - client->setCaptionMediaPlayers(captionableMediaSources); + m_client->addCaptionPresenter(captionPresenter); + m_client->setCaptionMediaPlayers(captionableMediaSources); #endif - userInterfaceManager->configureSettingsNotifications(client->getSettingsManager()); + userInterfaceManager->configureSettingsNotifications(m_client->getSettingsManager()); /* * Add GUI Renderer as an observer if display cards are supported. */ if (displayCardsSupported) { m_guiRenderer = std::make_shared(); - client->addTemplateRuntimeObserver(m_guiRenderer); + m_client->addTemplateRuntimeObserver(m_guiRenderer); } #ifdef PORTAUDIO @@ -1460,14 +1461,14 @@ bool SampleApplication::initialize( #ifdef ENABLE_ENDPOINT_CONTROLLERS // Default Endpoint - if (!addControllersToDefaultEndpoint(client->getDefaultEndpointBuilder())) { + if (!addControllersToDefaultEndpoint(m_client->getDefaultEndpointBuilder())) { ACSDK_CRITICAL(LX("Failed to add controllers to default endpoint!")); return false; } // Peripheral Endpoint std::shared_ptr peripheralEndpointBuilder = - client->createEndpointBuilder(); + m_client->createEndpointBuilder(); if (!peripheralEndpointBuilder) { ACSDK_CRITICAL(LX("Failed to create peripheral endpoint Builder!")); return false; @@ -1497,41 +1498,32 @@ bool SampleApplication::initialize( return false; } - client->registerEndpoint(std::move(peripheralEndpoint)); + m_client->registerEndpoint(std::move(peripheralEndpoint)); #endif // Creating wake word audio provider, if necessary #ifdef KWD - bool wakeAlwaysReadable = true; - bool wakeCanOverride = false; - bool wakeCanBeOverridden = true; - - alexaClientSDK::capabilityAgents::aip::AudioProvider wakeWordAudioProvider( - sharedDataStream, - *compatibleAudioFormat, - alexaClientSDK::capabilityAgents::aip::ASRProfile::NEAR_FIELD, - wakeAlwaysReadable, - wakeCanOverride, - wakeCanBeOverridden); - - // This observer is notified any time a keyword is detected and notifies the DefaultClient to start recognizing. - auto keywordObserver = std::make_shared(client, wakeWordAudioProvider); + alexaClientSDK::capabilityAgents::aip::AudioProvider wakeWordAudioProvider = + alexaClientSDK::capabilityAgents::aip::AudioProvider::WakeAudioProvider( + sharedDataStream, *compatibleAudioFormat); m_keywordDetector = alexaClientSDK::kwd::KeywordDetectorProvider::create( sharedDataStream, *compatibleAudioFormat, - {keywordObserver}, + std::unordered_set>(), std::unordered_set< - std::shared_ptr>(), - pathToInputFolder); + std::shared_ptr>()); if (!m_keywordDetector) { ACSDK_CRITICAL(LX("Failed to create keyword detector!")); return false; } + // // This observer is notified any time a keyword is detected and notifies the DefaultClient to start recognizing. + auto keywordObserver = KeywordObserver::create(m_client, wakeWordAudioProvider, m_keywordDetector); + // If wake word is enabled, then creating the interaction manager with a wake word audio provider. m_interactionManager = std::make_shared( - client, + m_client, micWrapper, userInterfaceManager, #ifdef ENABLE_PCC @@ -1568,7 +1560,7 @@ bool SampleApplication::initialize( // clang-format off // If wake word is not enabled, then creating the interaction manager without a wake word audio provider. m_interactionManager = std::make_shared( - client, + m_client, micWrapper, userInterfaceManager, #ifdef ENABLE_PCC @@ -1605,14 +1597,14 @@ bool SampleApplication::initialize( #endif m_shutdownRequiredList.push_back(m_interactionManager); - client->addAlexaDialogStateObserver(m_interactionManager); - client->addCallStateObserver(m_interactionManager); + m_client->addAlexaDialogStateObserver(m_interactionManager); + m_client->addCallStateObserver(m_interactionManager); #ifdef ENABLE_REVOKE_AUTH // Creating the revoke authorization observer. auto revokeObserver = - std::make_shared(client->getRegistrationManager()); - client->addRevokeAuthorizationObserver(revokeObserver); + std::make_shared(m_client->getRegistrationManager()); + m_client->addRevokeAuthorizationObserver(revokeObserver); #endif // Creating the input observer. @@ -1624,18 +1616,22 @@ bool SampleApplication::initialize( } authDelegate->addAuthObserver(m_userInputManager); - client->addRegistrationObserver(m_userInputManager); + m_client->addRegistrationObserver(m_userInputManager); m_capabilitiesDelegate->addCapabilitiesObserver(m_userInputManager); #ifdef AUTH_MANAGER - m_authManager->setRegistrationManager(client->getRegistrationManager()); + m_authManager->setRegistrationManager(m_client->getRegistrationManager()); auto httpPost = avsCommon::utils::libcurlUtils::HttpPost::createHttpPostInterface(); + auto cryptoFactory = manufactory->get>(); + auto keyStore = manufactory->get>(); + m_lwaAdapter = acsdkAuthorization::lwa::LWAAuthorizationAdapter::create( configPtr, std::move(httpPost), deviceInfo, - acsdkAuthorization::lwa::SQLiteLWAAuthorizationStorage::createLWAAuthorizationStorageInterface(configPtr)); + acsdkAuthorization::lwa::LWAAuthorizationStorage::createLWAAuthorizationStorageInterface( + configPtr, "", cryptoFactory, keyStore)); if (!m_lwaAdapter) { ACSDK_CRITICAL(LX("Failed to create LWA Adapter!")); @@ -1650,7 +1646,7 @@ bool SampleApplication::initialize( m_lwaAdapter->authorizeUsingCBL(cblRequest); #endif - client->connect(); + m_client->connect(); return true; } diff --git a/SampleApp/src/SampleApplicationComponent.cpp b/SampleApp/src/SampleApplicationComponent.cpp index 1b602adb1d..c8449455be 100644 --- a/SampleApp/src/SampleApplicationComponent.cpp +++ b/SampleApp/src/SampleApplicationComponent.cpp @@ -13,7 +13,9 @@ * permissions and limitations under the License. */ +#include #include +#include #include #ifdef ACSDK_ACS_UTILS @@ -103,7 +105,13 @@ SampleApplicationComponent getComponent( .addUniqueFactory(avsCommon::utils::libcurlUtils::HttpPost::createHttpPostInterface) .addRetainedFactory(avsCommon::utils::timing::MultiTimer::createMultiTimer) .addRetainedFactory(contextManager::ContextManager::createContextManagerInterface) - .addRetainedFactory(registrationManager::CustomerDataManagerFactory::createCustomerDataManagerInterface); + .addRetainedFactory(registrationManager::CustomerDataManagerFactory::createCustomerDataManagerInterface) +#ifdef ENABLE_PKCS11 + .addRetainedFactory(acsdkPkcs11::createKeyStore) +#else + .addInstance(std::shared_ptr()) +#endif + .addRetainedFactory(acsdkCrypto::createCryptoFactory); } } // namespace acsdkSampleApplication diff --git a/SampleApp/src/main.cpp b/SampleApp/src/main.cpp index 24daf39c86..9b23bf85b2 100644 --- a/SampleApp/src/main.cpp +++ b/SampleApp/src/main.cpp @@ -30,7 +30,7 @@ using namespace alexaClientSDK::sampleApp; * Function that evaluates if the SampleApp invocation uses old-style or new-style opt-arg style invocation. * * @param argc The number of elements in the @c argv array. - * @param argv An array of @argc elements, containing the program name and all command-line arguments. + * @param argv An array of @a argc elements, containing the program name and all command-line arguments. * @return @c true of the invocation uses optarg style argument @c false otherwise. */ bool usesOptStyleArgs(int argc, char* argv[]) { @@ -48,12 +48,11 @@ bool usesOptStyleArgs(int argc, char* argv[]) { * user input until the @c run() function returns. * * @param argc The number of elements in the @c argv array. - * @param argv An array of @argc elements, containing the program name and all command-line arguments. + * @param argv An array of @a argc elements, containing the program name and all command-line arguments. * @return @c EXIT_FAILURE if the program failed to initialize correctly, else @c EXIT_SUCCESS. */ int main(int argc, char* argv[]) { std::vector configFiles; - std::string pathToKWDInputFolder; std::string logLevel; if (usesOptStyleArgs(argc, argv)) { @@ -65,12 +64,6 @@ int main(int argc, char* argv[]) { } configFiles.push_back(std::string(argv[++i])); ConsolePrinter::simplePrint("configFile " + std::string(argv[i])); - } else if (strcmp(argv[i], "-K") == 0) { - if (i + 1 == argc) { - ConsolePrinter::simplePrint("No wakeword input specified for -K option"); - return SampleAppReturnCode::ERROR; - } - pathToKWDInputFolder = std::string(argv[++i]); } else if (strcmp(argv[i], "-L") == 0) { if (i + 1 == argc) { ConsolePrinter::simplePrint("No debugLevel specified for -L option"); @@ -80,24 +73,11 @@ int main(int argc, char* argv[]) { } else { ConsolePrinter::simplePrint( "USAGE: " + std::string(argv[0]) + " -C -C ... -C " + - " -K -L "); + " -L "); return SampleAppReturnCode::ERROR; } } } else { -#if defined(KWD_SENSORY) - if (argc < 3) { - ConsolePrinter::simplePrint( - "USAGE: " + std::string(argv[0]) + - " [log_level]"); - return SampleAppReturnCode::ERROR; - } else { - pathToKWDInputFolder = std::string(argv[2]); - if (4 == argc) { - logLevel = std::string(argv[3]); - } - } -#else if (argc < 2) { ConsolePrinter::simplePrint( "USAGE: " + std::string(argv[0]) + " [log_level]"); @@ -106,7 +86,6 @@ int main(int argc, char* argv[]) { if (3 == argc) { logLevel = std::string(argv[2]); } -#endif configFiles.push_back(std::string(argv[1])); ConsolePrinter::simplePrint("configFile " + std::string(argv[1])); @@ -128,7 +107,6 @@ int main(int argc, char* argv[]) { sampleApplication = SampleApplication::create( consoleReader, configFiles, - pathToKWDInputFolder, logLevel #ifdef DIAGNOSTICS , diff --git a/Settings/src/CMakeLists.txt b/Settings/src/CMakeLists.txt index 4ddae40d8f..3a324d8ea5 100644 --- a/Settings/src/CMakeLists.txt +++ b/Settings/src/CMakeLists.txt @@ -1,7 +1,7 @@ add_definitions("-DACSDK_LOG_MODULE=settings") -add_library(DeviceSettings SHARED +add_library(DeviceSettings CloudControlledSettingProtocol.cpp DeviceControlledSettingProtocol.cpp SettingEventSender.cpp diff --git a/Settings/src/Types/LocaleWakeWordsSetting.cpp b/Settings/src/Types/LocaleWakeWordsSetting.cpp index ddff1bb9b2..f51d835f9c 100644 --- a/Settings/src/Types/LocaleWakeWordsSetting.cpp +++ b/Settings/src/Types/LocaleWakeWordsSetting.cpp @@ -434,7 +434,7 @@ LocaleWakeWordsSetting::LocaleWakeWordsSetting( std::shared_ptr wakeWordsEventSender, std::shared_ptr settingStorage, std::shared_ptr assetsManager) : - LocalesSetting{{assetsManager->getDefaultLocale()}}, + LocalesSetting{assetsManager->getDefaultLocales()}, WakeWordsSetting{DEFAULT_WAKE_WORDS}, m_localeEventSender{localeEventSender}, m_wakeWordsEventSender{wakeWordsEventSender}, diff --git a/Settings/test/LocaleWakeWordsSettingTest.cpp b/Settings/test/LocaleWakeWordsSettingTest.cpp index 25d5cefb99..7d22726d90 100644 --- a/Settings/test/LocaleWakeWordsSettingTest.cpp +++ b/Settings/test/LocaleWakeWordsSettingTest.cpp @@ -79,6 +79,9 @@ static const std::set SUPPORTED_LOCALES{ENGLISH_CANADA_VALUE, FRENC /// A vector of locales that contains only en-CA. static const std::vector ENGLISH_LOCALES{ENGLISH_CANADA_VALUE}; +/// A vector of locales that contains only fr-CA. +static const std::vector FRENCH_LOCALES{FRENCH_CANADA_VALUE}; + /// The database key to be used to save wake words. static const std::string WAKE_WORDS_KEY = "SpeechRecognizer.wakeWords"; @@ -143,7 +146,7 @@ void LocaleWakeWordsSettingTest::SetUp() { m_localeObserver = std::make_shared>(); m_wakeWordsObserver = std::make_shared>(); - EXPECT_CALL(*m_assetsManagerMock, getDefaultLocale()).WillRepeatedly(Return(ENGLISH_CANADA_VALUE)); + EXPECT_CALL(*m_assetsManagerMock, getDefaultLocales()).WillRepeatedly(Return(ENGLISH_LOCALES)); EXPECT_CALL(*m_assetsManagerMock, getSupportedLocales()).WillRepeatedly(Return(SUPPORTED_LOCALES)); EXPECT_CALL(*m_assetsManagerMock, getDefaultSupportedWakeWords()) .WillRepeatedly(Return(SUPPORTED_WAKE_WORDS_COMBINATION)); @@ -180,7 +183,7 @@ void LocaleWakeWordsSettingTest::initializeSetting( const WakeWordsSetting::ValueType wakeWords) { WaitEvent e; - EXPECT_CALL(*m_assetsManagerMock, getDefaultLocale()).WillOnce(Return(FRENCH_CANADA_VALUE)); + EXPECT_CALL(*m_assetsManagerMock, getDefaultLocales()).WillOnce(Return(FRENCH_LOCALES)); EXPECT_CALL(*m_storageMock, loadSetting(LOCALES_KEY)) .WillOnce(Return( std::make_pair(SettingStatus::SYNCHRONIZED, settings::toSettingString(locales).second))); @@ -435,7 +438,7 @@ TEST_F(LocaleWakeWordsSettingTest, test_AVSChangeLocaleRequestSendEventFailed) { /// Test restore when no value is available in the database. This should run local change to both wake words and locale. TEST_F(LocaleWakeWordsSettingTest, test_restoreValueNotAvailable) { - EXPECT_CALL(*m_assetsManagerMock, getDefaultLocale()).WillOnce(Return(FRENCH_CANADA_VALUE)); + EXPECT_CALL(*m_assetsManagerMock, getDefaultLocales()).WillOnce(Return(FRENCH_LOCALES)); EXPECT_CALL(*m_storageMock, loadSetting(LOCALES_KEY)) .WillOnce(Return(std::make_pair(SettingStatus::NOT_AVAILABLE, ""))); EXPECT_CALL(*m_storageMock, loadSetting(WAKE_WORDS_KEY)) diff --git a/SpeechEncoder/OpusEncoderContext/src/CMakeLists.txt b/SpeechEncoder/OpusEncoderContext/src/CMakeLists.txt index 00b3985053..4e470c723c 100644 --- a/SpeechEncoder/OpusEncoderContext/src/CMakeLists.txt +++ b/SpeechEncoder/OpusEncoderContext/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=opusEncoderContext") -add_library(OpusEncoderContext SHARED +add_library(OpusEncoderContext OpusEncoderContext.cpp) find_path(OPUS_INCLUDE_DIR opus) diff --git a/SpeechEncoder/src/CMakeLists.txt b/SpeechEncoder/src/CMakeLists.txt index b331d03895..e1131104d7 100644 --- a/SpeechEncoder/src/CMakeLists.txt +++ b/SpeechEncoder/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=speechEncoder") -add_library(SpeechEncoder SHARED +add_library(SpeechEncoder SpeechEncoder.cpp ) diff --git a/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteDatabase.h b/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteDatabase.h index 2ec5198fcd..20969169c8 100644 --- a/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteDatabase.h +++ b/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteDatabase.h @@ -139,6 +139,16 @@ class SQLiteDatabase { */ bool clearTable(const std::string& tableName); + /** + * Drops specified table. + * + * This method drops the table if it exists and is empty. + * + * @param tableName The name of the table to drop. + * @return true if successful, false if there was an error. + */ + bool dropTable(const std::string& tableName); + /** * If open, close the internal SQLite DB. Do nothing if there is no DB open. */ diff --git a/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteMiscStorage.h b/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteMiscStorage.h index a9c8a66609..0aa76cf7f0 100644 --- a/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteMiscStorage.h +++ b/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteMiscStorage.h @@ -52,6 +52,16 @@ class SQLiteMiscStorage : public avsCommon::sdkInterfaces::storage::MiscStorageI static std::unique_ptr create( const avsCommon::utils::configuration::ConfigurationNode& configurationRoot); + /** + * Factory method for creating a storage object for a SQLite database. + * Note that the actual database will not be created by this function. + * + * @deprecated + * @param[in] databasePath Path to database + * @return Pointer to the SQLiteAlertStorage object, nullptr if there's an error creating it. + */ + static std::unique_ptr create(const std::string& databasePath); + /** * Destructor */ @@ -100,6 +110,17 @@ class SQLiteMiscStorage : public avsCommon::sdkInterfaces::storage::MiscStorageI std::unordered_map* valueContainer) override; /// @} + /** + * @brief Provides reference to a database. + * + * This method provides a reference to inner database object for database maintenance operations. The access to the + * database is not serialized against parallel access and should be used only when it is guaranteed there are no + * other consumers of the objects. + * + * @return Reference to database. + */ + SQLiteDatabase& getDatabase(); + private: /** * Constructor. diff --git a/Storage/SQLiteStorage/src/CMakeLists.txt b/Storage/SQLiteStorage/src/CMakeLists.txt index 012363ee29..6882670fd2 100644 --- a/Storage/SQLiteStorage/src/CMakeLists.txt +++ b/Storage/SQLiteStorage/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=sqliteStorage") -add_library(SQLiteStorage SHARED +add_library(SQLiteStorage SQLiteDatabase.cpp SQLiteMiscStorage.cpp SQLiteStatement.cpp diff --git a/Storage/SQLiteStorage/src/SQLiteDatabase.cpp b/Storage/SQLiteStorage/src/SQLiteDatabase.cpp index e0e0e78d6c..db9335ad78 100644 --- a/Storage/SQLiteStorage/src/SQLiteDatabase.cpp +++ b/Storage/SQLiteStorage/src/SQLiteDatabase.cpp @@ -30,7 +30,7 @@ static const std::string TAG("SQLiteDatabase"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -42,20 +42,7 @@ SQLiteDatabase::SQLiteDatabase(const std::string& storageFilePath) : } SQLiteDatabase::~SQLiteDatabase() { - if (m_dbHandle) { - ACSDK_WARN( - LX(__func__).m("DB wasn't closed before destruction of SQLiteDatabase").d("file path", m_storageFilePath)); - - // TODO: The DB should just be closed by the destructor, but currently some of the tests call - // SQLiteDatabase::close(). It doesn't happen outside of the test code. This JIRA is filed to take care of it - // later: ACSDK-1094. - close(); - } - - if (m_transactionIsInProgress) { - ACSDK_ERROR(LX(__func__).d("reason", "There is an incomplete transaction. Rolling it back.")); - rollbackTransaction(); - } + close(); // Reset the shared_ptr so transaction's weak_ptr will invalidate. m_sharedThisPlaceholder.reset(); @@ -63,18 +50,18 @@ SQLiteDatabase::~SQLiteDatabase() { bool SQLiteDatabase::initialize() { if (m_dbHandle) { - ACSDK_ERROR(LX(__func__).m("Database is already open.")); + ACSDK_ERROR(LX("initializeFailed").m("Database is already open.")); return false; } if (avsCommon::utils::file::fileExists(m_storageFilePath)) { - ACSDK_ERROR(LX(__func__).m("File specified already exists.").d("file path", m_storageFilePath)); + ACSDK_ERROR(LX("initializeFailed").m("File specified already exists.").d("file path", m_storageFilePath)); return false; } m_dbHandle = createSQLiteDatabase(m_storageFilePath); if (!m_dbHandle) { - ACSDK_ERROR(LX(__func__).m("Database could not be created.").d("file path", m_storageFilePath)); + ACSDK_ERROR(LX("initializeFailed").m("Database could not be created.").d("file path", m_storageFilePath)); return false; } @@ -83,18 +70,18 @@ bool SQLiteDatabase::initialize() { bool SQLiteDatabase::open() { if (m_dbHandle) { - ACSDK_ERROR(LX(__func__).m("Database is already open.")); + ACSDK_ERROR(LX("openFailed").m("Database is already open.")); return false; } if (!avsCommon::utils::file::fileExists(m_storageFilePath)) { - ACSDK_DEBUG0(LX(__func__).m("File specified does not exist.").d("file path", m_storageFilePath)); + ACSDK_DEBUG0(LX("openFailed").m("File specified does not exist.").d("file path", m_storageFilePath)); return false; } m_dbHandle = openSQLiteDatabase(m_storageFilePath); if (!m_dbHandle) { - ACSDK_ERROR(LX(__func__).m("Database could not be opened.").d("file path", m_storageFilePath)); + ACSDK_ERROR(LX("openFailed").m("Database could not be opened.").d("file path", m_storageFilePath)); return false; } @@ -116,8 +103,9 @@ bool SQLiteDatabase::performQuery(const std::string& sqlString) { bool SQLiteDatabase::tableExists(const std::string& tableName) { if (!alexaClientSDK::storage::sqliteStorage::tableExists(m_dbHandle, tableName)) { - ACSDK_DEBUG0( - LX(__func__).d("reason", "table doesn't exist or there was an error checking").d("table", tableName)); + ACSDK_DEBUG0(LX("tableExistsFailed") + .d("reason", "table doesn't exist or there was an error checking") + .d("table", tableName)); return false; } return true; @@ -125,7 +113,16 @@ bool SQLiteDatabase::tableExists(const std::string& tableName) { bool SQLiteDatabase::clearTable(const std::string& tableName) { if (!alexaClientSDK::storage::sqliteStorage::clearTable(m_dbHandle, tableName)) { - ACSDK_ERROR(LX(__func__).d("could not clear table", tableName)); + ACSDK_ERROR(LX("clearTableFailed").d("could not clear table", tableName)); + return false; + } + + return true; +} + +bool SQLiteDatabase::dropTable(const std::string& tableName) { + if (!alexaClientSDK::storage::sqliteStorage::dropTable(m_dbHandle, tableName)) { + ACSDK_ERROR(LX("dropTableFailed").d("could not drop table", tableName)); return false; } @@ -134,6 +131,11 @@ bool SQLiteDatabase::clearTable(const std::string& tableName) { void SQLiteDatabase::close() { if (m_dbHandle) { + if (m_transactionIsInProgress) { + ACSDK_ERROR(LX("closeError").d("reason", "There is an incomplete transaction. Rolling it back.")); + rollbackTransaction(); + } + closeSQLiteDatabase(m_dbHandle); m_dbHandle = nullptr; } diff --git a/Storage/SQLiteStorage/src/SQLiteMiscStorage.cpp b/Storage/SQLiteStorage/src/SQLiteMiscStorage.cpp index f1f235a564..fba615103a 100644 --- a/Storage/SQLiteStorage/src/SQLiteMiscStorage.cpp +++ b/Storage/SQLiteStorage/src/SQLiteMiscStorage.cpp @@ -39,7 +39,7 @@ static const std::string VALUE_COLUMN_NAME = "value"; /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -150,7 +150,11 @@ std::unique_ptr SQLiteMiscStorage::create(const Configuration return nullptr; } - return std::unique_ptr(new SQLiteMiscStorage(miscDbFilePath)); + return create(miscDbFilePath); +} + +std::unique_ptr SQLiteMiscStorage::create(const std::string& databasePath) { + return std::unique_ptr(new SQLiteMiscStorage(databasePath)); } SQLiteMiscStorage::SQLiteMiscStorage(const std::string& dbFilePath) : m_db{dbFilePath} { @@ -558,8 +562,7 @@ bool SQLiteMiscStorage::deleteTableLocked(const std::string& componentName, cons return false; } - const std::string sqlString = "DROP TABLE IF EXISTS " + dbTableName + ";"; - if (!m_db.performQuery(sqlString)) { + if (!m_db.dropTable(dbTableName)) { ACSDK_ERROR(LX(errorEvent).d("Could not delete table", tableName)); return false; } @@ -1051,6 +1054,10 @@ bool SQLiteMiscStorage::isOpenedLocked() { return m_db.isDatabaseReady(); } +SQLiteDatabase& SQLiteMiscStorage::getDatabase() { + return m_db; +} + } // namespace sqliteStorage } // namespace storage } // namespace alexaClientSDK diff --git a/Storage/SQLiteStorage/src/SQLiteStatement.cpp b/Storage/SQLiteStorage/src/SQLiteStatement.cpp index 497cb96272..39e2b41d66 100644 --- a/Storage/SQLiteStorage/src/SQLiteStatement.cpp +++ b/Storage/SQLiteStorage/src/SQLiteStatement.cpp @@ -29,7 +29,7 @@ static const std::string TAG("SQLiteStatement"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/Storage/SQLiteStorage/src/SQLiteUtils.cpp b/Storage/SQLiteStorage/src/SQLiteUtils.cpp index fcf29fdcc6..23916423c6 100644 --- a/Storage/SQLiteStorage/src/SQLiteUtils.cpp +++ b/Storage/SQLiteStorage/src/SQLiteUtils.cpp @@ -36,7 +36,7 @@ static const std::string TAG("SQLiteUtils"); /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/Storage/SQLiteStorage/test/SQLiteDatabaseTest.cpp b/Storage/SQLiteStorage/test/SQLiteDatabaseTest.cpp index d54cd7775f..5fdf8a9c4c 100644 --- a/Storage/SQLiteStorage/test/SQLiteDatabaseTest.cpp +++ b/Storage/SQLiteStorage/test/SQLiteDatabaseTest.cpp @@ -33,6 +33,9 @@ static std::string g_workingDirectory; static const std::string BAD_PATH = "_/_/_/there/is/no/way/this/path/should/exist/,/so/it/should/cause/an/error/when/creating/the/db"; +/// Test table name. +static const std::string TEST_TABLE_NAME{"testTable"}; + /** * Helper function that generates a unique filepath using the passed in g_workingDirectory. * @@ -214,6 +217,28 @@ TEST(SQLiteDatabaseTest, test_autoRollback) { db.close(); } +/// Test to initialize already existing DB. +TEST(SQLiteDatabaseTest, test_createDeleteTable) { + auto dbFilePath = generateDbFilePath(); + SQLiteDatabase db1(dbFilePath); + ASSERT_TRUE(db1.initialize()); + + if (db1.tableExists(TEST_TABLE_NAME)) { + db1.clearTable(TEST_TABLE_NAME); + db1.dropTable(TEST_TABLE_NAME); + } + + EXPECT_FALSE(db1.tableExists(TEST_TABLE_NAME)); + + ASSERT_TRUE(db1.performQuery("CREATE TABLE " + TEST_TABLE_NAME + " (key TEXT PRIMARY KEY NOT NULL);")); + + ASSERT_TRUE(db1.tableExists(TEST_TABLE_NAME)); + ASSERT_TRUE(db1.dropTable(TEST_TABLE_NAME)); + ASSERT_FALSE(db1.tableExists(TEST_TABLE_NAME)); + + db1.close(); +} + } // namespace test } // namespace sqliteStorage } // namespace storage diff --git a/Storage/SQLiteStorage/test/SQLiteMiscStorageTest.cpp b/Storage/SQLiteStorage/test/SQLiteMiscStorageTest.cpp index 10adc7b7bf..ff8f292d8d 100644 --- a/Storage/SQLiteStorage/test/SQLiteMiscStorageTest.cpp +++ b/Storage/SQLiteStorage/test/SQLiteMiscStorageTest.cpp @@ -503,6 +503,11 @@ TEST_F(SQLiteMiscStorageTest, test_tableEntryTestsMultiThread) { } } +/// Test misc storage provide non-null reference to database object. +TEST_F(SQLiteMiscStorageTest, test_getDatabaseReference) { + ASSERT_NE(nullptr, &m_miscStorage->getDatabase()); +} + } // namespace test } // namespace sqliteStorage } // namespace storage diff --git a/SynchronizeStateSender/src/CMakeLists.txt b/SynchronizeStateSender/src/CMakeLists.txt index 2c3b7d814c..416ee96bf5 100644 --- a/SynchronizeStateSender/src/CMakeLists.txt +++ b/SynchronizeStateSender/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=synchronizeStateSender") -add_library(SynchronizeStateSender SHARED +add_library(SynchronizeStateSender SynchronizeStateSenderFactory.cpp PostConnectSynchronizeStateSender.cpp) diff --git a/SynchronizeStateSender/src/PostConnectSynchronizeStateSender.cpp b/SynchronizeStateSender/src/PostConnectSynchronizeStateSender.cpp index eb17f822c6..3f6aeeab74 100644 --- a/SynchronizeStateSender/src/PostConnectSynchronizeStateSender.cpp +++ b/SynchronizeStateSender/src/PostConnectSynchronizeStateSender.cpp @@ -53,7 +53,7 @@ static const std::string METRIC_ACTIVITY_NAME_PREFIX = "POSTCONNECT_SYNCHRONIZE_ /// Table with the retry times on subsequent retries. static const std::vector RETRY_TABLE = { - 250, // Retry 1: 0.25s + 500, // Retry 1: 0.5s 1000, // Retry 1: 1s 2000, // Retry 2: 2s 4000, // Retry 3 4s diff --git a/ThirdParty/CMakeLists.txt b/ThirdParty/CMakeLists.txt index 4ee5c94c74..e19d2865bc 100644 --- a/ThirdParty/CMakeLists.txt +++ b/ThirdParty/CMakeLists.txt @@ -1,3 +1,6 @@ -add_subdirectory("rapidjson") +if (USE_DEFAULT_RAPIDJSON) + add_subdirectory("rapidjson") +endif() add_subdirectory("MultipartParser") add_subdirectory("bluez-alsa") +add_subdirectory("pkcs11-2.40") diff --git a/ThirdParty/pkcs11-2.40/CMakeLists.txt b/ThirdParty/pkcs11-2.40/CMakeLists.txt new file mode 100644 index 0000000000..2c5b754336 --- /dev/null +++ b/ThirdParty/pkcs11-2.40/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(PKCS11 LANGUAGES CXX) + +add_library(pkcs11-api-2.40 INTERFACE) +target_include_directories(pkcs11-api-2.40 INTERFACE "include") + +add_library(pkcs11-api ALIAS pkcs11-api-2.40) diff --git a/ThirdParty/pkcs11-2.40/NOTICE.txt b/ThirdParty/pkcs11-2.40/NOTICE.txt new file mode 100644 index 0000000000..216ee7a4b6 --- /dev/null +++ b/ThirdParty/pkcs11-2.40/NOTICE.txt @@ -0,0 +1,43 @@ +Copyright © OASIS Open 2015. All Rights Reserved. + +All capitalized terms in the following text have the meanings assigned to them in the OASIS Intellectual Property Rights +Policy (the "OASIS IPR Policy"). The full Policy may be found at the OASIS website: +[http://www.oasis-open.org/policies-guidelines/ipr] + +This document and translations of it may be copied and furnished to others, and derivative works that comment on or +otherwise explain it or assist in its implementation may be prepared, copied, published, and distributed, in whole or in +part, without restriction of any kind, provided that the above copyright notice and this section are included on all such +copies and derivative works. However, this document itself may not be modified in any way, including by removing the +copyright notice or references to OASIS, except as needed for the purpose of developing any document or deliverable +produced by an OASIS Technical Committee (in which case the rules applicable to copyrights, as set forth in the OASIS +IPR Policy, must be followed) or as required to translate it into languages other than English. + +The limited permissions granted above are perpetual and will not be revoked by OASIS or its successors or assigns. + +This document and the information contained herein is provided on an “AS IS” basis and OASIS DISCLAIMS ALL WARRANTIES, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE +ANY OWNERSHIP RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. OASIS AND ITS +MEMBERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THIS +DOCUMENT OR ANY PART THEREOF. + +[OASIS requests that any OASIS Party or any other party that believes it has patent claims that would necessarily be +infringed by implementations of this OASIS Standards Final Deliverable, to notify OASIS TC Administrator and provide +an indication of its willingness to grant patent licenses to such patent claims in a manner consistent with the IPR Mode +of the OASIS Technical Committee that produced this deliverable.] + +[OASIS invites any party to contact the OASIS TC Administrator if it is aware of a claim of ownership of any patent +claims that would necessarily be infringed by implementations of this OASIS Standards Final Deliverable by a patent +holder that is not willing to provide a license to such patent claims in a manner consistent with the IPR Mode of the +OASIS Technical Committee that produced this OASIS Standards Final Deliverable. OASIS may include such claims on its +website, but disclaims any obligation to do so.] + +[OASIS takes no position regarding the validity or scope of any intellectual property or other rights that might be +claimed to pertain to the implementation or use of the technology described in this OASIS Standards Final Deliverable or +the extent to which any license under such rights might or might not be available; neither does it represent that it has +made any effort to identify any such rights. Information on OASIS’ procedures with respect to rights in any document or +deliverable produced by an OASIS Technical Committee can be found on the OASIS website. Copies of claims of rights made +available for publication and any assurances of licenses to be made available, or the result of an attempt made to +obtain a general license or permission for the use of such proprietary rights by implementers or users of this OASIS +Standards Final Deliverable, can be obtained from the OASIS TC Administrator. OASIS makes no representation that any +information or list of intellectual property rights will at any time be complete, or that any claims in such list are, +in fact, Essential Claims.] diff --git a/ThirdParty/pkcs11-2.40/include/pkcs11.h b/ThirdParty/pkcs11-2.40/include/pkcs11.h new file mode 100644 index 0000000000..0d78dd7113 --- /dev/null +++ b/ThirdParty/pkcs11-2.40/include/pkcs11.h @@ -0,0 +1,265 @@ +/* Copyright (c) OASIS Open 2016. All Rights Reserved./ + * /Distributed under the terms of the OASIS IPR Policy, + * [http://www.oasis-open.org/policies-guidelines/ipr], AS-IS, WITHOUT ANY + * IMPLIED OR EXPRESS WARRANTY; there is no warranty of MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE or NONINFRINGEMENT of the rights of others. + */ + +/* Latest version of the specification: + * http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.html + */ + +#ifndef _PKCS11_H_ +#define _PKCS11_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Before including this file (pkcs11.h) (or pkcs11t.h by + * itself), 5 platform-specific macros must be defined. These + * macros are described below, and typical definitions for them + * are also given. Be advised that these definitions can depend + * on both the platform and the compiler used (and possibly also + * on whether a Cryptoki library is linked statically or + * dynamically). + * + * In addition to defining these 5 macros, the packing convention + * for Cryptoki structures should be set. The Cryptoki + * convention on packing is that structures should be 1-byte + * aligned. + * + * If you're using Microsoft Developer Studio 5.0 to produce + * Win32 stuff, this might be done by using the following + * preprocessor directive before including pkcs11.h or pkcs11t.h: + * + * #pragma pack(push, cryptoki, 1) + * + * and using the following preprocessor directive after including + * pkcs11.h or pkcs11t.h: + * + * #pragma pack(pop, cryptoki) + * + * If you're using an earlier version of Microsoft Developer + * Studio to produce Win16 stuff, this might be done by using + * the following preprocessor directive before including + * pkcs11.h or pkcs11t.h: + * + * #pragma pack(1) + * + * In a UNIX environment, you're on your own for this. You might + * not need to do (or be able to do!) anything. + * + * + * Now for the macros: + * + * + * 1. CK_PTR: The indirection string for making a pointer to an + * object. It can be used like this: + * + * typedef CK_BYTE CK_PTR CK_BYTE_PTR; + * + * If you're using Microsoft Developer Studio 5.0 to produce + * Win32 stuff, it might be defined by: + * + * #define CK_PTR * + * + * If you're using an earlier version of Microsoft Developer + * Studio to produce Win16 stuff, it might be defined by: + * + * #define CK_PTR far * + * + * In a typical UNIX environment, it might be defined by: + * + * #define CK_PTR * + * + * + * 2. CK_DECLARE_FUNCTION(returnType, name): A macro which makes + * an importable Cryptoki library function declaration out of a + * return type and a function name. It should be used in the + * following fashion: + * + * extern CK_DECLARE_FUNCTION(CK_RV, C_Initialize)( + * CK_VOID_PTR pReserved + * ); + * + * If you're using Microsoft Developer Studio 5.0 to declare a + * function in a Win32 Cryptoki .dll, it might be defined by: + * + * #define CK_DECLARE_FUNCTION(returnType, name) \ + * returnType __declspec(dllimport) name + * + * If you're using an earlier version of Microsoft Developer + * Studio to declare a function in a Win16 Cryptoki .dll, it + * might be defined by: + * + * #define CK_DECLARE_FUNCTION(returnType, name) \ + * returnType __export _far _pascal name + * + * In a UNIX environment, it might be defined by: + * + * #define CK_DECLARE_FUNCTION(returnType, name) \ + * returnType name + * + * + * 3. CK_DECLARE_FUNCTION_POINTER(returnType, name): A macro + * which makes a Cryptoki API function pointer declaration or + * function pointer type declaration out of a return type and a + * function name. It should be used in the following fashion: + * + * // Define funcPtr to be a pointer to a Cryptoki API function + * // taking arguments args and returning CK_RV. + * CK_DECLARE_FUNCTION_POINTER(CK_RV, funcPtr)(args); + * + * or + * + * // Define funcPtrType to be the type of a pointer to a + * // Cryptoki API function taking arguments args and returning + * // CK_RV, and then define funcPtr to be a variable of type + * // funcPtrType. + * typedef CK_DECLARE_FUNCTION_POINTER(CK_RV, funcPtrType)(args); + * funcPtrType funcPtr; + * + * If you're using Microsoft Developer Studio 5.0 to access + * functions in a Win32 Cryptoki .dll, in might be defined by: + * + * #define CK_DECLARE_FUNCTION_POINTER(returnType, name) \ + * returnType __declspec(dllimport) (* name) + * + * If you're using an earlier version of Microsoft Developer + * Studio to access functions in a Win16 Cryptoki .dll, it might + * be defined by: + * + * #define CK_DECLARE_FUNCTION_POINTER(returnType, name) \ + * returnType __export _far _pascal (* name) + * + * In a UNIX environment, it might be defined by: + * + * #define CK_DECLARE_FUNCTION_POINTER(returnType, name) \ + * returnType (* name) + * + * + * 4. CK_CALLBACK_FUNCTION(returnType, name): A macro which makes + * a function pointer type for an application callback out of + * a return type for the callback and a name for the callback. + * It should be used in the following fashion: + * + * CK_CALLBACK_FUNCTION(CK_RV, myCallback)(args); + * + * to declare a function pointer, myCallback, to a callback + * which takes arguments args and returns a CK_RV. It can also + * be used like this: + * + * typedef CK_CALLBACK_FUNCTION(CK_RV, myCallbackType)(args); + * myCallbackType myCallback; + * + * If you're using Microsoft Developer Studio 5.0 to do Win32 + * Cryptoki development, it might be defined by: + * + * #define CK_CALLBACK_FUNCTION(returnType, name) \ + * returnType (* name) + * + * If you're using an earlier version of Microsoft Developer + * Studio to do Win16 development, it might be defined by: + * + * #define CK_CALLBACK_FUNCTION(returnType, name) \ + * returnType _far _pascal (* name) + * + * In a UNIX environment, it might be defined by: + * + * #define CK_CALLBACK_FUNCTION(returnType, name) \ + * returnType (* name) + * + * + * 5. NULL_PTR: This macro is the value of a NULL pointer. + * + * In any ANSI/ISO C environment (and in many others as well), + * this should best be defined by + * + * #ifndef NULL_PTR + * #define NULL_PTR 0 + * #endif + */ + + +/* All the various Cryptoki types and #define'd values are in the + * file pkcs11t.h. + */ +#include "pkcs11t.h" + +#define __PASTE(x,y) x##y + + +/* ============================================================== + * Define the "extern" form of all the entry points. + * ============================================================== + */ + +#define CK_NEED_ARG_LIST 1 +#define CK_PKCS11_FUNCTION_INFO(name) \ + extern CK_DECLARE_FUNCTION(CK_RV, name) + +/* pkcs11f.h has all the information about the Cryptoki + * function prototypes. + */ +#include "pkcs11f.h" + +#undef CK_NEED_ARG_LIST +#undef CK_PKCS11_FUNCTION_INFO + + +/* ============================================================== + * Define the typedef form of all the entry points. That is, for + * each Cryptoki function C_XXX, define a type CK_C_XXX which is + * a pointer to that kind of function. + * ============================================================== + */ + +#define CK_NEED_ARG_LIST 1 +#define CK_PKCS11_FUNCTION_INFO(name) \ + typedef CK_DECLARE_FUNCTION_POINTER(CK_RV, __PASTE(CK_,name)) + +/* pkcs11f.h has all the information about the Cryptoki + * function prototypes. + */ +#include "pkcs11f.h" + +#undef CK_NEED_ARG_LIST +#undef CK_PKCS11_FUNCTION_INFO + + +/* ============================================================== + * Define structed vector of entry points. A CK_FUNCTION_LIST + * contains a CK_VERSION indicating a library's Cryptoki version + * and then a whole slew of function pointers to the routines in + * the library. This type was declared, but not defined, in + * pkcs11t.h. + * ============================================================== + */ + +#define CK_PKCS11_FUNCTION_INFO(name) \ + __PASTE(CK_,name) name; + +struct CK_FUNCTION_LIST { + + CK_VERSION version; /* Cryptoki version */ + +/* Pile all the function pointers into the CK_FUNCTION_LIST. */ +/* pkcs11f.h has all the information about the Cryptoki + * function prototypes. + */ +#include "pkcs11f.h" + +}; + +#undef CK_PKCS11_FUNCTION_INFO + + +#undef __PASTE + +#ifdef __cplusplus +} +#endif + +#endif /* _PKCS11_H_ */ + diff --git a/ThirdParty/pkcs11-2.40/include/pkcs11f.h b/ThirdParty/pkcs11-2.40/include/pkcs11f.h new file mode 100644 index 0000000000..ed90affc5e --- /dev/null +++ b/ThirdParty/pkcs11-2.40/include/pkcs11f.h @@ -0,0 +1,939 @@ +/* Copyright (c) OASIS Open 2016. All Rights Reserved./ + * /Distributed under the terms of the OASIS IPR Policy, + * [http://www.oasis-open.org/policies-guidelines/ipr], AS-IS, WITHOUT ANY + * IMPLIED OR EXPRESS WARRANTY; there is no warranty of MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE or NONINFRINGEMENT of the rights of others. + */ + +/* Latest version of the specification: + * http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.html + */ + +/* This header file contains pretty much everything about all the + * Cryptoki function prototypes. Because this information is + * used for more than just declaring function prototypes, the + * order of the functions appearing herein is important, and + * should not be altered. + */ + +/* General-purpose */ + +/* C_Initialize initializes the Cryptoki library. */ +CK_PKCS11_FUNCTION_INFO(C_Initialize) +#ifdef CK_NEED_ARG_LIST +( + CK_VOID_PTR pInitArgs /* if this is not NULL_PTR, it gets + * cast to CK_C_INITIALIZE_ARGS_PTR + * and dereferenced + */ +); +#endif + + +/* C_Finalize indicates that an application is done with the + * Cryptoki library. + */ +CK_PKCS11_FUNCTION_INFO(C_Finalize) +#ifdef CK_NEED_ARG_LIST +( + CK_VOID_PTR pReserved /* reserved. Should be NULL_PTR */ +); +#endif + + +/* C_GetInfo returns general information about Cryptoki. */ +CK_PKCS11_FUNCTION_INFO(C_GetInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_INFO_PTR pInfo /* location that receives information */ +); +#endif + + +/* C_GetFunctionList returns the function list. */ +CK_PKCS11_FUNCTION_INFO(C_GetFunctionList) +#ifdef CK_NEED_ARG_LIST +( + CK_FUNCTION_LIST_PTR_PTR ppFunctionList /* receives pointer to + * function list + */ +); +#endif + + + +/* Slot and token management */ + +/* C_GetSlotList obtains a list of slots in the system. */ +CK_PKCS11_FUNCTION_INFO(C_GetSlotList) +#ifdef CK_NEED_ARG_LIST +( + CK_BBOOL tokenPresent, /* only slots with tokens */ + CK_SLOT_ID_PTR pSlotList, /* receives array of slot IDs */ + CK_ULONG_PTR pulCount /* receives number of slots */ +); +#endif + + +/* C_GetSlotInfo obtains information about a particular slot in + * the system. + */ +CK_PKCS11_FUNCTION_INFO(C_GetSlotInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* the ID of the slot */ + CK_SLOT_INFO_PTR pInfo /* receives the slot information */ +); +#endif + + +/* C_GetTokenInfo obtains information about a particular token + * in the system. + */ +CK_PKCS11_FUNCTION_INFO(C_GetTokenInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* ID of the token's slot */ + CK_TOKEN_INFO_PTR pInfo /* receives the token information */ +); +#endif + + +/* C_GetMechanismList obtains a list of mechanism types + * supported by a token. + */ +CK_PKCS11_FUNCTION_INFO(C_GetMechanismList) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* ID of token's slot */ + CK_MECHANISM_TYPE_PTR pMechanismList, /* gets mech. array */ + CK_ULONG_PTR pulCount /* gets # of mechs. */ +); +#endif + + +/* C_GetMechanismInfo obtains information about a particular + * mechanism possibly supported by a token. + */ +CK_PKCS11_FUNCTION_INFO(C_GetMechanismInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* ID of the token's slot */ + CK_MECHANISM_TYPE type, /* type of mechanism */ + CK_MECHANISM_INFO_PTR pInfo /* receives mechanism info */ +); +#endif + + +/* C_InitToken initializes a token. */ +CK_PKCS11_FUNCTION_INFO(C_InitToken) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* ID of the token's slot */ + CK_UTF8CHAR_PTR pPin, /* the SO's initial PIN */ + CK_ULONG ulPinLen, /* length in bytes of the PIN */ + CK_UTF8CHAR_PTR pLabel /* 32-byte token label (blank padded) */ +); +#endif + + +/* C_InitPIN initializes the normal user's PIN. */ +CK_PKCS11_FUNCTION_INFO(C_InitPIN) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_UTF8CHAR_PTR pPin, /* the normal user's PIN */ + CK_ULONG ulPinLen /* length in bytes of the PIN */ +); +#endif + + +/* C_SetPIN modifies the PIN of the user who is logged in. */ +CK_PKCS11_FUNCTION_INFO(C_SetPIN) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_UTF8CHAR_PTR pOldPin, /* the old PIN */ + CK_ULONG ulOldLen, /* length of the old PIN */ + CK_UTF8CHAR_PTR pNewPin, /* the new PIN */ + CK_ULONG ulNewLen /* length of the new PIN */ +); +#endif + + + +/* Session management */ + +/* C_OpenSession opens a session between an application and a + * token. + */ +CK_PKCS11_FUNCTION_INFO(C_OpenSession) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID, /* the slot's ID */ + CK_FLAGS flags, /* from CK_SESSION_INFO */ + CK_VOID_PTR pApplication, /* passed to callback */ + CK_NOTIFY Notify, /* callback function */ + CK_SESSION_HANDLE_PTR phSession /* gets session handle */ +); +#endif + + +/* C_CloseSession closes a session between an application and a + * token. + */ +CK_PKCS11_FUNCTION_INFO(C_CloseSession) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + +/* C_CloseAllSessions closes all sessions with a token. */ +CK_PKCS11_FUNCTION_INFO(C_CloseAllSessions) +#ifdef CK_NEED_ARG_LIST +( + CK_SLOT_ID slotID /* the token's slot */ +); +#endif + + +/* C_GetSessionInfo obtains information about the session. */ +CK_PKCS11_FUNCTION_INFO(C_GetSessionInfo) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_SESSION_INFO_PTR pInfo /* receives session info */ +); +#endif + + +/* C_GetOperationState obtains the state of the cryptographic operation + * in a session. + */ +CK_PKCS11_FUNCTION_INFO(C_GetOperationState) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pOperationState, /* gets state */ + CK_ULONG_PTR pulOperationStateLen /* gets state length */ +); +#endif + + +/* C_SetOperationState restores the state of the cryptographic + * operation in a session. + */ +CK_PKCS11_FUNCTION_INFO(C_SetOperationState) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pOperationState, /* holds state */ + CK_ULONG ulOperationStateLen, /* holds state length */ + CK_OBJECT_HANDLE hEncryptionKey, /* en/decryption key */ + CK_OBJECT_HANDLE hAuthenticationKey /* sign/verify key */ +); +#endif + + +/* C_Login logs a user into a token. */ +CK_PKCS11_FUNCTION_INFO(C_Login) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_USER_TYPE userType, /* the user type */ + CK_UTF8CHAR_PTR pPin, /* the user's PIN */ + CK_ULONG ulPinLen /* the length of the PIN */ +); +#endif + + +/* C_Logout logs a user out from a token. */ +CK_PKCS11_FUNCTION_INFO(C_Logout) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + + +/* Object management */ + +/* C_CreateObject creates a new object. */ +CK_PKCS11_FUNCTION_INFO(C_CreateObject) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* the object's template */ + CK_ULONG ulCount, /* attributes in template */ + CK_OBJECT_HANDLE_PTR phObject /* gets new object's handle. */ +); +#endif + + +/* C_CopyObject copies an object, creating a new object for the + * copy. + */ +CK_PKCS11_FUNCTION_INFO(C_CopyObject) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject, /* the object's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* template for new object */ + CK_ULONG ulCount, /* attributes in template */ + CK_OBJECT_HANDLE_PTR phNewObject /* receives handle of copy */ +); +#endif + + +/* C_DestroyObject destroys an object. */ +CK_PKCS11_FUNCTION_INFO(C_DestroyObject) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject /* the object's handle */ +); +#endif + + +/* C_GetObjectSize gets the size of an object in bytes. */ +CK_PKCS11_FUNCTION_INFO(C_GetObjectSize) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject, /* the object's handle */ + CK_ULONG_PTR pulSize /* receives size of object */ +); +#endif + + +/* C_GetAttributeValue obtains the value of one or more object + * attributes. + */ +CK_PKCS11_FUNCTION_INFO(C_GetAttributeValue) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject, /* the object's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* specifies attrs; gets vals */ + CK_ULONG ulCount /* attributes in template */ +); +#endif + + +/* C_SetAttributeValue modifies the value of one or more object + * attributes. + */ +CK_PKCS11_FUNCTION_INFO(C_SetAttributeValue) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hObject, /* the object's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* specifies attrs and values */ + CK_ULONG ulCount /* attributes in template */ +); +#endif + + +/* C_FindObjectsInit initializes a search for token and session + * objects that match a template. + */ +CK_PKCS11_FUNCTION_INFO(C_FindObjectsInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_ATTRIBUTE_PTR pTemplate, /* attribute values to match */ + CK_ULONG ulCount /* attrs in search template */ +); +#endif + + +/* C_FindObjects continues a search for token and session + * objects that match a template, obtaining additional object + * handles. + */ +CK_PKCS11_FUNCTION_INFO(C_FindObjects) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_OBJECT_HANDLE_PTR phObject, /* gets obj. handles */ + CK_ULONG ulMaxObjectCount, /* max handles to get */ + CK_ULONG_PTR pulObjectCount /* actual # returned */ +); +#endif + + +/* C_FindObjectsFinal finishes a search for token and session + * objects. + */ +CK_PKCS11_FUNCTION_INFO(C_FindObjectsFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + + +/* Encryption and decryption */ + +/* C_EncryptInit initializes an encryption operation. */ +CK_PKCS11_FUNCTION_INFO(C_EncryptInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the encryption mechanism */ + CK_OBJECT_HANDLE hKey /* handle of encryption key */ +); +#endif + + +/* C_Encrypt encrypts single-part data. */ +CK_PKCS11_FUNCTION_INFO(C_Encrypt) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pData, /* the plaintext data */ + CK_ULONG ulDataLen, /* bytes of plaintext */ + CK_BYTE_PTR pEncryptedData, /* gets ciphertext */ + CK_ULONG_PTR pulEncryptedDataLen /* gets c-text size */ +); +#endif + + +/* C_EncryptUpdate continues a multiple-part encryption + * operation. + */ +CK_PKCS11_FUNCTION_INFO(C_EncryptUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pPart, /* the plaintext data */ + CK_ULONG ulPartLen, /* plaintext data len */ + CK_BYTE_PTR pEncryptedPart, /* gets ciphertext */ + CK_ULONG_PTR pulEncryptedPartLen /* gets c-text size */ +); +#endif + + +/* C_EncryptFinal finishes a multiple-part encryption + * operation. + */ +CK_PKCS11_FUNCTION_INFO(C_EncryptFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session handle */ + CK_BYTE_PTR pLastEncryptedPart, /* last c-text */ + CK_ULONG_PTR pulLastEncryptedPartLen /* gets last size */ +); +#endif + + +/* C_DecryptInit initializes a decryption operation. */ +CK_PKCS11_FUNCTION_INFO(C_DecryptInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the decryption mechanism */ + CK_OBJECT_HANDLE hKey /* handle of decryption key */ +); +#endif + + +/* C_Decrypt decrypts encrypted data in a single part. */ +CK_PKCS11_FUNCTION_INFO(C_Decrypt) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pEncryptedData, /* ciphertext */ + CK_ULONG ulEncryptedDataLen, /* ciphertext length */ + CK_BYTE_PTR pData, /* gets plaintext */ + CK_ULONG_PTR pulDataLen /* gets p-text size */ +); +#endif + + +/* C_DecryptUpdate continues a multiple-part decryption + * operation. + */ +CK_PKCS11_FUNCTION_INFO(C_DecryptUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pEncryptedPart, /* encrypted data */ + CK_ULONG ulEncryptedPartLen, /* input length */ + CK_BYTE_PTR pPart, /* gets plaintext */ + CK_ULONG_PTR pulPartLen /* p-text size */ +); +#endif + + +/* C_DecryptFinal finishes a multiple-part decryption + * operation. + */ +CK_PKCS11_FUNCTION_INFO(C_DecryptFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pLastPart, /* gets plaintext */ + CK_ULONG_PTR pulLastPartLen /* p-text size */ +); +#endif + + + +/* Message digesting */ + +/* C_DigestInit initializes a message-digesting operation. */ +CK_PKCS11_FUNCTION_INFO(C_DigestInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism /* the digesting mechanism */ +); +#endif + + +/* C_Digest digests data in a single part. */ +CK_PKCS11_FUNCTION_INFO(C_Digest) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pData, /* data to be digested */ + CK_ULONG ulDataLen, /* bytes of data to digest */ + CK_BYTE_PTR pDigest, /* gets the message digest */ + CK_ULONG_PTR pulDigestLen /* gets digest length */ +); +#endif + + +/* C_DigestUpdate continues a multiple-part message-digesting + * operation. + */ +CK_PKCS11_FUNCTION_INFO(C_DigestUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pPart, /* data to be digested */ + CK_ULONG ulPartLen /* bytes of data to be digested */ +); +#endif + + +/* C_DigestKey continues a multi-part message-digesting + * operation, by digesting the value of a secret key as part of + * the data already digested. + */ +CK_PKCS11_FUNCTION_INFO(C_DigestKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_OBJECT_HANDLE hKey /* secret key to digest */ +); +#endif + + +/* C_DigestFinal finishes a multiple-part message-digesting + * operation. + */ +CK_PKCS11_FUNCTION_INFO(C_DigestFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pDigest, /* gets the message digest */ + CK_ULONG_PTR pulDigestLen /* gets byte count of digest */ +); +#endif + + + +/* Signing and MACing */ + +/* C_SignInit initializes a signature (private key encryption) + * operation, where the signature is (will be) an appendix to + * the data, and plaintext cannot be recovered from the + * signature. + */ +CK_PKCS11_FUNCTION_INFO(C_SignInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the signature mechanism */ + CK_OBJECT_HANDLE hKey /* handle of signature key */ +); +#endif + + +/* C_Sign signs (encrypts with private key) data in a single + * part, where the signature is (will be) an appendix to the + * data, and plaintext cannot be recovered from the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_Sign) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pData, /* the data to sign */ + CK_ULONG ulDataLen, /* count of bytes to sign */ + CK_BYTE_PTR pSignature, /* gets the signature */ + CK_ULONG_PTR pulSignatureLen /* gets signature length */ +); +#endif + + +/* C_SignUpdate continues a multiple-part signature operation, + * where the signature is (will be) an appendix to the data, + * and plaintext cannot be recovered from the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_SignUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pPart, /* the data to sign */ + CK_ULONG ulPartLen /* count of bytes to sign */ +); +#endif + + +/* C_SignFinal finishes a multiple-part signature operation, + * returning the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_SignFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pSignature, /* gets the signature */ + CK_ULONG_PTR pulSignatureLen /* gets signature length */ +); +#endif + + +/* C_SignRecoverInit initializes a signature operation, where + * the data can be recovered from the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_SignRecoverInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the signature mechanism */ + CK_OBJECT_HANDLE hKey /* handle of the signature key */ +); +#endif + + +/* C_SignRecover signs data in a single operation, where the + * data can be recovered from the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_SignRecover) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pData, /* the data to sign */ + CK_ULONG ulDataLen, /* count of bytes to sign */ + CK_BYTE_PTR pSignature, /* gets the signature */ + CK_ULONG_PTR pulSignatureLen /* gets signature length */ +); +#endif + + + +/* Verifying signatures and MACs */ + +/* C_VerifyInit initializes a verification operation, where the + * signature is an appendix to the data, and plaintext cannot + * cannot be recovered from the signature (e.g. DSA). + */ +CK_PKCS11_FUNCTION_INFO(C_VerifyInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the verification mechanism */ + CK_OBJECT_HANDLE hKey /* verification key */ +); +#endif + + +/* C_Verify verifies a signature in a single-part operation, + * where the signature is an appendix to the data, and plaintext + * cannot be recovered from the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_Verify) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pData, /* signed data */ + CK_ULONG ulDataLen, /* length of signed data */ + CK_BYTE_PTR pSignature, /* signature */ + CK_ULONG ulSignatureLen /* signature length*/ +); +#endif + + +/* C_VerifyUpdate continues a multiple-part verification + * operation, where the signature is an appendix to the data, + * and plaintext cannot be recovered from the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_VerifyUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pPart, /* signed data */ + CK_ULONG ulPartLen /* length of signed data */ +); +#endif + + +/* C_VerifyFinal finishes a multiple-part verification + * operation, checking the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_VerifyFinal) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pSignature, /* signature to verify */ + CK_ULONG ulSignatureLen /* signature length */ +); +#endif + + +/* C_VerifyRecoverInit initializes a signature verification + * operation, where the data is recovered from the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_VerifyRecoverInit) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the verification mechanism */ + CK_OBJECT_HANDLE hKey /* verification key */ +); +#endif + + +/* C_VerifyRecover verifies a signature in a single-part + * operation, where the data is recovered from the signature. + */ +CK_PKCS11_FUNCTION_INFO(C_VerifyRecover) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pSignature, /* signature to verify */ + CK_ULONG ulSignatureLen, /* signature length */ + CK_BYTE_PTR pData, /* gets signed data */ + CK_ULONG_PTR pulDataLen /* gets signed data len */ +); +#endif + + + +/* Dual-function cryptographic operations */ + +/* C_DigestEncryptUpdate continues a multiple-part digesting + * and encryption operation. + */ +CK_PKCS11_FUNCTION_INFO(C_DigestEncryptUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pPart, /* the plaintext data */ + CK_ULONG ulPartLen, /* plaintext length */ + CK_BYTE_PTR pEncryptedPart, /* gets ciphertext */ + CK_ULONG_PTR pulEncryptedPartLen /* gets c-text length */ +); +#endif + + +/* C_DecryptDigestUpdate continues a multiple-part decryption and + * digesting operation. + */ +CK_PKCS11_FUNCTION_INFO(C_DecryptDigestUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pEncryptedPart, /* ciphertext */ + CK_ULONG ulEncryptedPartLen, /* ciphertext length */ + CK_BYTE_PTR pPart, /* gets plaintext */ + CK_ULONG_PTR pulPartLen /* gets plaintext len */ +); +#endif + + +/* C_SignEncryptUpdate continues a multiple-part signing and + * encryption operation. + */ +CK_PKCS11_FUNCTION_INFO(C_SignEncryptUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pPart, /* the plaintext data */ + CK_ULONG ulPartLen, /* plaintext length */ + CK_BYTE_PTR pEncryptedPart, /* gets ciphertext */ + CK_ULONG_PTR pulEncryptedPartLen /* gets c-text length */ +); +#endif + + +/* C_DecryptVerifyUpdate continues a multiple-part decryption and + * verify operation. + */ +CK_PKCS11_FUNCTION_INFO(C_DecryptVerifyUpdate) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_BYTE_PTR pEncryptedPart, /* ciphertext */ + CK_ULONG ulEncryptedPartLen, /* ciphertext length */ + CK_BYTE_PTR pPart, /* gets plaintext */ + CK_ULONG_PTR pulPartLen /* gets p-text length */ +); +#endif + + + +/* Key management */ + +/* C_GenerateKey generates a secret key, creating a new key + * object. + */ +CK_PKCS11_FUNCTION_INFO(C_GenerateKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* key generation mech. */ + CK_ATTRIBUTE_PTR pTemplate, /* template for new key */ + CK_ULONG ulCount, /* # of attrs in template */ + CK_OBJECT_HANDLE_PTR phKey /* gets handle of new key */ +); +#endif + + +/* C_GenerateKeyPair generates a public-key/private-key pair, + * creating new key objects. + */ +CK_PKCS11_FUNCTION_INFO(C_GenerateKeyPair) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session handle */ + CK_MECHANISM_PTR pMechanism, /* key-gen mech. */ + CK_ATTRIBUTE_PTR pPublicKeyTemplate, /* template for pub. key */ + CK_ULONG ulPublicKeyAttributeCount, /* # pub. attrs. */ + CK_ATTRIBUTE_PTR pPrivateKeyTemplate, /* template for priv. key */ + CK_ULONG ulPrivateKeyAttributeCount, /* # priv. attrs. */ + CK_OBJECT_HANDLE_PTR phPublicKey, /* gets pub. key handle */ + CK_OBJECT_HANDLE_PTR phPrivateKey /* gets priv. key handle */ +); +#endif + + +/* C_WrapKey wraps (i.e., encrypts) a key. */ +CK_PKCS11_FUNCTION_INFO(C_WrapKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_MECHANISM_PTR pMechanism, /* the wrapping mechanism */ + CK_OBJECT_HANDLE hWrappingKey, /* wrapping key */ + CK_OBJECT_HANDLE hKey, /* key to be wrapped */ + CK_BYTE_PTR pWrappedKey, /* gets wrapped key */ + CK_ULONG_PTR pulWrappedKeyLen /* gets wrapped key size */ +); +#endif + + +/* C_UnwrapKey unwraps (decrypts) a wrapped key, creating a new + * key object. + */ +CK_PKCS11_FUNCTION_INFO(C_UnwrapKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_MECHANISM_PTR pMechanism, /* unwrapping mech. */ + CK_OBJECT_HANDLE hUnwrappingKey, /* unwrapping key */ + CK_BYTE_PTR pWrappedKey, /* the wrapped key */ + CK_ULONG ulWrappedKeyLen, /* wrapped key len */ + CK_ATTRIBUTE_PTR pTemplate, /* new key template */ + CK_ULONG ulAttributeCount, /* template length */ + CK_OBJECT_HANDLE_PTR phKey /* gets new handle */ +); +#endif + + +/* C_DeriveKey derives a key from a base key, creating a new key + * object. + */ +CK_PKCS11_FUNCTION_INFO(C_DeriveKey) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* session's handle */ + CK_MECHANISM_PTR pMechanism, /* key deriv. mech. */ + CK_OBJECT_HANDLE hBaseKey, /* base key */ + CK_ATTRIBUTE_PTR pTemplate, /* new key template */ + CK_ULONG ulAttributeCount, /* template length */ + CK_OBJECT_HANDLE_PTR phKey /* gets new handle */ +); +#endif + + + +/* Random number generation */ + +/* C_SeedRandom mixes additional seed material into the token's + * random number generator. + */ +CK_PKCS11_FUNCTION_INFO(C_SeedRandom) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR pSeed, /* the seed material */ + CK_ULONG ulSeedLen /* length of seed material */ +); +#endif + + +/* C_GenerateRandom generates random data. */ +CK_PKCS11_FUNCTION_INFO(C_GenerateRandom) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_BYTE_PTR RandomData, /* receives the random data */ + CK_ULONG ulRandomLen /* # of bytes to generate */ +); +#endif + + + +/* Parallel function management */ + +/* C_GetFunctionStatus is a legacy function; it obtains an + * updated status of a function running in parallel with an + * application. + */ +CK_PKCS11_FUNCTION_INFO(C_GetFunctionStatus) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + +/* C_CancelFunction is a legacy function; it cancels a function + * running in parallel. + */ +CK_PKCS11_FUNCTION_INFO(C_CancelFunction) +#ifdef CK_NEED_ARG_LIST +( + CK_SESSION_HANDLE hSession /* the session's handle */ +); +#endif + + +/* C_WaitForSlotEvent waits for a slot event (token insertion, + * removal, etc.) to occur. + */ +CK_PKCS11_FUNCTION_INFO(C_WaitForSlotEvent) +#ifdef CK_NEED_ARG_LIST +( + CK_FLAGS flags, /* blocking/nonblocking flag */ + CK_SLOT_ID_PTR pSlot, /* location that receives the slot ID */ + CK_VOID_PTR pRserved /* reserved. Should be NULL_PTR */ +); +#endif + diff --git a/ThirdParty/pkcs11-2.40/include/pkcs11t.h b/ThirdParty/pkcs11-2.40/include/pkcs11t.h new file mode 100644 index 0000000000..c13e67cf55 --- /dev/null +++ b/ThirdParty/pkcs11-2.40/include/pkcs11t.h @@ -0,0 +1,2003 @@ +/* Copyright (c) OASIS Open 2016. All Rights Reserved./ + * /Distributed under the terms of the OASIS IPR Policy, + * [http://www.oasis-open.org/policies-guidelines/ipr], AS-IS, WITHOUT ANY + * IMPLIED OR EXPRESS WARRANTY; there is no warranty of MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE or NONINFRINGEMENT of the rights of others. + */ + +/* Latest version of the specification: + * http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.html + */ + +/* See top of pkcs11.h for information about the macros that + * must be defined and the structure-packing conventions that + * must be set before including this file. + */ + +#ifndef _PKCS11T_H_ +#define _PKCS11T_H_ 1 + +#define CRYPTOKI_VERSION_MAJOR 2 +#define CRYPTOKI_VERSION_MINOR 40 +#define CRYPTOKI_VERSION_AMENDMENT 0 + +#define CK_TRUE 1 +#define CK_FALSE 0 + +#ifndef CK_DISABLE_TRUE_FALSE +#ifndef FALSE +#define FALSE CK_FALSE +#endif +#ifndef TRUE +#define TRUE CK_TRUE +#endif +#endif + +/* an unsigned 8-bit value */ +typedef unsigned char CK_BYTE; + +/* an unsigned 8-bit character */ +typedef CK_BYTE CK_CHAR; + +/* an 8-bit UTF-8 character */ +typedef CK_BYTE CK_UTF8CHAR; + +/* a BYTE-sized Boolean flag */ +typedef CK_BYTE CK_BBOOL; + +/* an unsigned value, at least 32 bits long */ +typedef unsigned long int CK_ULONG; + +/* a signed value, the same size as a CK_ULONG */ +typedef long int CK_LONG; + +/* at least 32 bits; each bit is a Boolean flag */ +typedef CK_ULONG CK_FLAGS; + + +/* some special values for certain CK_ULONG variables */ +#define CK_UNAVAILABLE_INFORMATION (~0UL) +#define CK_EFFECTIVELY_INFINITE 0UL + + +typedef CK_BYTE CK_PTR CK_BYTE_PTR; +typedef CK_CHAR CK_PTR CK_CHAR_PTR; +typedef CK_UTF8CHAR CK_PTR CK_UTF8CHAR_PTR; +typedef CK_ULONG CK_PTR CK_ULONG_PTR; +typedef void CK_PTR CK_VOID_PTR; + +/* Pointer to a CK_VOID_PTR-- i.e., pointer to pointer to void */ +typedef CK_VOID_PTR CK_PTR CK_VOID_PTR_PTR; + + +/* The following value is always invalid if used as a session + * handle or object handle + */ +#define CK_INVALID_HANDLE 0UL + + +typedef struct CK_VERSION { + CK_BYTE major; /* integer portion of version number */ + CK_BYTE minor; /* 1/100ths portion of version number */ +} CK_VERSION; + +typedef CK_VERSION CK_PTR CK_VERSION_PTR; + + +typedef struct CK_INFO { + CK_VERSION cryptokiVersion; /* Cryptoki interface ver */ + CK_UTF8CHAR manufacturerID[32]; /* blank padded */ + CK_FLAGS flags; /* must be zero */ + CK_UTF8CHAR libraryDescription[32]; /* blank padded */ + CK_VERSION libraryVersion; /* version of library */ +} CK_INFO; + +typedef CK_INFO CK_PTR CK_INFO_PTR; + + +/* CK_NOTIFICATION enumerates the types of notifications that + * Cryptoki provides to an application + */ +typedef CK_ULONG CK_NOTIFICATION; +#define CKN_SURRENDER 0UL +#define CKN_OTP_CHANGED 1UL + +typedef CK_ULONG CK_SLOT_ID; + +typedef CK_SLOT_ID CK_PTR CK_SLOT_ID_PTR; + + +/* CK_SLOT_INFO provides information about a slot */ +typedef struct CK_SLOT_INFO { + CK_UTF8CHAR slotDescription[64]; /* blank padded */ + CK_UTF8CHAR manufacturerID[32]; /* blank padded */ + CK_FLAGS flags; + + CK_VERSION hardwareVersion; /* version of hardware */ + CK_VERSION firmwareVersion; /* version of firmware */ +} CK_SLOT_INFO; + +/* flags: bit flags that provide capabilities of the slot + * Bit Flag Mask Meaning + */ +#define CKF_TOKEN_PRESENT 0x00000001UL /* a token is there */ +#define CKF_REMOVABLE_DEVICE 0x00000002UL /* removable devices*/ +#define CKF_HW_SLOT 0x00000004UL /* hardware slot */ + +typedef CK_SLOT_INFO CK_PTR CK_SLOT_INFO_PTR; + + +/* CK_TOKEN_INFO provides information about a token */ +typedef struct CK_TOKEN_INFO { + CK_UTF8CHAR label[32]; /* blank padded */ + CK_UTF8CHAR manufacturerID[32]; /* blank padded */ + CK_UTF8CHAR model[16]; /* blank padded */ + CK_CHAR serialNumber[16]; /* blank padded */ + CK_FLAGS flags; /* see below */ + + CK_ULONG ulMaxSessionCount; /* max open sessions */ + CK_ULONG ulSessionCount; /* sess. now open */ + CK_ULONG ulMaxRwSessionCount; /* max R/W sessions */ + CK_ULONG ulRwSessionCount; /* R/W sess. now open */ + CK_ULONG ulMaxPinLen; /* in bytes */ + CK_ULONG ulMinPinLen; /* in bytes */ + CK_ULONG ulTotalPublicMemory; /* in bytes */ + CK_ULONG ulFreePublicMemory; /* in bytes */ + CK_ULONG ulTotalPrivateMemory; /* in bytes */ + CK_ULONG ulFreePrivateMemory; /* in bytes */ + CK_VERSION hardwareVersion; /* version of hardware */ + CK_VERSION firmwareVersion; /* version of firmware */ + CK_CHAR utcTime[16]; /* time */ +} CK_TOKEN_INFO; + +/* The flags parameter is defined as follows: + * Bit Flag Mask Meaning + */ +#define CKF_RNG 0x00000001UL /* has random # generator */ +#define CKF_WRITE_PROTECTED 0x00000002UL /* token is write-protected */ +#define CKF_LOGIN_REQUIRED 0x00000004UL /* user must login */ +#define CKF_USER_PIN_INITIALIZED 0x00000008UL /* normal user's PIN is set */ + +/* CKF_RESTORE_KEY_NOT_NEEDED. If it is set, + * that means that *every* time the state of cryptographic + * operations of a session is successfully saved, all keys + * needed to continue those operations are stored in the state + */ +#define CKF_RESTORE_KEY_NOT_NEEDED 0x00000020UL + +/* CKF_CLOCK_ON_TOKEN. If it is set, that means + * that the token has some sort of clock. The time on that + * clock is returned in the token info structure + */ +#define CKF_CLOCK_ON_TOKEN 0x00000040UL + +/* CKF_PROTECTED_AUTHENTICATION_PATH. If it is + * set, that means that there is some way for the user to login + * without sending a PIN through the Cryptoki library itself + */ +#define CKF_PROTECTED_AUTHENTICATION_PATH 0x00000100UL + +/* CKF_DUAL_CRYPTO_OPERATIONS. If it is true, + * that means that a single session with the token can perform + * dual simultaneous cryptographic operations (digest and + * encrypt; decrypt and digest; sign and encrypt; and decrypt + * and sign) + */ +#define CKF_DUAL_CRYPTO_OPERATIONS 0x00000200UL + +/* CKF_TOKEN_INITIALIZED. If it is true, the + * token has been initialized using C_InitializeToken or an + * equivalent mechanism outside the scope of PKCS #11. + * Calling C_InitializeToken when this flag is set will cause + * the token to be reinitialized. + */ +#define CKF_TOKEN_INITIALIZED 0x00000400UL + +/* CKF_SECONDARY_AUTHENTICATION. If it is + * true, the token supports secondary authentication for + * private key objects. + */ +#define CKF_SECONDARY_AUTHENTICATION 0x00000800UL + +/* CKF_USER_PIN_COUNT_LOW. If it is true, an + * incorrect user login PIN has been entered at least once + * since the last successful authentication. + */ +#define CKF_USER_PIN_COUNT_LOW 0x00010000UL + +/* CKF_USER_PIN_FINAL_TRY. If it is true, + * supplying an incorrect user PIN will it to become locked. + */ +#define CKF_USER_PIN_FINAL_TRY 0x00020000UL + +/* CKF_USER_PIN_LOCKED. If it is true, the + * user PIN has been locked. User login to the token is not + * possible. + */ +#define CKF_USER_PIN_LOCKED 0x00040000UL + +/* CKF_USER_PIN_TO_BE_CHANGED. If it is true, + * the user PIN value is the default value set by token + * initialization or manufacturing, or the PIN has been + * expired by the card. + */ +#define CKF_USER_PIN_TO_BE_CHANGED 0x00080000UL + +/* CKF_SO_PIN_COUNT_LOW. If it is true, an + * incorrect SO login PIN has been entered at least once since + * the last successful authentication. + */ +#define CKF_SO_PIN_COUNT_LOW 0x00100000UL + +/* CKF_SO_PIN_FINAL_TRY. If it is true, + * supplying an incorrect SO PIN will it to become locked. + */ +#define CKF_SO_PIN_FINAL_TRY 0x00200000UL + +/* CKF_SO_PIN_LOCKED. If it is true, the SO + * PIN has been locked. SO login to the token is not possible. + */ +#define CKF_SO_PIN_LOCKED 0x00400000UL + +/* CKF_SO_PIN_TO_BE_CHANGED. If it is true, + * the SO PIN value is the default value set by token + * initialization or manufacturing, or the PIN has been + * expired by the card. + */ +#define CKF_SO_PIN_TO_BE_CHANGED 0x00800000UL + +#define CKF_ERROR_STATE 0x01000000UL + +typedef CK_TOKEN_INFO CK_PTR CK_TOKEN_INFO_PTR; + + +/* CK_SESSION_HANDLE is a Cryptoki-assigned value that + * identifies a session + */ +typedef CK_ULONG CK_SESSION_HANDLE; + +typedef CK_SESSION_HANDLE CK_PTR CK_SESSION_HANDLE_PTR; + + +/* CK_USER_TYPE enumerates the types of Cryptoki users */ +typedef CK_ULONG CK_USER_TYPE; +/* Security Officer */ +#define CKU_SO 0UL +/* Normal user */ +#define CKU_USER 1UL +/* Context specific */ +#define CKU_CONTEXT_SPECIFIC 2UL + +/* CK_STATE enumerates the session states */ +typedef CK_ULONG CK_STATE; +#define CKS_RO_PUBLIC_SESSION 0UL +#define CKS_RO_USER_FUNCTIONS 1UL +#define CKS_RW_PUBLIC_SESSION 2UL +#define CKS_RW_USER_FUNCTIONS 3UL +#define CKS_RW_SO_FUNCTIONS 4UL + +/* CK_SESSION_INFO provides information about a session */ +typedef struct CK_SESSION_INFO { + CK_SLOT_ID slotID; + CK_STATE state; + CK_FLAGS flags; /* see below */ + CK_ULONG ulDeviceError; /* device-dependent error code */ +} CK_SESSION_INFO; + +/* The flags are defined in the following table: + * Bit Flag Mask Meaning + */ +#define CKF_RW_SESSION 0x00000002UL /* session is r/w */ +#define CKF_SERIAL_SESSION 0x00000004UL /* no parallel */ + +typedef CK_SESSION_INFO CK_PTR CK_SESSION_INFO_PTR; + + +/* CK_OBJECT_HANDLE is a token-specific identifier for an + * object + */ +typedef CK_ULONG CK_OBJECT_HANDLE; + +typedef CK_OBJECT_HANDLE CK_PTR CK_OBJECT_HANDLE_PTR; + + +/* CK_OBJECT_CLASS is a value that identifies the classes (or + * types) of objects that Cryptoki recognizes. It is defined + * as follows: + */ +typedef CK_ULONG CK_OBJECT_CLASS; + +/* The following classes of objects are defined: */ +#define CKO_DATA 0x00000000UL +#define CKO_CERTIFICATE 0x00000001UL +#define CKO_PUBLIC_KEY 0x00000002UL +#define CKO_PRIVATE_KEY 0x00000003UL +#define CKO_SECRET_KEY 0x00000004UL +#define CKO_HW_FEATURE 0x00000005UL +#define CKO_DOMAIN_PARAMETERS 0x00000006UL +#define CKO_MECHANISM 0x00000007UL +#define CKO_OTP_KEY 0x00000008UL + +#define CKO_VENDOR_DEFINED 0x80000000UL + +typedef CK_OBJECT_CLASS CK_PTR CK_OBJECT_CLASS_PTR; + +/* CK_HW_FEATURE_TYPE is a value that identifies the hardware feature type + * of an object with CK_OBJECT_CLASS equal to CKO_HW_FEATURE. + */ +typedef CK_ULONG CK_HW_FEATURE_TYPE; + +/* The following hardware feature types are defined */ +#define CKH_MONOTONIC_COUNTER 0x00000001UL +#define CKH_CLOCK 0x00000002UL +#define CKH_USER_INTERFACE 0x00000003UL +#define CKH_VENDOR_DEFINED 0x80000000UL + +/* CK_KEY_TYPE is a value that identifies a key type */ +typedef CK_ULONG CK_KEY_TYPE; + +/* the following key types are defined: */ +#define CKK_RSA 0x00000000UL +#define CKK_DSA 0x00000001UL +#define CKK_DH 0x00000002UL +#define CKK_ECDSA 0x00000003UL /* Deprecated */ +#define CKK_EC 0x00000003UL +#define CKK_X9_42_DH 0x00000004UL +#define CKK_KEA 0x00000005UL +#define CKK_GENERIC_SECRET 0x00000010UL +#define CKK_RC2 0x00000011UL +#define CKK_RC4 0x00000012UL +#define CKK_DES 0x00000013UL +#define CKK_DES2 0x00000014UL +#define CKK_DES3 0x00000015UL +#define CKK_CAST 0x00000016UL +#define CKK_CAST3 0x00000017UL +#define CKK_CAST5 0x00000018UL /* Deprecated */ +#define CKK_CAST128 0x00000018UL +#define CKK_RC5 0x00000019UL +#define CKK_IDEA 0x0000001AUL +#define CKK_SKIPJACK 0x0000001BUL +#define CKK_BATON 0x0000001CUL +#define CKK_JUNIPER 0x0000001DUL +#define CKK_CDMF 0x0000001EUL +#define CKK_AES 0x0000001FUL +#define CKK_BLOWFISH 0x00000020UL +#define CKK_TWOFISH 0x00000021UL +#define CKK_SECURID 0x00000022UL +#define CKK_HOTP 0x00000023UL +#define CKK_ACTI 0x00000024UL +#define CKK_CAMELLIA 0x00000025UL +#define CKK_ARIA 0x00000026UL + +#define CKK_MD5_HMAC 0x00000027UL +#define CKK_SHA_1_HMAC 0x00000028UL +#define CKK_RIPEMD128_HMAC 0x00000029UL +#define CKK_RIPEMD160_HMAC 0x0000002AUL +#define CKK_SHA256_HMAC 0x0000002BUL +#define CKK_SHA384_HMAC 0x0000002CUL +#define CKK_SHA512_HMAC 0x0000002DUL +#define CKK_SHA224_HMAC 0x0000002EUL + +#define CKK_SEED 0x0000002FUL +#define CKK_GOSTR3410 0x00000030UL +#define CKK_GOSTR3411 0x00000031UL +#define CKK_GOST28147 0x00000032UL + + + +#define CKK_VENDOR_DEFINED 0x80000000UL + + +/* CK_CERTIFICATE_TYPE is a value that identifies a certificate + * type + */ +typedef CK_ULONG CK_CERTIFICATE_TYPE; + +#define CK_CERTIFICATE_CATEGORY_UNSPECIFIED 0UL +#define CK_CERTIFICATE_CATEGORY_TOKEN_USER 1UL +#define CK_CERTIFICATE_CATEGORY_AUTHORITY 2UL +#define CK_CERTIFICATE_CATEGORY_OTHER_ENTITY 3UL + +#define CK_SECURITY_DOMAIN_UNSPECIFIED 0UL +#define CK_SECURITY_DOMAIN_MANUFACTURER 1UL +#define CK_SECURITY_DOMAIN_OPERATOR 2UL +#define CK_SECURITY_DOMAIN_THIRD_PARTY 3UL + + +/* The following certificate types are defined: */ +#define CKC_X_509 0x00000000UL +#define CKC_X_509_ATTR_CERT 0x00000001UL +#define CKC_WTLS 0x00000002UL +#define CKC_VENDOR_DEFINED 0x80000000UL + + +/* CK_ATTRIBUTE_TYPE is a value that identifies an attribute + * type + */ +typedef CK_ULONG CK_ATTRIBUTE_TYPE; + +/* The CKF_ARRAY_ATTRIBUTE flag identifies an attribute which + * consists of an array of values. + */ +#define CKF_ARRAY_ATTRIBUTE 0x40000000UL + +/* The following OTP-related defines relate to the CKA_OTP_FORMAT attribute */ +#define CK_OTP_FORMAT_DECIMAL 0UL +#define CK_OTP_FORMAT_HEXADECIMAL 1UL +#define CK_OTP_FORMAT_ALPHANUMERIC 2UL +#define CK_OTP_FORMAT_BINARY 3UL + +/* The following OTP-related defines relate to the CKA_OTP_..._REQUIREMENT + * attributes + */ +#define CK_OTP_PARAM_IGNORED 0UL +#define CK_OTP_PARAM_OPTIONAL 1UL +#define CK_OTP_PARAM_MANDATORY 2UL + +/* The following attribute types are defined: */ +#define CKA_CLASS 0x00000000UL +#define CKA_TOKEN 0x00000001UL +#define CKA_PRIVATE 0x00000002UL +#define CKA_LABEL 0x00000003UL +#define CKA_APPLICATION 0x00000010UL +#define CKA_VALUE 0x00000011UL +#define CKA_OBJECT_ID 0x00000012UL +#define CKA_CERTIFICATE_TYPE 0x00000080UL +#define CKA_ISSUER 0x00000081UL +#define CKA_SERIAL_NUMBER 0x00000082UL +#define CKA_AC_ISSUER 0x00000083UL +#define CKA_OWNER 0x00000084UL +#define CKA_ATTR_TYPES 0x00000085UL +#define CKA_TRUSTED 0x00000086UL +#define CKA_CERTIFICATE_CATEGORY 0x00000087UL +#define CKA_JAVA_MIDP_SECURITY_DOMAIN 0x00000088UL +#define CKA_URL 0x00000089UL +#define CKA_HASH_OF_SUBJECT_PUBLIC_KEY 0x0000008AUL +#define CKA_HASH_OF_ISSUER_PUBLIC_KEY 0x0000008BUL +#define CKA_NAME_HASH_ALGORITHM 0x0000008CUL +#define CKA_CHECK_VALUE 0x00000090UL + +#define CKA_KEY_TYPE 0x00000100UL +#define CKA_SUBJECT 0x00000101UL +#define CKA_ID 0x00000102UL +#define CKA_SENSITIVE 0x00000103UL +#define CKA_ENCRYPT 0x00000104UL +#define CKA_DECRYPT 0x00000105UL +#define CKA_WRAP 0x00000106UL +#define CKA_UNWRAP 0x00000107UL +#define CKA_SIGN 0x00000108UL +#define CKA_SIGN_RECOVER 0x00000109UL +#define CKA_VERIFY 0x0000010AUL +#define CKA_VERIFY_RECOVER 0x0000010BUL +#define CKA_DERIVE 0x0000010CUL +#define CKA_START_DATE 0x00000110UL +#define CKA_END_DATE 0x00000111UL +#define CKA_MODULUS 0x00000120UL +#define CKA_MODULUS_BITS 0x00000121UL +#define CKA_PUBLIC_EXPONENT 0x00000122UL +#define CKA_PRIVATE_EXPONENT 0x00000123UL +#define CKA_PRIME_1 0x00000124UL +#define CKA_PRIME_2 0x00000125UL +#define CKA_EXPONENT_1 0x00000126UL +#define CKA_EXPONENT_2 0x00000127UL +#define CKA_COEFFICIENT 0x00000128UL +#define CKA_PUBLIC_KEY_INFO 0x00000129UL +#define CKA_PRIME 0x00000130UL +#define CKA_SUBPRIME 0x00000131UL +#define CKA_BASE 0x00000132UL + +#define CKA_PRIME_BITS 0x00000133UL +#define CKA_SUBPRIME_BITS 0x00000134UL +#define CKA_SUB_PRIME_BITS CKA_SUBPRIME_BITS + +#define CKA_VALUE_BITS 0x00000160UL +#define CKA_VALUE_LEN 0x00000161UL +#define CKA_EXTRACTABLE 0x00000162UL +#define CKA_LOCAL 0x00000163UL +#define CKA_NEVER_EXTRACTABLE 0x00000164UL +#define CKA_ALWAYS_SENSITIVE 0x00000165UL +#define CKA_KEY_GEN_MECHANISM 0x00000166UL + +#define CKA_MODIFIABLE 0x00000170UL +#define CKA_COPYABLE 0x00000171UL + +#define CKA_DESTROYABLE 0x00000172UL + +#define CKA_ECDSA_PARAMS 0x00000180UL /* Deprecated */ +#define CKA_EC_PARAMS 0x00000180UL + +#define CKA_EC_POINT 0x00000181UL + +#define CKA_SECONDARY_AUTH 0x00000200UL /* Deprecated */ +#define CKA_AUTH_PIN_FLAGS 0x00000201UL /* Deprecated */ + +#define CKA_ALWAYS_AUTHENTICATE 0x00000202UL + +#define CKA_WRAP_WITH_TRUSTED 0x00000210UL +#define CKA_WRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE|0x00000211UL) +#define CKA_UNWRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE|0x00000212UL) +#define CKA_DERIVE_TEMPLATE (CKF_ARRAY_ATTRIBUTE|0x00000213UL) + +#define CKA_OTP_FORMAT 0x00000220UL +#define CKA_OTP_LENGTH 0x00000221UL +#define CKA_OTP_TIME_INTERVAL 0x00000222UL +#define CKA_OTP_USER_FRIENDLY_MODE 0x00000223UL +#define CKA_OTP_CHALLENGE_REQUIREMENT 0x00000224UL +#define CKA_OTP_TIME_REQUIREMENT 0x00000225UL +#define CKA_OTP_COUNTER_REQUIREMENT 0x00000226UL +#define CKA_OTP_PIN_REQUIREMENT 0x00000227UL +#define CKA_OTP_COUNTER 0x0000022EUL +#define CKA_OTP_TIME 0x0000022FUL +#define CKA_OTP_USER_IDENTIFIER 0x0000022AUL +#define CKA_OTP_SERVICE_IDENTIFIER 0x0000022BUL +#define CKA_OTP_SERVICE_LOGO 0x0000022CUL +#define CKA_OTP_SERVICE_LOGO_TYPE 0x0000022DUL + +#define CKA_GOSTR3410_PARAMS 0x00000250UL +#define CKA_GOSTR3411_PARAMS 0x00000251UL +#define CKA_GOST28147_PARAMS 0x00000252UL + +#define CKA_HW_FEATURE_TYPE 0x00000300UL +#define CKA_RESET_ON_INIT 0x00000301UL +#define CKA_HAS_RESET 0x00000302UL + +#define CKA_PIXEL_X 0x00000400UL +#define CKA_PIXEL_Y 0x00000401UL +#define CKA_RESOLUTION 0x00000402UL +#define CKA_CHAR_ROWS 0x00000403UL +#define CKA_CHAR_COLUMNS 0x00000404UL +#define CKA_COLOR 0x00000405UL +#define CKA_BITS_PER_PIXEL 0x00000406UL +#define CKA_CHAR_SETS 0x00000480UL +#define CKA_ENCODING_METHODS 0x00000481UL +#define CKA_MIME_TYPES 0x00000482UL +#define CKA_MECHANISM_TYPE 0x00000500UL +#define CKA_REQUIRED_CMS_ATTRIBUTES 0x00000501UL +#define CKA_DEFAULT_CMS_ATTRIBUTES 0x00000502UL +#define CKA_SUPPORTED_CMS_ATTRIBUTES 0x00000503UL +#define CKA_ALLOWED_MECHANISMS (CKF_ARRAY_ATTRIBUTE|0x00000600UL) + +#define CKA_VENDOR_DEFINED 0x80000000UL + +/* CK_ATTRIBUTE is a structure that includes the type, length + * and value of an attribute + */ +typedef struct CK_ATTRIBUTE { + CK_ATTRIBUTE_TYPE type; + CK_VOID_PTR pValue; + CK_ULONG ulValueLen; /* in bytes */ +} CK_ATTRIBUTE; + +typedef CK_ATTRIBUTE CK_PTR CK_ATTRIBUTE_PTR; + +/* CK_DATE is a structure that defines a date */ +typedef struct CK_DATE{ + CK_CHAR year[4]; /* the year ("1900" - "9999") */ + CK_CHAR month[2]; /* the month ("01" - "12") */ + CK_CHAR day[2]; /* the day ("01" - "31") */ +} CK_DATE; + + +/* CK_MECHANISM_TYPE is a value that identifies a mechanism + * type + */ +typedef CK_ULONG CK_MECHANISM_TYPE; + +/* the following mechanism types are defined: */ +#define CKM_RSA_PKCS_KEY_PAIR_GEN 0x00000000UL +#define CKM_RSA_PKCS 0x00000001UL +#define CKM_RSA_9796 0x00000002UL +#define CKM_RSA_X_509 0x00000003UL + +#define CKM_MD2_RSA_PKCS 0x00000004UL +#define CKM_MD5_RSA_PKCS 0x00000005UL +#define CKM_SHA1_RSA_PKCS 0x00000006UL + +#define CKM_RIPEMD128_RSA_PKCS 0x00000007UL +#define CKM_RIPEMD160_RSA_PKCS 0x00000008UL +#define CKM_RSA_PKCS_OAEP 0x00000009UL + +#define CKM_RSA_X9_31_KEY_PAIR_GEN 0x0000000AUL +#define CKM_RSA_X9_31 0x0000000BUL +#define CKM_SHA1_RSA_X9_31 0x0000000CUL +#define CKM_RSA_PKCS_PSS 0x0000000DUL +#define CKM_SHA1_RSA_PKCS_PSS 0x0000000EUL + +#define CKM_DSA_KEY_PAIR_GEN 0x00000010UL +#define CKM_DSA 0x00000011UL +#define CKM_DSA_SHA1 0x00000012UL +#define CKM_DSA_SHA224 0x00000013UL +#define CKM_DSA_SHA256 0x00000014UL +#define CKM_DSA_SHA384 0x00000015UL +#define CKM_DSA_SHA512 0x00000016UL + +#define CKM_DH_PKCS_KEY_PAIR_GEN 0x00000020UL +#define CKM_DH_PKCS_DERIVE 0x00000021UL + +#define CKM_X9_42_DH_KEY_PAIR_GEN 0x00000030UL +#define CKM_X9_42_DH_DERIVE 0x00000031UL +#define CKM_X9_42_DH_HYBRID_DERIVE 0x00000032UL +#define CKM_X9_42_MQV_DERIVE 0x00000033UL + +#define CKM_SHA256_RSA_PKCS 0x00000040UL +#define CKM_SHA384_RSA_PKCS 0x00000041UL +#define CKM_SHA512_RSA_PKCS 0x00000042UL +#define CKM_SHA256_RSA_PKCS_PSS 0x00000043UL +#define CKM_SHA384_RSA_PKCS_PSS 0x00000044UL +#define CKM_SHA512_RSA_PKCS_PSS 0x00000045UL + +#define CKM_SHA224_RSA_PKCS 0x00000046UL +#define CKM_SHA224_RSA_PKCS_PSS 0x00000047UL + +#define CKM_SHA512_224 0x00000048UL +#define CKM_SHA512_224_HMAC 0x00000049UL +#define CKM_SHA512_224_HMAC_GENERAL 0x0000004AUL +#define CKM_SHA512_224_KEY_DERIVATION 0x0000004BUL +#define CKM_SHA512_256 0x0000004CUL +#define CKM_SHA512_256_HMAC 0x0000004DUL +#define CKM_SHA512_256_HMAC_GENERAL 0x0000004EUL +#define CKM_SHA512_256_KEY_DERIVATION 0x0000004FUL + +#define CKM_SHA512_T 0x00000050UL +#define CKM_SHA512_T_HMAC 0x00000051UL +#define CKM_SHA512_T_HMAC_GENERAL 0x00000052UL +#define CKM_SHA512_T_KEY_DERIVATION 0x00000053UL + +#define CKM_RC2_KEY_GEN 0x00000100UL +#define CKM_RC2_ECB 0x00000101UL +#define CKM_RC2_CBC 0x00000102UL +#define CKM_RC2_MAC 0x00000103UL + +#define CKM_RC2_MAC_GENERAL 0x00000104UL +#define CKM_RC2_CBC_PAD 0x00000105UL + +#define CKM_RC4_KEY_GEN 0x00000110UL +#define CKM_RC4 0x00000111UL +#define CKM_DES_KEY_GEN 0x00000120UL +#define CKM_DES_ECB 0x00000121UL +#define CKM_DES_CBC 0x00000122UL +#define CKM_DES_MAC 0x00000123UL + +#define CKM_DES_MAC_GENERAL 0x00000124UL +#define CKM_DES_CBC_PAD 0x00000125UL + +#define CKM_DES2_KEY_GEN 0x00000130UL +#define CKM_DES3_KEY_GEN 0x00000131UL +#define CKM_DES3_ECB 0x00000132UL +#define CKM_DES3_CBC 0x00000133UL +#define CKM_DES3_MAC 0x00000134UL + +#define CKM_DES3_MAC_GENERAL 0x00000135UL +#define CKM_DES3_CBC_PAD 0x00000136UL +#define CKM_DES3_CMAC_GENERAL 0x00000137UL +#define CKM_DES3_CMAC 0x00000138UL +#define CKM_CDMF_KEY_GEN 0x00000140UL +#define CKM_CDMF_ECB 0x00000141UL +#define CKM_CDMF_CBC 0x00000142UL +#define CKM_CDMF_MAC 0x00000143UL +#define CKM_CDMF_MAC_GENERAL 0x00000144UL +#define CKM_CDMF_CBC_PAD 0x00000145UL + +#define CKM_DES_OFB64 0x00000150UL +#define CKM_DES_OFB8 0x00000151UL +#define CKM_DES_CFB64 0x00000152UL +#define CKM_DES_CFB8 0x00000153UL + +#define CKM_MD2 0x00000200UL + +#define CKM_MD2_HMAC 0x00000201UL +#define CKM_MD2_HMAC_GENERAL 0x00000202UL + +#define CKM_MD5 0x00000210UL + +#define CKM_MD5_HMAC 0x00000211UL +#define CKM_MD5_HMAC_GENERAL 0x00000212UL + +#define CKM_SHA_1 0x00000220UL + +#define CKM_SHA_1_HMAC 0x00000221UL +#define CKM_SHA_1_HMAC_GENERAL 0x00000222UL + +#define CKM_RIPEMD128 0x00000230UL +#define CKM_RIPEMD128_HMAC 0x00000231UL +#define CKM_RIPEMD128_HMAC_GENERAL 0x00000232UL +#define CKM_RIPEMD160 0x00000240UL +#define CKM_RIPEMD160_HMAC 0x00000241UL +#define CKM_RIPEMD160_HMAC_GENERAL 0x00000242UL + +#define CKM_SHA256 0x00000250UL +#define CKM_SHA256_HMAC 0x00000251UL +#define CKM_SHA256_HMAC_GENERAL 0x00000252UL +#define CKM_SHA224 0x00000255UL +#define CKM_SHA224_HMAC 0x00000256UL +#define CKM_SHA224_HMAC_GENERAL 0x00000257UL +#define CKM_SHA384 0x00000260UL +#define CKM_SHA384_HMAC 0x00000261UL +#define CKM_SHA384_HMAC_GENERAL 0x00000262UL +#define CKM_SHA512 0x00000270UL +#define CKM_SHA512_HMAC 0x00000271UL +#define CKM_SHA512_HMAC_GENERAL 0x00000272UL +#define CKM_SECURID_KEY_GEN 0x00000280UL +#define CKM_SECURID 0x00000282UL +#define CKM_HOTP_KEY_GEN 0x00000290UL +#define CKM_HOTP 0x00000291UL +#define CKM_ACTI 0x000002A0UL +#define CKM_ACTI_KEY_GEN 0x000002A1UL + +#define CKM_CAST_KEY_GEN 0x00000300UL +#define CKM_CAST_ECB 0x00000301UL +#define CKM_CAST_CBC 0x00000302UL +#define CKM_CAST_MAC 0x00000303UL +#define CKM_CAST_MAC_GENERAL 0x00000304UL +#define CKM_CAST_CBC_PAD 0x00000305UL +#define CKM_CAST3_KEY_GEN 0x00000310UL +#define CKM_CAST3_ECB 0x00000311UL +#define CKM_CAST3_CBC 0x00000312UL +#define CKM_CAST3_MAC 0x00000313UL +#define CKM_CAST3_MAC_GENERAL 0x00000314UL +#define CKM_CAST3_CBC_PAD 0x00000315UL +/* Note that CAST128 and CAST5 are the same algorithm */ +#define CKM_CAST5_KEY_GEN 0x00000320UL +#define CKM_CAST128_KEY_GEN 0x00000320UL +#define CKM_CAST5_ECB 0x00000321UL +#define CKM_CAST128_ECB 0x00000321UL +#define CKM_CAST5_CBC 0x00000322UL /* Deprecated */ +#define CKM_CAST128_CBC 0x00000322UL +#define CKM_CAST5_MAC 0x00000323UL /* Deprecated */ +#define CKM_CAST128_MAC 0x00000323UL +#define CKM_CAST5_MAC_GENERAL 0x00000324UL /* Deprecated */ +#define CKM_CAST128_MAC_GENERAL 0x00000324UL +#define CKM_CAST5_CBC_PAD 0x00000325UL /* Deprecated */ +#define CKM_CAST128_CBC_PAD 0x00000325UL +#define CKM_RC5_KEY_GEN 0x00000330UL +#define CKM_RC5_ECB 0x00000331UL +#define CKM_RC5_CBC 0x00000332UL +#define CKM_RC5_MAC 0x00000333UL +#define CKM_RC5_MAC_GENERAL 0x00000334UL +#define CKM_RC5_CBC_PAD 0x00000335UL +#define CKM_IDEA_KEY_GEN 0x00000340UL +#define CKM_IDEA_ECB 0x00000341UL +#define CKM_IDEA_CBC 0x00000342UL +#define CKM_IDEA_MAC 0x00000343UL +#define CKM_IDEA_MAC_GENERAL 0x00000344UL +#define CKM_IDEA_CBC_PAD 0x00000345UL +#define CKM_GENERIC_SECRET_KEY_GEN 0x00000350UL +#define CKM_CONCATENATE_BASE_AND_KEY 0x00000360UL +#define CKM_CONCATENATE_BASE_AND_DATA 0x00000362UL +#define CKM_CONCATENATE_DATA_AND_BASE 0x00000363UL +#define CKM_XOR_BASE_AND_DATA 0x00000364UL +#define CKM_EXTRACT_KEY_FROM_KEY 0x00000365UL +#define CKM_SSL3_PRE_MASTER_KEY_GEN 0x00000370UL +#define CKM_SSL3_MASTER_KEY_DERIVE 0x00000371UL +#define CKM_SSL3_KEY_AND_MAC_DERIVE 0x00000372UL + +#define CKM_SSL3_MASTER_KEY_DERIVE_DH 0x00000373UL +#define CKM_TLS_PRE_MASTER_KEY_GEN 0x00000374UL +#define CKM_TLS_MASTER_KEY_DERIVE 0x00000375UL +#define CKM_TLS_KEY_AND_MAC_DERIVE 0x00000376UL +#define CKM_TLS_MASTER_KEY_DERIVE_DH 0x00000377UL + +#define CKM_TLS_PRF 0x00000378UL + +#define CKM_SSL3_MD5_MAC 0x00000380UL +#define CKM_SSL3_SHA1_MAC 0x00000381UL +#define CKM_MD5_KEY_DERIVATION 0x00000390UL +#define CKM_MD2_KEY_DERIVATION 0x00000391UL +#define CKM_SHA1_KEY_DERIVATION 0x00000392UL + +#define CKM_SHA256_KEY_DERIVATION 0x00000393UL +#define CKM_SHA384_KEY_DERIVATION 0x00000394UL +#define CKM_SHA512_KEY_DERIVATION 0x00000395UL +#define CKM_SHA224_KEY_DERIVATION 0x00000396UL + +#define CKM_PBE_MD2_DES_CBC 0x000003A0UL +#define CKM_PBE_MD5_DES_CBC 0x000003A1UL +#define CKM_PBE_MD5_CAST_CBC 0x000003A2UL +#define CKM_PBE_MD5_CAST3_CBC 0x000003A3UL +#define CKM_PBE_MD5_CAST5_CBC 0x000003A4UL /* Deprecated */ +#define CKM_PBE_MD5_CAST128_CBC 0x000003A4UL +#define CKM_PBE_SHA1_CAST5_CBC 0x000003A5UL /* Deprecated */ +#define CKM_PBE_SHA1_CAST128_CBC 0x000003A5UL +#define CKM_PBE_SHA1_RC4_128 0x000003A6UL +#define CKM_PBE_SHA1_RC4_40 0x000003A7UL +#define CKM_PBE_SHA1_DES3_EDE_CBC 0x000003A8UL +#define CKM_PBE_SHA1_DES2_EDE_CBC 0x000003A9UL +#define CKM_PBE_SHA1_RC2_128_CBC 0x000003AAUL +#define CKM_PBE_SHA1_RC2_40_CBC 0x000003ABUL + +#define CKM_PKCS5_PBKD2 0x000003B0UL + +#define CKM_PBA_SHA1_WITH_SHA1_HMAC 0x000003C0UL + +#define CKM_WTLS_PRE_MASTER_KEY_GEN 0x000003D0UL +#define CKM_WTLS_MASTER_KEY_DERIVE 0x000003D1UL +#define CKM_WTLS_MASTER_KEY_DERIVE_DH_ECC 0x000003D2UL +#define CKM_WTLS_PRF 0x000003D3UL +#define CKM_WTLS_SERVER_KEY_AND_MAC_DERIVE 0x000003D4UL +#define CKM_WTLS_CLIENT_KEY_AND_MAC_DERIVE 0x000003D5UL + +#define CKM_TLS10_MAC_SERVER 0x000003D6UL +#define CKM_TLS10_MAC_CLIENT 0x000003D7UL +#define CKM_TLS12_MAC 0x000003D8UL +#define CKM_TLS12_KDF 0x000003D9UL +#define CKM_TLS12_MASTER_KEY_DERIVE 0x000003E0UL +#define CKM_TLS12_KEY_AND_MAC_DERIVE 0x000003E1UL +#define CKM_TLS12_MASTER_KEY_DERIVE_DH 0x000003E2UL +#define CKM_TLS12_KEY_SAFE_DERIVE 0x000003E3UL +#define CKM_TLS_MAC 0x000003E4UL +#define CKM_TLS_KDF 0x000003E5UL + +#define CKM_KEY_WRAP_LYNKS 0x00000400UL +#define CKM_KEY_WRAP_SET_OAEP 0x00000401UL + +#define CKM_CMS_SIG 0x00000500UL +#define CKM_KIP_DERIVE 0x00000510UL +#define CKM_KIP_WRAP 0x00000511UL +#define CKM_KIP_MAC 0x00000512UL + +#define CKM_CAMELLIA_KEY_GEN 0x00000550UL +#define CKM_CAMELLIA_ECB 0x00000551UL +#define CKM_CAMELLIA_CBC 0x00000552UL +#define CKM_CAMELLIA_MAC 0x00000553UL +#define CKM_CAMELLIA_MAC_GENERAL 0x00000554UL +#define CKM_CAMELLIA_CBC_PAD 0x00000555UL +#define CKM_CAMELLIA_ECB_ENCRYPT_DATA 0x00000556UL +#define CKM_CAMELLIA_CBC_ENCRYPT_DATA 0x00000557UL +#define CKM_CAMELLIA_CTR 0x00000558UL + +#define CKM_ARIA_KEY_GEN 0x00000560UL +#define CKM_ARIA_ECB 0x00000561UL +#define CKM_ARIA_CBC 0x00000562UL +#define CKM_ARIA_MAC 0x00000563UL +#define CKM_ARIA_MAC_GENERAL 0x00000564UL +#define CKM_ARIA_CBC_PAD 0x00000565UL +#define CKM_ARIA_ECB_ENCRYPT_DATA 0x00000566UL +#define CKM_ARIA_CBC_ENCRYPT_DATA 0x00000567UL + +#define CKM_SEED_KEY_GEN 0x00000650UL +#define CKM_SEED_ECB 0x00000651UL +#define CKM_SEED_CBC 0x00000652UL +#define CKM_SEED_MAC 0x00000653UL +#define CKM_SEED_MAC_GENERAL 0x00000654UL +#define CKM_SEED_CBC_PAD 0x00000655UL +#define CKM_SEED_ECB_ENCRYPT_DATA 0x00000656UL +#define CKM_SEED_CBC_ENCRYPT_DATA 0x00000657UL + +#define CKM_SKIPJACK_KEY_GEN 0x00001000UL +#define CKM_SKIPJACK_ECB64 0x00001001UL +#define CKM_SKIPJACK_CBC64 0x00001002UL +#define CKM_SKIPJACK_OFB64 0x00001003UL +#define CKM_SKIPJACK_CFB64 0x00001004UL +#define CKM_SKIPJACK_CFB32 0x00001005UL +#define CKM_SKIPJACK_CFB16 0x00001006UL +#define CKM_SKIPJACK_CFB8 0x00001007UL +#define CKM_SKIPJACK_WRAP 0x00001008UL +#define CKM_SKIPJACK_PRIVATE_WRAP 0x00001009UL +#define CKM_SKIPJACK_RELAYX 0x0000100aUL +#define CKM_KEA_KEY_PAIR_GEN 0x00001010UL +#define CKM_KEA_KEY_DERIVE 0x00001011UL +#define CKM_KEA_DERIVE 0x00001012UL +#define CKM_FORTEZZA_TIMESTAMP 0x00001020UL +#define CKM_BATON_KEY_GEN 0x00001030UL +#define CKM_BATON_ECB128 0x00001031UL +#define CKM_BATON_ECB96 0x00001032UL +#define CKM_BATON_CBC128 0x00001033UL +#define CKM_BATON_COUNTER 0x00001034UL +#define CKM_BATON_SHUFFLE 0x00001035UL +#define CKM_BATON_WRAP 0x00001036UL + +#define CKM_ECDSA_KEY_PAIR_GEN 0x00001040UL /* Deprecated */ +#define CKM_EC_KEY_PAIR_GEN 0x00001040UL + +#define CKM_ECDSA 0x00001041UL +#define CKM_ECDSA_SHA1 0x00001042UL +#define CKM_ECDSA_SHA224 0x00001043UL +#define CKM_ECDSA_SHA256 0x00001044UL +#define CKM_ECDSA_SHA384 0x00001045UL +#define CKM_ECDSA_SHA512 0x00001046UL + +#define CKM_ECDH1_DERIVE 0x00001050UL +#define CKM_ECDH1_COFACTOR_DERIVE 0x00001051UL +#define CKM_ECMQV_DERIVE 0x00001052UL + +#define CKM_ECDH_AES_KEY_WRAP 0x00001053UL +#define CKM_RSA_AES_KEY_WRAP 0x00001054UL + +#define CKM_JUNIPER_KEY_GEN 0x00001060UL +#define CKM_JUNIPER_ECB128 0x00001061UL +#define CKM_JUNIPER_CBC128 0x00001062UL +#define CKM_JUNIPER_COUNTER 0x00001063UL +#define CKM_JUNIPER_SHUFFLE 0x00001064UL +#define CKM_JUNIPER_WRAP 0x00001065UL +#define CKM_FASTHASH 0x00001070UL + +#define CKM_AES_KEY_GEN 0x00001080UL +#define CKM_AES_ECB 0x00001081UL +#define CKM_AES_CBC 0x00001082UL +#define CKM_AES_MAC 0x00001083UL +#define CKM_AES_MAC_GENERAL 0x00001084UL +#define CKM_AES_CBC_PAD 0x00001085UL +#define CKM_AES_CTR 0x00001086UL +#define CKM_AES_GCM 0x00001087UL +#define CKM_AES_CCM 0x00001088UL +#define CKM_AES_CTS 0x00001089UL +#define CKM_AES_CMAC 0x0000108AUL +#define CKM_AES_CMAC_GENERAL 0x0000108BUL + +#define CKM_AES_XCBC_MAC 0x0000108CUL +#define CKM_AES_XCBC_MAC_96 0x0000108DUL +#define CKM_AES_GMAC 0x0000108EUL + +#define CKM_BLOWFISH_KEY_GEN 0x00001090UL +#define CKM_BLOWFISH_CBC 0x00001091UL +#define CKM_TWOFISH_KEY_GEN 0x00001092UL +#define CKM_TWOFISH_CBC 0x00001093UL +#define CKM_BLOWFISH_CBC_PAD 0x00001094UL +#define CKM_TWOFISH_CBC_PAD 0x00001095UL + +#define CKM_DES_ECB_ENCRYPT_DATA 0x00001100UL +#define CKM_DES_CBC_ENCRYPT_DATA 0x00001101UL +#define CKM_DES3_ECB_ENCRYPT_DATA 0x00001102UL +#define CKM_DES3_CBC_ENCRYPT_DATA 0x00001103UL +#define CKM_AES_ECB_ENCRYPT_DATA 0x00001104UL +#define CKM_AES_CBC_ENCRYPT_DATA 0x00001105UL + +#define CKM_GOSTR3410_KEY_PAIR_GEN 0x00001200UL +#define CKM_GOSTR3410 0x00001201UL +#define CKM_GOSTR3410_WITH_GOSTR3411 0x00001202UL +#define CKM_GOSTR3410_KEY_WRAP 0x00001203UL +#define CKM_GOSTR3410_DERIVE 0x00001204UL +#define CKM_GOSTR3411 0x00001210UL +#define CKM_GOSTR3411_HMAC 0x00001211UL +#define CKM_GOST28147_KEY_GEN 0x00001220UL +#define CKM_GOST28147_ECB 0x00001221UL +#define CKM_GOST28147 0x00001222UL +#define CKM_GOST28147_MAC 0x00001223UL +#define CKM_GOST28147_KEY_WRAP 0x00001224UL + +#define CKM_DSA_PARAMETER_GEN 0x00002000UL +#define CKM_DH_PKCS_PARAMETER_GEN 0x00002001UL +#define CKM_X9_42_DH_PARAMETER_GEN 0x00002002UL +#define CKM_DSA_PROBABLISTIC_PARAMETER_GEN 0x00002003UL +#define CKM_DSA_SHAWE_TAYLOR_PARAMETER_GEN 0x00002004UL + +#define CKM_AES_OFB 0x00002104UL +#define CKM_AES_CFB64 0x00002105UL +#define CKM_AES_CFB8 0x00002106UL +#define CKM_AES_CFB128 0x00002107UL + +#define CKM_AES_CFB1 0x00002108UL +#define CKM_AES_KEY_WRAP 0x00002109UL /* WAS: 0x00001090 */ +#define CKM_AES_KEY_WRAP_PAD 0x0000210AUL /* WAS: 0x00001091 */ + +#define CKM_RSA_PKCS_TPM_1_1 0x00004001UL +#define CKM_RSA_PKCS_OAEP_TPM_1_1 0x00004002UL + +#define CKM_VENDOR_DEFINED 0x80000000UL + +typedef CK_MECHANISM_TYPE CK_PTR CK_MECHANISM_TYPE_PTR; + + +/* CK_MECHANISM is a structure that specifies a particular + * mechanism + */ +typedef struct CK_MECHANISM { + CK_MECHANISM_TYPE mechanism; + CK_VOID_PTR pParameter; + CK_ULONG ulParameterLen; /* in bytes */ +} CK_MECHANISM; + +typedef CK_MECHANISM CK_PTR CK_MECHANISM_PTR; + + +/* CK_MECHANISM_INFO provides information about a particular + * mechanism + */ +typedef struct CK_MECHANISM_INFO { + CK_ULONG ulMinKeySize; + CK_ULONG ulMaxKeySize; + CK_FLAGS flags; +} CK_MECHANISM_INFO; + +/* The flags are defined as follows: + * Bit Flag Mask Meaning */ +#define CKF_HW 0x00000001UL /* performed by HW */ + +/* Specify whether or not a mechanism can be used for a particular task */ +#define CKF_ENCRYPT 0x00000100UL +#define CKF_DECRYPT 0x00000200UL +#define CKF_DIGEST 0x00000400UL +#define CKF_SIGN 0x00000800UL +#define CKF_SIGN_RECOVER 0x00001000UL +#define CKF_VERIFY 0x00002000UL +#define CKF_VERIFY_RECOVER 0x00004000UL +#define CKF_GENERATE 0x00008000UL +#define CKF_GENERATE_KEY_PAIR 0x00010000UL +#define CKF_WRAP 0x00020000UL +#define CKF_UNWRAP 0x00040000UL +#define CKF_DERIVE 0x00080000UL + +/* Describe a token's EC capabilities not available in mechanism + * information. + */ +#define CKF_EC_F_P 0x00100000UL +#define CKF_EC_F_2M 0x00200000UL +#define CKF_EC_ECPARAMETERS 0x00400000UL +#define CKF_EC_NAMEDCURVE 0x00800000UL +#define CKF_EC_UNCOMPRESS 0x01000000UL +#define CKF_EC_COMPRESS 0x02000000UL + +#define CKF_EXTENSION 0x80000000UL + +typedef CK_MECHANISM_INFO CK_PTR CK_MECHANISM_INFO_PTR; + +/* CK_RV is a value that identifies the return value of a + * Cryptoki function + */ +typedef CK_ULONG CK_RV; + +#define CKR_OK 0x00000000UL +#define CKR_CANCEL 0x00000001UL +#define CKR_HOST_MEMORY 0x00000002UL +#define CKR_SLOT_ID_INVALID 0x00000003UL + +#define CKR_GENERAL_ERROR 0x00000005UL +#define CKR_FUNCTION_FAILED 0x00000006UL + +#define CKR_ARGUMENTS_BAD 0x00000007UL +#define CKR_NO_EVENT 0x00000008UL +#define CKR_NEED_TO_CREATE_THREADS 0x00000009UL +#define CKR_CANT_LOCK 0x0000000AUL + +#define CKR_ATTRIBUTE_READ_ONLY 0x00000010UL +#define CKR_ATTRIBUTE_SENSITIVE 0x00000011UL +#define CKR_ATTRIBUTE_TYPE_INVALID 0x00000012UL +#define CKR_ATTRIBUTE_VALUE_INVALID 0x00000013UL + +#define CKR_ACTION_PROHIBITED 0x0000001BUL + +#define CKR_DATA_INVALID 0x00000020UL +#define CKR_DATA_LEN_RANGE 0x00000021UL +#define CKR_DEVICE_ERROR 0x00000030UL +#define CKR_DEVICE_MEMORY 0x00000031UL +#define CKR_DEVICE_REMOVED 0x00000032UL +#define CKR_ENCRYPTED_DATA_INVALID 0x00000040UL +#define CKR_ENCRYPTED_DATA_LEN_RANGE 0x00000041UL +#define CKR_FUNCTION_CANCELED 0x00000050UL +#define CKR_FUNCTION_NOT_PARALLEL 0x00000051UL + +#define CKR_FUNCTION_NOT_SUPPORTED 0x00000054UL + +#define CKR_KEY_HANDLE_INVALID 0x00000060UL + +#define CKR_KEY_SIZE_RANGE 0x00000062UL +#define CKR_KEY_TYPE_INCONSISTENT 0x00000063UL + +#define CKR_KEY_NOT_NEEDED 0x00000064UL +#define CKR_KEY_CHANGED 0x00000065UL +#define CKR_KEY_NEEDED 0x00000066UL +#define CKR_KEY_INDIGESTIBLE 0x00000067UL +#define CKR_KEY_FUNCTION_NOT_PERMITTED 0x00000068UL +#define CKR_KEY_NOT_WRAPPABLE 0x00000069UL +#define CKR_KEY_UNEXTRACTABLE 0x0000006AUL + +#define CKR_MECHANISM_INVALID 0x00000070UL +#define CKR_MECHANISM_PARAM_INVALID 0x00000071UL + +#define CKR_OBJECT_HANDLE_INVALID 0x00000082UL +#define CKR_OPERATION_ACTIVE 0x00000090UL +#define CKR_OPERATION_NOT_INITIALIZED 0x00000091UL +#define CKR_PIN_INCORRECT 0x000000A0UL +#define CKR_PIN_INVALID 0x000000A1UL +#define CKR_PIN_LEN_RANGE 0x000000A2UL + +#define CKR_PIN_EXPIRED 0x000000A3UL +#define CKR_PIN_LOCKED 0x000000A4UL + +#define CKR_SESSION_CLOSED 0x000000B0UL +#define CKR_SESSION_COUNT 0x000000B1UL +#define CKR_SESSION_HANDLE_INVALID 0x000000B3UL +#define CKR_SESSION_PARALLEL_NOT_SUPPORTED 0x000000B4UL +#define CKR_SESSION_READ_ONLY 0x000000B5UL +#define CKR_SESSION_EXISTS 0x000000B6UL + +#define CKR_SESSION_READ_ONLY_EXISTS 0x000000B7UL +#define CKR_SESSION_READ_WRITE_SO_EXISTS 0x000000B8UL + +#define CKR_SIGNATURE_INVALID 0x000000C0UL +#define CKR_SIGNATURE_LEN_RANGE 0x000000C1UL +#define CKR_TEMPLATE_INCOMPLETE 0x000000D0UL +#define CKR_TEMPLATE_INCONSISTENT 0x000000D1UL +#define CKR_TOKEN_NOT_PRESENT 0x000000E0UL +#define CKR_TOKEN_NOT_RECOGNIZED 0x000000E1UL +#define CKR_TOKEN_WRITE_PROTECTED 0x000000E2UL +#define CKR_UNWRAPPING_KEY_HANDLE_INVALID 0x000000F0UL +#define CKR_UNWRAPPING_KEY_SIZE_RANGE 0x000000F1UL +#define CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT 0x000000F2UL +#define CKR_USER_ALREADY_LOGGED_IN 0x00000100UL +#define CKR_USER_NOT_LOGGED_IN 0x00000101UL +#define CKR_USER_PIN_NOT_INITIALIZED 0x00000102UL +#define CKR_USER_TYPE_INVALID 0x00000103UL + +#define CKR_USER_ANOTHER_ALREADY_LOGGED_IN 0x00000104UL +#define CKR_USER_TOO_MANY_TYPES 0x00000105UL + +#define CKR_WRAPPED_KEY_INVALID 0x00000110UL +#define CKR_WRAPPED_KEY_LEN_RANGE 0x00000112UL +#define CKR_WRAPPING_KEY_HANDLE_INVALID 0x00000113UL +#define CKR_WRAPPING_KEY_SIZE_RANGE 0x00000114UL +#define CKR_WRAPPING_KEY_TYPE_INCONSISTENT 0x00000115UL +#define CKR_RANDOM_SEED_NOT_SUPPORTED 0x00000120UL + +#define CKR_RANDOM_NO_RNG 0x00000121UL + +#define CKR_DOMAIN_PARAMS_INVALID 0x00000130UL + +#define CKR_CURVE_NOT_SUPPORTED 0x00000140UL + +#define CKR_BUFFER_TOO_SMALL 0x00000150UL +#define CKR_SAVED_STATE_INVALID 0x00000160UL +#define CKR_INFORMATION_SENSITIVE 0x00000170UL +#define CKR_STATE_UNSAVEABLE 0x00000180UL + +#define CKR_CRYPTOKI_NOT_INITIALIZED 0x00000190UL +#define CKR_CRYPTOKI_ALREADY_INITIALIZED 0x00000191UL +#define CKR_MUTEX_BAD 0x000001A0UL +#define CKR_MUTEX_NOT_LOCKED 0x000001A1UL + +#define CKR_NEW_PIN_MODE 0x000001B0UL +#define CKR_NEXT_OTP 0x000001B1UL + +#define CKR_EXCEEDED_MAX_ITERATIONS 0x000001B5UL +#define CKR_FIPS_SELF_TEST_FAILED 0x000001B6UL +#define CKR_LIBRARY_LOAD_FAILED 0x000001B7UL +#define CKR_PIN_TOO_WEAK 0x000001B8UL +#define CKR_PUBLIC_KEY_INVALID 0x000001B9UL + +#define CKR_FUNCTION_REJECTED 0x00000200UL + +#define CKR_VENDOR_DEFINED 0x80000000UL + + +/* CK_NOTIFY is an application callback that processes events */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_NOTIFY)( + CK_SESSION_HANDLE hSession, /* the session's handle */ + CK_NOTIFICATION event, + CK_VOID_PTR pApplication /* passed to C_OpenSession */ +); + + +/* CK_FUNCTION_LIST is a structure holding a Cryptoki spec + * version and pointers of appropriate types to all the + * Cryptoki functions + */ +typedef struct CK_FUNCTION_LIST CK_FUNCTION_LIST; + +typedef CK_FUNCTION_LIST CK_PTR CK_FUNCTION_LIST_PTR; + +typedef CK_FUNCTION_LIST_PTR CK_PTR CK_FUNCTION_LIST_PTR_PTR; + + +/* CK_CREATEMUTEX is an application callback for creating a + * mutex object + */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_CREATEMUTEX)( + CK_VOID_PTR_PTR ppMutex /* location to receive ptr to mutex */ +); + + +/* CK_DESTROYMUTEX is an application callback for destroying a + * mutex object + */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_DESTROYMUTEX)( + CK_VOID_PTR pMutex /* pointer to mutex */ +); + + +/* CK_LOCKMUTEX is an application callback for locking a mutex */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_LOCKMUTEX)( + CK_VOID_PTR pMutex /* pointer to mutex */ +); + + +/* CK_UNLOCKMUTEX is an application callback for unlocking a + * mutex + */ +typedef CK_CALLBACK_FUNCTION(CK_RV, CK_UNLOCKMUTEX)( + CK_VOID_PTR pMutex /* pointer to mutex */ +); + + +/* CK_C_INITIALIZE_ARGS provides the optional arguments to + * C_Initialize + */ +typedef struct CK_C_INITIALIZE_ARGS { + CK_CREATEMUTEX CreateMutex; + CK_DESTROYMUTEX DestroyMutex; + CK_LOCKMUTEX LockMutex; + CK_UNLOCKMUTEX UnlockMutex; + CK_FLAGS flags; + CK_VOID_PTR pReserved; +} CK_C_INITIALIZE_ARGS; + +/* flags: bit flags that provide capabilities of the slot + * Bit Flag Mask Meaning + */ +#define CKF_LIBRARY_CANT_CREATE_OS_THREADS 0x00000001UL +#define CKF_OS_LOCKING_OK 0x00000002UL + +typedef CK_C_INITIALIZE_ARGS CK_PTR CK_C_INITIALIZE_ARGS_PTR; + + +/* additional flags for parameters to functions */ + +/* CKF_DONT_BLOCK is for the function C_WaitForSlotEvent */ +#define CKF_DONT_BLOCK 1 + +/* CK_RSA_PKCS_MGF_TYPE is used to indicate the Message + * Generation Function (MGF) applied to a message block when + * formatting a message block for the PKCS #1 OAEP encryption + * scheme. + */ +typedef CK_ULONG CK_RSA_PKCS_MGF_TYPE; + +typedef CK_RSA_PKCS_MGF_TYPE CK_PTR CK_RSA_PKCS_MGF_TYPE_PTR; + +/* The following MGFs are defined */ +#define CKG_MGF1_SHA1 0x00000001UL +#define CKG_MGF1_SHA256 0x00000002UL +#define CKG_MGF1_SHA384 0x00000003UL +#define CKG_MGF1_SHA512 0x00000004UL +#define CKG_MGF1_SHA224 0x00000005UL + +/* CK_RSA_PKCS_OAEP_SOURCE_TYPE is used to indicate the source + * of the encoding parameter when formatting a message block + * for the PKCS #1 OAEP encryption scheme. + */ +typedef CK_ULONG CK_RSA_PKCS_OAEP_SOURCE_TYPE; + +typedef CK_RSA_PKCS_OAEP_SOURCE_TYPE CK_PTR CK_RSA_PKCS_OAEP_SOURCE_TYPE_PTR; + +/* The following encoding parameter sources are defined */ +#define CKZ_DATA_SPECIFIED 0x00000001UL + +/* CK_RSA_PKCS_OAEP_PARAMS provides the parameters to the + * CKM_RSA_PKCS_OAEP mechanism. + */ +typedef struct CK_RSA_PKCS_OAEP_PARAMS { + CK_MECHANISM_TYPE hashAlg; + CK_RSA_PKCS_MGF_TYPE mgf; + CK_RSA_PKCS_OAEP_SOURCE_TYPE source; + CK_VOID_PTR pSourceData; + CK_ULONG ulSourceDataLen; +} CK_RSA_PKCS_OAEP_PARAMS; + +typedef CK_RSA_PKCS_OAEP_PARAMS CK_PTR CK_RSA_PKCS_OAEP_PARAMS_PTR; + +/* CK_RSA_PKCS_PSS_PARAMS provides the parameters to the + * CKM_RSA_PKCS_PSS mechanism(s). + */ +typedef struct CK_RSA_PKCS_PSS_PARAMS { + CK_MECHANISM_TYPE hashAlg; + CK_RSA_PKCS_MGF_TYPE mgf; + CK_ULONG sLen; +} CK_RSA_PKCS_PSS_PARAMS; + +typedef CK_RSA_PKCS_PSS_PARAMS CK_PTR CK_RSA_PKCS_PSS_PARAMS_PTR; + +typedef CK_ULONG CK_EC_KDF_TYPE; + +/* The following EC Key Derivation Functions are defined */ +#define CKD_NULL 0x00000001UL +#define CKD_SHA1_KDF 0x00000002UL + +/* The following X9.42 DH key derivation functions are defined */ +#define CKD_SHA1_KDF_ASN1 0x00000003UL +#define CKD_SHA1_KDF_CONCATENATE 0x00000004UL +#define CKD_SHA224_KDF 0x00000005UL +#define CKD_SHA256_KDF 0x00000006UL +#define CKD_SHA384_KDF 0x00000007UL +#define CKD_SHA512_KDF 0x00000008UL +#define CKD_CPDIVERSIFY_KDF 0x00000009UL + + +/* CK_ECDH1_DERIVE_PARAMS provides the parameters to the + * CKM_ECDH1_DERIVE and CKM_ECDH1_COFACTOR_DERIVE mechanisms, + * where each party contributes one key pair. + */ +typedef struct CK_ECDH1_DERIVE_PARAMS { + CK_EC_KDF_TYPE kdf; + CK_ULONG ulSharedDataLen; + CK_BYTE_PTR pSharedData; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; +} CK_ECDH1_DERIVE_PARAMS; + +typedef CK_ECDH1_DERIVE_PARAMS CK_PTR CK_ECDH1_DERIVE_PARAMS_PTR; + +/* + * CK_ECDH2_DERIVE_PARAMS provides the parameters to the + * CKM_ECMQV_DERIVE mechanism, where each party contributes two key pairs. + */ +typedef struct CK_ECDH2_DERIVE_PARAMS { + CK_EC_KDF_TYPE kdf; + CK_ULONG ulSharedDataLen; + CK_BYTE_PTR pSharedData; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPrivateDataLen; + CK_OBJECT_HANDLE hPrivateData; + CK_ULONG ulPublicDataLen2; + CK_BYTE_PTR pPublicData2; +} CK_ECDH2_DERIVE_PARAMS; + +typedef CK_ECDH2_DERIVE_PARAMS CK_PTR CK_ECDH2_DERIVE_PARAMS_PTR; + +typedef struct CK_ECMQV_DERIVE_PARAMS { + CK_EC_KDF_TYPE kdf; + CK_ULONG ulSharedDataLen; + CK_BYTE_PTR pSharedData; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPrivateDataLen; + CK_OBJECT_HANDLE hPrivateData; + CK_ULONG ulPublicDataLen2; + CK_BYTE_PTR pPublicData2; + CK_OBJECT_HANDLE publicKey; +} CK_ECMQV_DERIVE_PARAMS; + +typedef CK_ECMQV_DERIVE_PARAMS CK_PTR CK_ECMQV_DERIVE_PARAMS_PTR; + +/* Typedefs and defines for the CKM_X9_42_DH_KEY_PAIR_GEN and the + * CKM_X9_42_DH_PARAMETER_GEN mechanisms + */ +typedef CK_ULONG CK_X9_42_DH_KDF_TYPE; +typedef CK_X9_42_DH_KDF_TYPE CK_PTR CK_X9_42_DH_KDF_TYPE_PTR; + +/* CK_X9_42_DH1_DERIVE_PARAMS provides the parameters to the + * CKM_X9_42_DH_DERIVE key derivation mechanism, where each party + * contributes one key pair + */ +typedef struct CK_X9_42_DH1_DERIVE_PARAMS { + CK_X9_42_DH_KDF_TYPE kdf; + CK_ULONG ulOtherInfoLen; + CK_BYTE_PTR pOtherInfo; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; +} CK_X9_42_DH1_DERIVE_PARAMS; + +typedef struct CK_X9_42_DH1_DERIVE_PARAMS CK_PTR CK_X9_42_DH1_DERIVE_PARAMS_PTR; + +/* CK_X9_42_DH2_DERIVE_PARAMS provides the parameters to the + * CKM_X9_42_DH_HYBRID_DERIVE and CKM_X9_42_MQV_DERIVE key derivation + * mechanisms, where each party contributes two key pairs + */ +typedef struct CK_X9_42_DH2_DERIVE_PARAMS { + CK_X9_42_DH_KDF_TYPE kdf; + CK_ULONG ulOtherInfoLen; + CK_BYTE_PTR pOtherInfo; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPrivateDataLen; + CK_OBJECT_HANDLE hPrivateData; + CK_ULONG ulPublicDataLen2; + CK_BYTE_PTR pPublicData2; +} CK_X9_42_DH2_DERIVE_PARAMS; + +typedef CK_X9_42_DH2_DERIVE_PARAMS CK_PTR CK_X9_42_DH2_DERIVE_PARAMS_PTR; + +typedef struct CK_X9_42_MQV_DERIVE_PARAMS { + CK_X9_42_DH_KDF_TYPE kdf; + CK_ULONG ulOtherInfoLen; + CK_BYTE_PTR pOtherInfo; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPrivateDataLen; + CK_OBJECT_HANDLE hPrivateData; + CK_ULONG ulPublicDataLen2; + CK_BYTE_PTR pPublicData2; + CK_OBJECT_HANDLE publicKey; +} CK_X9_42_MQV_DERIVE_PARAMS; + +typedef CK_X9_42_MQV_DERIVE_PARAMS CK_PTR CK_X9_42_MQV_DERIVE_PARAMS_PTR; + +/* CK_KEA_DERIVE_PARAMS provides the parameters to the + * CKM_KEA_DERIVE mechanism + */ +typedef struct CK_KEA_DERIVE_PARAMS { + CK_BBOOL isSender; + CK_ULONG ulRandomLen; + CK_BYTE_PTR pRandomA; + CK_BYTE_PTR pRandomB; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; +} CK_KEA_DERIVE_PARAMS; + +typedef CK_KEA_DERIVE_PARAMS CK_PTR CK_KEA_DERIVE_PARAMS_PTR; + + +/* CK_RC2_PARAMS provides the parameters to the CKM_RC2_ECB and + * CKM_RC2_MAC mechanisms. An instance of CK_RC2_PARAMS just + * holds the effective keysize + */ +typedef CK_ULONG CK_RC2_PARAMS; + +typedef CK_RC2_PARAMS CK_PTR CK_RC2_PARAMS_PTR; + + +/* CK_RC2_CBC_PARAMS provides the parameters to the CKM_RC2_CBC + * mechanism + */ +typedef struct CK_RC2_CBC_PARAMS { + CK_ULONG ulEffectiveBits; /* effective bits (1-1024) */ + CK_BYTE iv[8]; /* IV for CBC mode */ +} CK_RC2_CBC_PARAMS; + +typedef CK_RC2_CBC_PARAMS CK_PTR CK_RC2_CBC_PARAMS_PTR; + + +/* CK_RC2_MAC_GENERAL_PARAMS provides the parameters for the + * CKM_RC2_MAC_GENERAL mechanism + */ +typedef struct CK_RC2_MAC_GENERAL_PARAMS { + CK_ULONG ulEffectiveBits; /* effective bits (1-1024) */ + CK_ULONG ulMacLength; /* Length of MAC in bytes */ +} CK_RC2_MAC_GENERAL_PARAMS; + +typedef CK_RC2_MAC_GENERAL_PARAMS CK_PTR \ + CK_RC2_MAC_GENERAL_PARAMS_PTR; + + +/* CK_RC5_PARAMS provides the parameters to the CKM_RC5_ECB and + * CKM_RC5_MAC mechanisms + */ +typedef struct CK_RC5_PARAMS { + CK_ULONG ulWordsize; /* wordsize in bits */ + CK_ULONG ulRounds; /* number of rounds */ +} CK_RC5_PARAMS; + +typedef CK_RC5_PARAMS CK_PTR CK_RC5_PARAMS_PTR; + + +/* CK_RC5_CBC_PARAMS provides the parameters to the CKM_RC5_CBC + * mechanism + */ +typedef struct CK_RC5_CBC_PARAMS { + CK_ULONG ulWordsize; /* wordsize in bits */ + CK_ULONG ulRounds; /* number of rounds */ + CK_BYTE_PTR pIv; /* pointer to IV */ + CK_ULONG ulIvLen; /* length of IV in bytes */ +} CK_RC5_CBC_PARAMS; + +typedef CK_RC5_CBC_PARAMS CK_PTR CK_RC5_CBC_PARAMS_PTR; + + +/* CK_RC5_MAC_GENERAL_PARAMS provides the parameters for the + * CKM_RC5_MAC_GENERAL mechanism + */ +typedef struct CK_RC5_MAC_GENERAL_PARAMS { + CK_ULONG ulWordsize; /* wordsize in bits */ + CK_ULONG ulRounds; /* number of rounds */ + CK_ULONG ulMacLength; /* Length of MAC in bytes */ +} CK_RC5_MAC_GENERAL_PARAMS; + +typedef CK_RC5_MAC_GENERAL_PARAMS CK_PTR \ + CK_RC5_MAC_GENERAL_PARAMS_PTR; + +/* CK_MAC_GENERAL_PARAMS provides the parameters to most block + * ciphers' MAC_GENERAL mechanisms. Its value is the length of + * the MAC + */ +typedef CK_ULONG CK_MAC_GENERAL_PARAMS; + +typedef CK_MAC_GENERAL_PARAMS CK_PTR CK_MAC_GENERAL_PARAMS_PTR; + +typedef struct CK_DES_CBC_ENCRYPT_DATA_PARAMS { + CK_BYTE iv[8]; + CK_BYTE_PTR pData; + CK_ULONG length; +} CK_DES_CBC_ENCRYPT_DATA_PARAMS; + +typedef CK_DES_CBC_ENCRYPT_DATA_PARAMS CK_PTR CK_DES_CBC_ENCRYPT_DATA_PARAMS_PTR; + +typedef struct CK_AES_CBC_ENCRYPT_DATA_PARAMS { + CK_BYTE iv[16]; + CK_BYTE_PTR pData; + CK_ULONG length; +} CK_AES_CBC_ENCRYPT_DATA_PARAMS; + +typedef CK_AES_CBC_ENCRYPT_DATA_PARAMS CK_PTR CK_AES_CBC_ENCRYPT_DATA_PARAMS_PTR; + +/* CK_SKIPJACK_PRIVATE_WRAP_PARAMS provides the parameters to the + * CKM_SKIPJACK_PRIVATE_WRAP mechanism + */ +typedef struct CK_SKIPJACK_PRIVATE_WRAP_PARAMS { + CK_ULONG ulPasswordLen; + CK_BYTE_PTR pPassword; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPAndGLen; + CK_ULONG ulQLen; + CK_ULONG ulRandomLen; + CK_BYTE_PTR pRandomA; + CK_BYTE_PTR pPrimeP; + CK_BYTE_PTR pBaseG; + CK_BYTE_PTR pSubprimeQ; +} CK_SKIPJACK_PRIVATE_WRAP_PARAMS; + +typedef CK_SKIPJACK_PRIVATE_WRAP_PARAMS CK_PTR \ + CK_SKIPJACK_PRIVATE_WRAP_PARAMS_PTR; + + +/* CK_SKIPJACK_RELAYX_PARAMS provides the parameters to the + * CKM_SKIPJACK_RELAYX mechanism + */ +typedef struct CK_SKIPJACK_RELAYX_PARAMS { + CK_ULONG ulOldWrappedXLen; + CK_BYTE_PTR pOldWrappedX; + CK_ULONG ulOldPasswordLen; + CK_BYTE_PTR pOldPassword; + CK_ULONG ulOldPublicDataLen; + CK_BYTE_PTR pOldPublicData; + CK_ULONG ulOldRandomLen; + CK_BYTE_PTR pOldRandomA; + CK_ULONG ulNewPasswordLen; + CK_BYTE_PTR pNewPassword; + CK_ULONG ulNewPublicDataLen; + CK_BYTE_PTR pNewPublicData; + CK_ULONG ulNewRandomLen; + CK_BYTE_PTR pNewRandomA; +} CK_SKIPJACK_RELAYX_PARAMS; + +typedef CK_SKIPJACK_RELAYX_PARAMS CK_PTR \ + CK_SKIPJACK_RELAYX_PARAMS_PTR; + + +typedef struct CK_PBE_PARAMS { + CK_BYTE_PTR pInitVector; + CK_UTF8CHAR_PTR pPassword; + CK_ULONG ulPasswordLen; + CK_BYTE_PTR pSalt; + CK_ULONG ulSaltLen; + CK_ULONG ulIteration; +} CK_PBE_PARAMS; + +typedef CK_PBE_PARAMS CK_PTR CK_PBE_PARAMS_PTR; + + +/* CK_KEY_WRAP_SET_OAEP_PARAMS provides the parameters to the + * CKM_KEY_WRAP_SET_OAEP mechanism + */ +typedef struct CK_KEY_WRAP_SET_OAEP_PARAMS { + CK_BYTE bBC; /* block contents byte */ + CK_BYTE_PTR pX; /* extra data */ + CK_ULONG ulXLen; /* length of extra data in bytes */ +} CK_KEY_WRAP_SET_OAEP_PARAMS; + +typedef CK_KEY_WRAP_SET_OAEP_PARAMS CK_PTR CK_KEY_WRAP_SET_OAEP_PARAMS_PTR; + +typedef struct CK_SSL3_RANDOM_DATA { + CK_BYTE_PTR pClientRandom; + CK_ULONG ulClientRandomLen; + CK_BYTE_PTR pServerRandom; + CK_ULONG ulServerRandomLen; +} CK_SSL3_RANDOM_DATA; + + +typedef struct CK_SSL3_MASTER_KEY_DERIVE_PARAMS { + CK_SSL3_RANDOM_DATA RandomInfo; + CK_VERSION_PTR pVersion; +} CK_SSL3_MASTER_KEY_DERIVE_PARAMS; + +typedef struct CK_SSL3_MASTER_KEY_DERIVE_PARAMS CK_PTR \ + CK_SSL3_MASTER_KEY_DERIVE_PARAMS_PTR; + +typedef struct CK_SSL3_KEY_MAT_OUT { + CK_OBJECT_HANDLE hClientMacSecret; + CK_OBJECT_HANDLE hServerMacSecret; + CK_OBJECT_HANDLE hClientKey; + CK_OBJECT_HANDLE hServerKey; + CK_BYTE_PTR pIVClient; + CK_BYTE_PTR pIVServer; +} CK_SSL3_KEY_MAT_OUT; + +typedef CK_SSL3_KEY_MAT_OUT CK_PTR CK_SSL3_KEY_MAT_OUT_PTR; + + +typedef struct CK_SSL3_KEY_MAT_PARAMS { + CK_ULONG ulMacSizeInBits; + CK_ULONG ulKeySizeInBits; + CK_ULONG ulIVSizeInBits; + CK_BBOOL bIsExport; + CK_SSL3_RANDOM_DATA RandomInfo; + CK_SSL3_KEY_MAT_OUT_PTR pReturnedKeyMaterial; +} CK_SSL3_KEY_MAT_PARAMS; + +typedef CK_SSL3_KEY_MAT_PARAMS CK_PTR CK_SSL3_KEY_MAT_PARAMS_PTR; + +typedef struct CK_TLS_PRF_PARAMS { + CK_BYTE_PTR pSeed; + CK_ULONG ulSeedLen; + CK_BYTE_PTR pLabel; + CK_ULONG ulLabelLen; + CK_BYTE_PTR pOutput; + CK_ULONG_PTR pulOutputLen; +} CK_TLS_PRF_PARAMS; + +typedef CK_TLS_PRF_PARAMS CK_PTR CK_TLS_PRF_PARAMS_PTR; + +typedef struct CK_WTLS_RANDOM_DATA { + CK_BYTE_PTR pClientRandom; + CK_ULONG ulClientRandomLen; + CK_BYTE_PTR pServerRandom; + CK_ULONG ulServerRandomLen; +} CK_WTLS_RANDOM_DATA; + +typedef CK_WTLS_RANDOM_DATA CK_PTR CK_WTLS_RANDOM_DATA_PTR; + +typedef struct CK_WTLS_MASTER_KEY_DERIVE_PARAMS { + CK_MECHANISM_TYPE DigestMechanism; + CK_WTLS_RANDOM_DATA RandomInfo; + CK_BYTE_PTR pVersion; +} CK_WTLS_MASTER_KEY_DERIVE_PARAMS; + +typedef CK_WTLS_MASTER_KEY_DERIVE_PARAMS CK_PTR \ + CK_WTLS_MASTER_KEY_DERIVE_PARAMS_PTR; + +typedef struct CK_WTLS_PRF_PARAMS { + CK_MECHANISM_TYPE DigestMechanism; + CK_BYTE_PTR pSeed; + CK_ULONG ulSeedLen; + CK_BYTE_PTR pLabel; + CK_ULONG ulLabelLen; + CK_BYTE_PTR pOutput; + CK_ULONG_PTR pulOutputLen; +} CK_WTLS_PRF_PARAMS; + +typedef CK_WTLS_PRF_PARAMS CK_PTR CK_WTLS_PRF_PARAMS_PTR; + +typedef struct CK_WTLS_KEY_MAT_OUT { + CK_OBJECT_HANDLE hMacSecret; + CK_OBJECT_HANDLE hKey; + CK_BYTE_PTR pIV; +} CK_WTLS_KEY_MAT_OUT; + +typedef CK_WTLS_KEY_MAT_OUT CK_PTR CK_WTLS_KEY_MAT_OUT_PTR; + +typedef struct CK_WTLS_KEY_MAT_PARAMS { + CK_MECHANISM_TYPE DigestMechanism; + CK_ULONG ulMacSizeInBits; + CK_ULONG ulKeySizeInBits; + CK_ULONG ulIVSizeInBits; + CK_ULONG ulSequenceNumber; + CK_BBOOL bIsExport; + CK_WTLS_RANDOM_DATA RandomInfo; + CK_WTLS_KEY_MAT_OUT_PTR pReturnedKeyMaterial; +} CK_WTLS_KEY_MAT_PARAMS; + +typedef CK_WTLS_KEY_MAT_PARAMS CK_PTR CK_WTLS_KEY_MAT_PARAMS_PTR; + +typedef struct CK_CMS_SIG_PARAMS { + CK_OBJECT_HANDLE certificateHandle; + CK_MECHANISM_PTR pSigningMechanism; + CK_MECHANISM_PTR pDigestMechanism; + CK_UTF8CHAR_PTR pContentType; + CK_BYTE_PTR pRequestedAttributes; + CK_ULONG ulRequestedAttributesLen; + CK_BYTE_PTR pRequiredAttributes; + CK_ULONG ulRequiredAttributesLen; +} CK_CMS_SIG_PARAMS; + +typedef CK_CMS_SIG_PARAMS CK_PTR CK_CMS_SIG_PARAMS_PTR; + +typedef struct CK_KEY_DERIVATION_STRING_DATA { + CK_BYTE_PTR pData; + CK_ULONG ulLen; +} CK_KEY_DERIVATION_STRING_DATA; + +typedef CK_KEY_DERIVATION_STRING_DATA CK_PTR \ + CK_KEY_DERIVATION_STRING_DATA_PTR; + + +/* The CK_EXTRACT_PARAMS is used for the + * CKM_EXTRACT_KEY_FROM_KEY mechanism. It specifies which bit + * of the base key should be used as the first bit of the + * derived key + */ +typedef CK_ULONG CK_EXTRACT_PARAMS; + +typedef CK_EXTRACT_PARAMS CK_PTR CK_EXTRACT_PARAMS_PTR; + +/* CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE is used to + * indicate the Pseudo-Random Function (PRF) used to generate + * key bits using PKCS #5 PBKDF2. + */ +typedef CK_ULONG CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE; + +typedef CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE CK_PTR \ + CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE_PTR; + +#define CKP_PKCS5_PBKD2_HMAC_SHA1 0x00000001UL +#define CKP_PKCS5_PBKD2_HMAC_GOSTR3411 0x00000002UL +#define CKP_PKCS5_PBKD2_HMAC_SHA224 0x00000003UL +#define CKP_PKCS5_PBKD2_HMAC_SHA256 0x00000004UL +#define CKP_PKCS5_PBKD2_HMAC_SHA384 0x00000005UL +#define CKP_PKCS5_PBKD2_HMAC_SHA512 0x00000006UL +#define CKP_PKCS5_PBKD2_HMAC_SHA512_224 0x00000007UL +#define CKP_PKCS5_PBKD2_HMAC_SHA512_256 0x00000008UL + +/* CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE is used to indicate the + * source of the salt value when deriving a key using PKCS #5 + * PBKDF2. + */ +typedef CK_ULONG CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE; + +typedef CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE CK_PTR \ + CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE_PTR; + +/* The following salt value sources are defined in PKCS #5 v2.0. */ +#define CKZ_SALT_SPECIFIED 0x00000001UL + +/* CK_PKCS5_PBKD2_PARAMS is a structure that provides the + * parameters to the CKM_PKCS5_PBKD2 mechanism. + */ +typedef struct CK_PKCS5_PBKD2_PARAMS { + CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE saltSource; + CK_VOID_PTR pSaltSourceData; + CK_ULONG ulSaltSourceDataLen; + CK_ULONG iterations; + CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE prf; + CK_VOID_PTR pPrfData; + CK_ULONG ulPrfDataLen; + CK_UTF8CHAR_PTR pPassword; + CK_ULONG_PTR ulPasswordLen; +} CK_PKCS5_PBKD2_PARAMS; + +typedef CK_PKCS5_PBKD2_PARAMS CK_PTR CK_PKCS5_PBKD2_PARAMS_PTR; + +/* CK_PKCS5_PBKD2_PARAMS2 is a corrected version of the CK_PKCS5_PBKD2_PARAMS + * structure that provides the parameters to the CKM_PKCS5_PBKD2 mechanism + * noting that the ulPasswordLen field is a CK_ULONG and not a CK_ULONG_PTR. + */ +typedef struct CK_PKCS5_PBKD2_PARAMS2 { + CK_PKCS5_PBKDF2_SALT_SOURCE_TYPE saltSource; + CK_VOID_PTR pSaltSourceData; + CK_ULONG ulSaltSourceDataLen; + CK_ULONG iterations; + CK_PKCS5_PBKD2_PSEUDO_RANDOM_FUNCTION_TYPE prf; + CK_VOID_PTR pPrfData; + CK_ULONG ulPrfDataLen; + CK_UTF8CHAR_PTR pPassword; + CK_ULONG ulPasswordLen; +} CK_PKCS5_PBKD2_PARAMS2; + +typedef CK_PKCS5_PBKD2_PARAMS2 CK_PTR CK_PKCS5_PBKD2_PARAMS2_PTR; + +typedef CK_ULONG CK_OTP_PARAM_TYPE; +typedef CK_OTP_PARAM_TYPE CK_PARAM_TYPE; /* backward compatibility */ + +typedef struct CK_OTP_PARAM { + CK_OTP_PARAM_TYPE type; + CK_VOID_PTR pValue; + CK_ULONG ulValueLen; +} CK_OTP_PARAM; + +typedef CK_OTP_PARAM CK_PTR CK_OTP_PARAM_PTR; + +typedef struct CK_OTP_PARAMS { + CK_OTP_PARAM_PTR pParams; + CK_ULONG ulCount; +} CK_OTP_PARAMS; + +typedef CK_OTP_PARAMS CK_PTR CK_OTP_PARAMS_PTR; + +typedef struct CK_OTP_SIGNATURE_INFO { + CK_OTP_PARAM_PTR pParams; + CK_ULONG ulCount; +} CK_OTP_SIGNATURE_INFO; + +typedef CK_OTP_SIGNATURE_INFO CK_PTR CK_OTP_SIGNATURE_INFO_PTR; + +#define CK_OTP_VALUE 0UL +#define CK_OTP_PIN 1UL +#define CK_OTP_CHALLENGE 2UL +#define CK_OTP_TIME 3UL +#define CK_OTP_COUNTER 4UL +#define CK_OTP_FLAGS 5UL +#define CK_OTP_OUTPUT_LENGTH 6UL +#define CK_OTP_OUTPUT_FORMAT 7UL + +#define CKF_NEXT_OTP 0x00000001UL +#define CKF_EXCLUDE_TIME 0x00000002UL +#define CKF_EXCLUDE_COUNTER 0x00000004UL +#define CKF_EXCLUDE_CHALLENGE 0x00000008UL +#define CKF_EXCLUDE_PIN 0x00000010UL +#define CKF_USER_FRIENDLY_OTP 0x00000020UL + +typedef struct CK_KIP_PARAMS { + CK_MECHANISM_PTR pMechanism; + CK_OBJECT_HANDLE hKey; + CK_BYTE_PTR pSeed; + CK_ULONG ulSeedLen; +} CK_KIP_PARAMS; + +typedef CK_KIP_PARAMS CK_PTR CK_KIP_PARAMS_PTR; + +typedef struct CK_AES_CTR_PARAMS { + CK_ULONG ulCounterBits; + CK_BYTE cb[16]; +} CK_AES_CTR_PARAMS; + +typedef CK_AES_CTR_PARAMS CK_PTR CK_AES_CTR_PARAMS_PTR; + +typedef struct CK_GCM_PARAMS { + CK_BYTE_PTR pIv; + CK_ULONG ulIvLen; + CK_ULONG ulIvBits; + CK_BYTE_PTR pAAD; + CK_ULONG ulAADLen; + CK_ULONG ulTagBits; +} CK_GCM_PARAMS; + +typedef CK_GCM_PARAMS CK_PTR CK_GCM_PARAMS_PTR; + +typedef struct CK_CCM_PARAMS { + CK_ULONG ulDataLen; + CK_BYTE_PTR pNonce; + CK_ULONG ulNonceLen; + CK_BYTE_PTR pAAD; + CK_ULONG ulAADLen; + CK_ULONG ulMACLen; +} CK_CCM_PARAMS; + +typedef CK_CCM_PARAMS CK_PTR CK_CCM_PARAMS_PTR; + +/* Deprecated. Use CK_GCM_PARAMS */ +typedef struct CK_AES_GCM_PARAMS { + CK_BYTE_PTR pIv; + CK_ULONG ulIvLen; + CK_ULONG ulIvBits; + CK_BYTE_PTR pAAD; + CK_ULONG ulAADLen; + CK_ULONG ulTagBits; +} CK_AES_GCM_PARAMS; + +typedef CK_AES_GCM_PARAMS CK_PTR CK_AES_GCM_PARAMS_PTR; + +/* Deprecated. Use CK_CCM_PARAMS */ +typedef struct CK_AES_CCM_PARAMS { + CK_ULONG ulDataLen; + CK_BYTE_PTR pNonce; + CK_ULONG ulNonceLen; + CK_BYTE_PTR pAAD; + CK_ULONG ulAADLen; + CK_ULONG ulMACLen; +} CK_AES_CCM_PARAMS; + +typedef CK_AES_CCM_PARAMS CK_PTR CK_AES_CCM_PARAMS_PTR; + +typedef struct CK_CAMELLIA_CTR_PARAMS { + CK_ULONG ulCounterBits; + CK_BYTE cb[16]; +} CK_CAMELLIA_CTR_PARAMS; + +typedef CK_CAMELLIA_CTR_PARAMS CK_PTR CK_CAMELLIA_CTR_PARAMS_PTR; + +typedef struct CK_CAMELLIA_CBC_ENCRYPT_DATA_PARAMS { + CK_BYTE iv[16]; + CK_BYTE_PTR pData; + CK_ULONG length; +} CK_CAMELLIA_CBC_ENCRYPT_DATA_PARAMS; + +typedef CK_CAMELLIA_CBC_ENCRYPT_DATA_PARAMS CK_PTR \ + CK_CAMELLIA_CBC_ENCRYPT_DATA_PARAMS_PTR; + +typedef struct CK_ARIA_CBC_ENCRYPT_DATA_PARAMS { + CK_BYTE iv[16]; + CK_BYTE_PTR pData; + CK_ULONG length; +} CK_ARIA_CBC_ENCRYPT_DATA_PARAMS; + +typedef CK_ARIA_CBC_ENCRYPT_DATA_PARAMS CK_PTR \ + CK_ARIA_CBC_ENCRYPT_DATA_PARAMS_PTR; + +typedef struct CK_DSA_PARAMETER_GEN_PARAM { + CK_MECHANISM_TYPE hash; + CK_BYTE_PTR pSeed; + CK_ULONG ulSeedLen; + CK_ULONG ulIndex; +} CK_DSA_PARAMETER_GEN_PARAM; + +typedef CK_DSA_PARAMETER_GEN_PARAM CK_PTR CK_DSA_PARAMETER_GEN_PARAM_PTR; + +typedef struct CK_ECDH_AES_KEY_WRAP_PARAMS { + CK_ULONG ulAESKeyBits; + CK_EC_KDF_TYPE kdf; + CK_ULONG ulSharedDataLen; + CK_BYTE_PTR pSharedData; +} CK_ECDH_AES_KEY_WRAP_PARAMS; + +typedef CK_ECDH_AES_KEY_WRAP_PARAMS CK_PTR CK_ECDH_AES_KEY_WRAP_PARAMS_PTR; + +typedef CK_ULONG CK_JAVA_MIDP_SECURITY_DOMAIN; + +typedef CK_ULONG CK_CERTIFICATE_CATEGORY; + +typedef struct CK_RSA_AES_KEY_WRAP_PARAMS { + CK_ULONG ulAESKeyBits; + CK_RSA_PKCS_OAEP_PARAMS_PTR pOAEPParams; +} CK_RSA_AES_KEY_WRAP_PARAMS; + +typedef CK_RSA_AES_KEY_WRAP_PARAMS CK_PTR CK_RSA_AES_KEY_WRAP_PARAMS_PTR; + +typedef struct CK_TLS12_MASTER_KEY_DERIVE_PARAMS { + CK_SSL3_RANDOM_DATA RandomInfo; + CK_VERSION_PTR pVersion; + CK_MECHANISM_TYPE prfHashMechanism; +} CK_TLS12_MASTER_KEY_DERIVE_PARAMS; + +typedef CK_TLS12_MASTER_KEY_DERIVE_PARAMS CK_PTR \ + CK_TLS12_MASTER_KEY_DERIVE_PARAMS_PTR; + +typedef struct CK_TLS12_KEY_MAT_PARAMS { + CK_ULONG ulMacSizeInBits; + CK_ULONG ulKeySizeInBits; + CK_ULONG ulIVSizeInBits; + CK_BBOOL bIsExport; + CK_SSL3_RANDOM_DATA RandomInfo; + CK_SSL3_KEY_MAT_OUT_PTR pReturnedKeyMaterial; + CK_MECHANISM_TYPE prfHashMechanism; +} CK_TLS12_KEY_MAT_PARAMS; + +typedef CK_TLS12_KEY_MAT_PARAMS CK_PTR CK_TLS12_KEY_MAT_PARAMS_PTR; + +typedef struct CK_TLS_KDF_PARAMS { + CK_MECHANISM_TYPE prfMechanism; + CK_BYTE_PTR pLabel; + CK_ULONG ulLabelLength; + CK_SSL3_RANDOM_DATA RandomInfo; + CK_BYTE_PTR pContextData; + CK_ULONG ulContextDataLength; +} CK_TLS_KDF_PARAMS; + +typedef CK_TLS_KDF_PARAMS CK_PTR CK_TLS_KDF_PARAMS_PTR; + +typedef struct CK_TLS_MAC_PARAMS { + CK_MECHANISM_TYPE prfHashMechanism; + CK_ULONG ulMacLength; + CK_ULONG ulServerOrClient; +} CK_TLS_MAC_PARAMS; + +typedef CK_TLS_MAC_PARAMS CK_PTR CK_TLS_MAC_PARAMS_PTR; + +typedef struct CK_GOSTR3410_DERIVE_PARAMS { + CK_EC_KDF_TYPE kdf; + CK_BYTE_PTR pPublicData; + CK_ULONG ulPublicDataLen; + CK_BYTE_PTR pUKM; + CK_ULONG ulUKMLen; +} CK_GOSTR3410_DERIVE_PARAMS; + +typedef CK_GOSTR3410_DERIVE_PARAMS CK_PTR CK_GOSTR3410_DERIVE_PARAMS_PTR; + +typedef struct CK_GOSTR3410_KEY_WRAP_PARAMS { + CK_BYTE_PTR pWrapOID; + CK_ULONG ulWrapOIDLen; + CK_BYTE_PTR pUKM; + CK_ULONG ulUKMLen; + CK_OBJECT_HANDLE hKey; +} CK_GOSTR3410_KEY_WRAP_PARAMS; + +typedef CK_GOSTR3410_KEY_WRAP_PARAMS CK_PTR CK_GOSTR3410_KEY_WRAP_PARAMS_PTR; + +typedef struct CK_SEED_CBC_ENCRYPT_DATA_PARAMS { + CK_BYTE iv[16]; + CK_BYTE_PTR pData; + CK_ULONG length; +} CK_SEED_CBC_ENCRYPT_DATA_PARAMS; + +typedef CK_SEED_CBC_ENCRYPT_DATA_PARAMS CK_PTR \ + CK_SEED_CBC_ENCRYPT_DATA_PARAMS_PTR; + +#endif /* _PKCS11T_H_ */ + diff --git a/applications/CMakeLists.txt b/applications/CMakeLists.txt index c738597dac..0b31cb52d8 100644 --- a/applications/CMakeLists.txt +++ b/applications/CMakeLists.txt @@ -34,4 +34,4 @@ if (OPUS) add_subdirectory("acsdkOpusSpeechEncoder") else() add_subdirectory("acsdkNullSpeechEncoder") -endif() \ No newline at end of file +endif() diff --git a/applications/acsdkAndroidApplicationAudioPipelineFactory/src/CMakeLists.txt b/applications/acsdkAndroidApplicationAudioPipelineFactory/src/CMakeLists.txt index 7e039d2807..4ae0cb1eb2 100644 --- a/applications/acsdkAndroidApplicationAudioPipelineFactory/src/CMakeLists.txt +++ b/applications/acsdkAndroidApplicationAudioPipelineFactory/src/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_definitions("-DACSDK_LOG_MODULE=acsdkAndroidApplicationAudioPipelineFactory") -add_library(acsdkAndroidApplicationAudioPipelineFactory SHARED +add_library(acsdkAndroidApplicationAudioPipelineFactory AndroidApplicationAudioPipelineFactory.cpp ApplicationAudioPipelineFactoryComponent.cpp) diff --git a/applications/acsdkAudioInputStream/src/CMakeLists.txt b/applications/acsdkAudioInputStream/src/CMakeLists.txt index ed4afc3347..1164488af0 100644 --- a/applications/acsdkAudioInputStream/src/CMakeLists.txt +++ b/applications/acsdkAudioInputStream/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkAudioInputStream") -add_library(acsdkAudioInputStream SHARED +add_library(acsdkAudioInputStream AudioInputStreamFactory.cpp AudioInputStreamComponent.cpp CompatibleAudioFormat.cpp) diff --git a/applications/acsdkBlueZBluetoothImplementation/src/CMakeLists.txt b/applications/acsdkBlueZBluetoothImplementation/src/CMakeLists.txt index 00b026acc6..92cbd1b597 100644 --- a/applications/acsdkBlueZBluetoothImplementation/src/CMakeLists.txt +++ b/applications/acsdkBlueZBluetoothImplementation/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=BluetoothImplementation") -add_library(acsdkBlueZBluetoothImplementation SHARED +add_library(acsdkBlueZBluetoothImplementation BluetoothImplementationComponent.cpp) target_include_directories(acsdkBlueZBluetoothImplementation PUBLIC diff --git a/applications/acsdkCBLAuthorizationDelegate/include/acsdkAuthorizationDelegate/AuthorizationDelegateComponent.h b/applications/acsdkCBLAuthorizationDelegate/include/acsdkAuthorizationDelegate/AuthorizationDelegateComponent.h index 0536248eec..3b3e264a1d 100644 --- a/applications/acsdkCBLAuthorizationDelegate/include/acsdkAuthorizationDelegate/AuthorizationDelegateComponent.h +++ b/applications/acsdkCBLAuthorizationDelegate/include/acsdkAuthorizationDelegate/AuthorizationDelegateComponent.h @@ -18,6 +18,8 @@ #include +#include +#include #include #include #include @@ -39,7 +41,9 @@ using AuthorizationDelegateComponent = acsdkManufactory::Component< acsdkManufactory::Import>, acsdkManufactory::Import>, acsdkManufactory::Import>, - acsdkManufactory::Import>>; + acsdkManufactory::Import>, + acsdkManufactory::Import>, + acsdkManufactory::Import>>; /** * Get the @c Manufactory component for creating an instance of AVSConnectionMangerInterface. diff --git a/applications/acsdkCBLAuthorizationDelegate/src/CMakeLists.txt b/applications/acsdkCBLAuthorizationDelegate/src/CMakeLists.txt index 7a4be00fd6..3e3c4a19bf 100644 --- a/applications/acsdkCBLAuthorizationDelegate/src/CMakeLists.txt +++ b/applications/acsdkCBLAuthorizationDelegate/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkCBLAuthorizationDelegate") -add_library(acsdkCBLAuthorizationDelegate SHARED +add_library(acsdkCBLAuthorizationDelegate AuthorizationDelegateComponent.cpp) target_include_directories(acsdkCBLAuthorizationDelegate PUBLIC diff --git a/applications/acsdkCustomApplicationAudioPipelineFactory/src/CMakeLists.txt b/applications/acsdkCustomApplicationAudioPipelineFactory/src/CMakeLists.txt index e41c0d693c..223a4a901b 100644 --- a/applications/acsdkCustomApplicationAudioPipelineFactory/src/CMakeLists.txt +++ b/applications/acsdkCustomApplicationAudioPipelineFactory/src/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_definitions("-DACSDK_LOG_MODULE=acsdkCustomApplicationAudioPipelineFactory") -add_library(acsdkCustomApplicationAudioPipelineFactory SHARED +add_library(acsdkCustomApplicationAudioPipelineFactory ApplicationAudioPipelineFactoryComponent.cpp CustomApplicationAudioPipelineFactory.cpp) diff --git a/applications/acsdkDefaultDeviceSettingsManager/src/CMakeLists.txt b/applications/acsdkDefaultDeviceSettingsManager/src/CMakeLists.txt index 7bda7a1281..1e25919f74 100644 --- a/applications/acsdkDefaultDeviceSettingsManager/src/CMakeLists.txt +++ b/applications/acsdkDefaultDeviceSettingsManager/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkDefaultDeviceSettingsManager") -add_library(acsdkDefaultDeviceSettingsManager SHARED +add_library(acsdkDefaultDeviceSettingsManager DeviceSettingsManagerBuilder.cpp DeviceSettingsManagerComponent.cpp) diff --git a/applications/acsdkDefaultInternetConnectionMonitor/src/CMakeLists.txt b/applications/acsdkDefaultInternetConnectionMonitor/src/CMakeLists.txt index 8e4c33ad1b..6660d059f3 100644 --- a/applications/acsdkDefaultInternetConnectionMonitor/src/CMakeLists.txt +++ b/applications/acsdkDefaultInternetConnectionMonitor/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkDefaultInternetConnectionMonitor") -add_library(acsdkDefaultInternetConnectionMonitor SHARED +add_library(acsdkDefaultInternetConnectionMonitor InternetConnectionMonitorComponent.cpp) target_include_directories(acsdkDefaultInternetConnectionMonitor PUBLIC diff --git a/applications/acsdkDefaultSampleApplicationOptions/include/acsdkDefaultSampleApplicationOptions/DefaultSampleApplicationOptionsComponent.h b/applications/acsdkDefaultSampleApplicationOptions/include/acsdkDefaultSampleApplicationOptions/DefaultSampleApplicationOptionsComponent.h index 77445980ff..1ba1f09d18 100644 --- a/applications/acsdkDefaultSampleApplicationOptions/include/acsdkDefaultSampleApplicationOptions/DefaultSampleApplicationOptionsComponent.h +++ b/applications/acsdkDefaultSampleApplicationOptions/include/acsdkDefaultSampleApplicationOptions/DefaultSampleApplicationOptionsComponent.h @@ -17,6 +17,8 @@ #define ACSDKDEFAULTSAMPLEAPPLICATIONOPTIONS_DEFAULTSAMPLEAPPLICATIONOPTIONSCOMPONENT_H_ #include +#include +#include #include #include #include @@ -41,7 +43,9 @@ using SampleApplicationOptionsComponent = acsdkManufactory::Component< acsdkManufactory::Import>, acsdkManufactory::Import>, acsdkManufactory::Import>, - acsdkManufactory::Import>>; + acsdkManufactory::Import>, + acsdkManufactory::Import>, + acsdkManufactory::Import>>; /** * Get the @c Manufactory @c Component for the default @c SampleApplication options. diff --git a/applications/acsdkDefaultSampleApplicationOptions/src/CMakeLists.txt b/applications/acsdkDefaultSampleApplicationOptions/src/CMakeLists.txt index edb0845ce3..7a4ccef3eb 100644 --- a/applications/acsdkDefaultSampleApplicationOptions/src/CMakeLists.txt +++ b/applications/acsdkDefaultSampleApplicationOptions/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkDefaultSampleApplicationOptions") -add_library(acsdkDefaultSampleApplicationOptions SHARED +add_library(acsdkDefaultSampleApplicationOptions DefaultSampleApplicationOptionsComponent.cpp) target_include_directories(acsdkDefaultSampleApplicationOptions PUBLIC diff --git a/applications/acsdkDefaultSampleApplicationOptions/src/DefaultSampleApplicationOptionsComponent.cpp b/applications/acsdkDefaultSampleApplicationOptions/src/DefaultSampleApplicationOptionsComponent.cpp index 3401f82955..5721808140 100644 --- a/applications/acsdkDefaultSampleApplicationOptions/src/DefaultSampleApplicationOptionsComponent.cpp +++ b/applications/acsdkDefaultSampleApplicationOptions/src/DefaultSampleApplicationOptionsComponent.cpp @@ -57,7 +57,9 @@ static acsdkManufactory::Component< acsdkManufactory::Import>, acsdkManufactory::Import>, acsdkManufactory::Import>, - acsdkManufactory::Import>> + acsdkManufactory::Import>, + acsdkManufactory::Import>, + acsdkManufactory::Import>> getAuthDelegateComponent(const std::shared_ptr& authDelegate) { /// If a custom authDelegate is provided, add that implementation. Otherwise, use defaults. if (authDelegate) { diff --git a/applications/acsdkGstreamerApplicationAudioPipelineFactory/src/CMakeLists.txt b/applications/acsdkGstreamerApplicationAudioPipelineFactory/src/CMakeLists.txt index 969ce6277f..fb5d3f2de8 100644 --- a/applications/acsdkGstreamerApplicationAudioPipelineFactory/src/CMakeLists.txt +++ b/applications/acsdkGstreamerApplicationAudioPipelineFactory/src/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_definitions("-DACSDK_LOG_MODULE=acsdkGstreamerApplicationAudioPipelineFactory") -add_library(acsdkGstreamerApplicationAudioPipelineFactory SHARED +add_library(acsdkGstreamerApplicationAudioPipelineFactory ApplicationAudioPipelineFactoryComponent.cpp GstreamerApplicationAudioPipelineFactory.cpp) diff --git a/applications/acsdkLibcurlAlexaCommunications/src/CMakeLists.txt b/applications/acsdkLibcurlAlexaCommunications/src/CMakeLists.txt index d29350f4cf..5075a16126 100644 --- a/applications/acsdkLibcurlAlexaCommunications/src/CMakeLists.txt +++ b/applications/acsdkLibcurlAlexaCommunications/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkLibcurlAlexaCommunications") -add_library(acsdkLibcurlAlexaCommunications SHARED +add_library(acsdkLibcurlAlexaCommunications AlexaCommunicationsComponent.cpp) target_include_directories(acsdkLibcurlAlexaCommunications PUBLIC diff --git a/applications/acsdkLibcurlHTTPContentFetcher/include/acsdkHTTPContentFetcher/HTTPContentFetcherComponent.h b/applications/acsdkLibcurlHTTPContentFetcher/include/acsdkHTTPContentFetcher/HTTPContentFetcherComponent.h index 517cca89c2..0d281f1f7c 100644 --- a/applications/acsdkLibcurlHTTPContentFetcher/include/acsdkHTTPContentFetcher/HTTPContentFetcherComponent.h +++ b/applications/acsdkLibcurlHTTPContentFetcher/include/acsdkHTTPContentFetcher/HTTPContentFetcherComponent.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace alexaClientSDK { namespace acsdkHTTPContentFetcher { diff --git a/applications/acsdkLibcurlHTTPContentFetcher/src/CMakeLists.txt b/applications/acsdkLibcurlHTTPContentFetcher/src/CMakeLists.txt index d4f8f76c3a..ddef0a4db7 100644 --- a/applications/acsdkLibcurlHTTPContentFetcher/src/CMakeLists.txt +++ b/applications/acsdkLibcurlHTTPContentFetcher/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkLibcurlHTTPContentFetcher") -add_library(acsdkLibcurlHTTPContentFetcher SHARED +add_library(acsdkLibcurlHTTPContentFetcher HTTPContentFetcherComponent.cpp) target_include_directories(acsdkLibcurlHTTPContentFetcher PUBLIC diff --git a/applications/acsdkNullBluetoothImplementation/src/CMakeLists.txt b/applications/acsdkNullBluetoothImplementation/src/CMakeLists.txt index b2bf0d4921..9065f28071 100644 --- a/applications/acsdkNullBluetoothImplementation/src/CMakeLists.txt +++ b/applications/acsdkNullBluetoothImplementation/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=BluetoothImplementation") -add_library(acsdkNullBluetoothImplementation SHARED +add_library(acsdkNullBluetoothImplementation BluetoothImplementationComponent.cpp) target_include_directories(acsdkNullBluetoothImplementation PUBLIC diff --git a/applications/acsdkNullMetricRecorder/src/CMakeLists.txt b/applications/acsdkNullMetricRecorder/src/CMakeLists.txt index 037b758994..7980f7bf82 100644 --- a/applications/acsdkNullMetricRecorder/src/CMakeLists.txt +++ b/applications/acsdkNullMetricRecorder/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkNullMetricRecorder") -add_library(acsdkNullMetricRecorder SHARED +add_library(acsdkNullMetricRecorder MetricRecorderComponent.cpp) target_include_directories(acsdkNullMetricRecorder PUBLIC diff --git a/applications/acsdkNullSpeechEncoder/src/CMakeLists.txt b/applications/acsdkNullSpeechEncoder/src/CMakeLists.txt index 4ef66e8cc4..8350bdbbe5 100644 --- a/applications/acsdkNullSpeechEncoder/src/CMakeLists.txt +++ b/applications/acsdkNullSpeechEncoder/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkNullSpeechEncoder") -add_library(acsdkNullSpeechEncoder SHARED +add_library(acsdkNullSpeechEncoder SpeechEncoderComponent.cpp) target_include_directories(acsdkNullSpeechEncoder PUBLIC diff --git a/applications/acsdkNullSystemTimeZone/src/CMakeLists.txt b/applications/acsdkNullSystemTimeZone/src/CMakeLists.txt index ffc04bc91c..90a19de027 100644 --- a/applications/acsdkNullSystemTimeZone/src/CMakeLists.txt +++ b/applications/acsdkNullSystemTimeZone/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkNullSystemTimeZone") -add_library(acsdkNullSystemTimeZone SHARED +add_library(acsdkNullSystemTimeZone SystemTimeZoneComponent.cpp) target_include_directories(acsdkNullSystemTimeZone PUBLIC diff --git a/applications/acsdkOpusSpeechEncoder/src/CMakeLists.txt b/applications/acsdkOpusSpeechEncoder/src/CMakeLists.txt index 3209fe445a..fabc17969d 100644 --- a/applications/acsdkOpusSpeechEncoder/src/CMakeLists.txt +++ b/applications/acsdkOpusSpeechEncoder/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkOpusSpeechEncoder") -add_library(acsdkOpusSpeechEncoder SHARED +add_library(acsdkOpusSpeechEncoder SpeechEncoderComponent.cpp) target_include_directories(acsdkOpusSpeechEncoder PUBLIC diff --git a/applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClient.h b/applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClient.h index 2fd3c593c9..eac3137d0e 100644 --- a/applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClient.h +++ b/applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClient.h @@ -43,7 +43,7 @@ #endif #ifdef KWD -#include +#include #endif #ifdef GSTREAMER_MEDIA_PLAYER @@ -75,16 +75,14 @@ class PreviewAlexaClient { * * @param consoleReader The @c ConsoleReader to read inputs from console. * @param configFiles The vector of configuration files. - * @param pathToInputFolder The path to the inputs folder containing data files needed by this application. * @param logLevel The level of logging to enable. If this parameter is an empty string, the SDK's default * logging level will be used. - * @param An optional @c DiagnosticsInterface object to provide diagnostics on the SDK. + * @param diagnostics An optional @c DiagnosticsInterface object to provide diagnostics on the SDK. * @return A new @c PreviewAlexaClient, or @c nullptr if the operation failed. */ static std::unique_ptr create( std::shared_ptr consoleReader, const std::vector& configFiles, - const std::string& pathToInputFolder, const std::string& logLevel = "", std::shared_ptr diagnostics = nullptr); @@ -95,6 +93,17 @@ class PreviewAlexaClient { */ sampleApp::SampleAppReturnCode run(); +#ifdef DIAGNOSTICS + /** + * Initiates application stop for restart sequence. This method notifies event loop that the application + * should be terminated with subsequent restart, in other words, if the application is running, it should + * return SampleAppReturnCode::RESTART code. + * + * @return True if restart has been successfully initiated, false on error or if operation is not supported. + */ + bool initiateRestart(); +#endif + /// Destructor which manages the @c SampleApplication shutdown sequence. ~PreviewAlexaClient(); @@ -104,16 +113,14 @@ class PreviewAlexaClient { * * @param consoleReader The @c ConsoleReader to read inputs from console. * @param configFiles The vector of configuration files. - * @param pathToInputFolder The path to the inputs folder containing data files needed by this application. * @param logLevel The level of logging to enable. If this parameter is an empty string, the SDK's default * logging level will be used. - * @param An optional @c DiagnosticsInterface object to provide diagnostics on the SDK. + * @param diagnostics An optional @c DiagnosticsInterface object to provide diagnostics on the SDK. * @return @c true if initialization succeeded, else @c false. */ bool initialize( std::shared_ptr consoleReader, const std::vector& configFiles, - const std::string& pathToInputFolder, const std::string& logLevel, std::shared_ptr diagnostics); @@ -189,10 +196,8 @@ class PreviewAlexaClient { /// The @c MediaPlayer used by @c NotificationsCapabilityAgent. std::shared_ptr m_ringtoneMediaPlayer; -#ifdef KWD /// The Wakeword Detector which can wake up the client using audio input. - std::unique_ptr m_keywordDetector; -#endif + std::shared_ptr m_keywordDetector; #if defined(ANDROID_MEDIA_PLAYER) || defined(ANDROID_MICROPHONE) /// The android OpenSL ES engine used to create media players and microphone. diff --git a/applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClientComponent.h b/applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClientComponent.h index fb4a61f626..bcde94822b 100644 --- a/applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClientComponent.h +++ b/applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClientComponent.h @@ -22,11 +22,15 @@ #include #include +#include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -81,12 +85,14 @@ using PreviewAlexaClientComponent = acsdkManufactory::Component< std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, @@ -132,7 +138,9 @@ using PreviewAlexaClientComponent = acsdkManufactory::Component< std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr>; + std::shared_ptr, + std::shared_ptr, + std::shared_ptr>; /** * Get the manufactory @c Component for PreviewAlexaClient. diff --git a/applications/acsdkPreviewAlexaClient/src/CMakeLists.txt b/applications/acsdkPreviewAlexaClient/src/CMakeLists.txt index 1e2c39c151..f0ea97a360 100644 --- a/applications/acsdkPreviewAlexaClient/src/CMakeLists.txt +++ b/applications/acsdkPreviewAlexaClient/src/CMakeLists.txt @@ -3,7 +3,7 @@ list(APPEND LibPreviewAlexaClient_SOURCES PreviewAlexaClient.cpp PreviewAlexaClientComponent.cpp) -add_library(LibPreviewAlexaClient SHARED ${LibPreviewAlexaClient_SOURCES}) +add_library(LibPreviewAlexaClient ${LibPreviewAlexaClient_SOURCES}) target_include_directories(LibPreviewAlexaClient PUBLIC "${acsdkPreviewAlexaClient_SOURCE_DIR}/include") @@ -23,11 +23,14 @@ target_link_libraries(LibPreviewAlexaClient acsdkAlerts acsdkApplicationAudioPipelineFactoryInterfaces acsdkCore + acsdkCrypto acsdkDeviceSetup acsdkDeviceSetupInterfaces acsdkInteractionModel + acsdkKWD acsdkLibcurlHTTPContentFetcher acsdkManufactory + acsdkPkcs11 acsdkSampleApplicationInterfaces LibSampleApp) @@ -39,6 +42,10 @@ if (AUTH_MANAGER) target_link_libraries(LibPreviewAlexaClient acsdkAuthorization) endif() +if (PKCS11) + target_compile_definitions(LibPreviewAlexaClient PRIVATE ENABLE_PKCS11) +endif() + add_rpath_to_target("LibPreviewAlexaClient") add_executable(PreviewAlexaClient diff --git a/applications/acsdkPreviewAlexaClient/src/PreviewAlexaClient.cpp b/applications/acsdkPreviewAlexaClient/src/PreviewAlexaClient.cpp index 337c7447ee..50cf2cb0e9 100644 --- a/applications/acsdkPreviewAlexaClient/src/PreviewAlexaClient.cpp +++ b/applications/acsdkPreviewAlexaClient/src/PreviewAlexaClient.cpp @@ -49,7 +49,7 @@ #ifdef AUTH_MANAGER #include #include -#include +#include #include #include #endif @@ -67,10 +67,6 @@ #include #endif -#ifdef KWD -#include -#endif - #ifdef PORTAUDIO #include #endif @@ -167,12 +163,14 @@ using PreviewAlexaClientManufactory = Manufactory< std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, + std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, @@ -219,7 +217,9 @@ using PreviewAlexaClientManufactory = Manufactory< std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr>; + std::shared_ptr, + std::shared_ptr, + std::shared_ptr>; /// String to identify log entries originating from this file. static const std::string TAG("PreviewAlexaClient"); @@ -673,11 +673,10 @@ buildModeControllerAttributes( std::unique_ptr PreviewAlexaClient::create( std::shared_ptr consoleReader, const std::vector& configFiles, - const std::string& pathToInputFolder, const std::string& logLevel, std::shared_ptr diagnostics) { auto clientApplication = std::unique_ptr(new PreviewAlexaClient); - if (!clientApplication->initialize(consoleReader, configFiles, pathToInputFolder, logLevel, diagnostics)) { + if (!clientApplication->initialize(consoleReader, configFiles, logLevel, diagnostics)) { ACSDK_CRITICAL(LX("Failed to initialize SampleApplication")); return nullptr; } @@ -713,7 +712,6 @@ PreviewAlexaClient::~PreviewAlexaClient() { bool PreviewAlexaClient::initialize( std::shared_ptr consoleReader, const std::vector& configFiles, - const std::string& pathToInputFolder, const std::string& logLevel, std::shared_ptr diagnostics) { avsCommon::utils::logger::Level logLevelValue = avsCommon::utils::logger::Level::UNKNOWN; @@ -1068,31 +1066,13 @@ bool PreviewAlexaClient::initialize( * stream is used since this sample application will only have one microphone. */ - // Creating tap to talk audio provider - bool tapAlwaysReadable = true; - bool tapCanOverride = true; - bool tapCanBeOverridden = true; + // Creating tap to talk audio provider. + auto tapToTalkAudioProvider = alexaClientSDK::capabilityAgents::aip::AudioProvider::TapAudioProvider( + sharedDataStream, *compatibleAudioFormat); - alexaClientSDK::capabilityAgents::aip::AudioProvider tapToTalkAudioProvider( - sharedDataStream, - *compatibleAudioFormat, - alexaClientSDK::capabilityAgents::aip::ASRProfile::NEAR_FIELD, - tapAlwaysReadable, - tapCanOverride, - tapCanBeOverridden); - - // Creating hold to talk audio provider - bool holdAlwaysReadable = false; - bool holdCanOverride = true; - bool holdCanBeOverridden = false; - - alexaClientSDK::capabilityAgents::aip::AudioProvider holdToTalkAudioProvider( - sharedDataStream, - *compatibleAudioFormat, - alexaClientSDK::capabilityAgents::aip::ASRProfile::CLOSE_TALK, - holdAlwaysReadable, - holdCanOverride, - holdCanBeOverridden); + // Creating hold to talk audio provider. + auto holdToTalkAudioProvider = alexaClientSDK::capabilityAgents::aip::AudioProvider::HoldAudioProvider( + sharedDataStream, *compatibleAudioFormat); /* * Creating the DefaultClient - this component serves as an out-of-box default object that instantiates and "glues" @@ -1221,72 +1201,26 @@ bool PreviewAlexaClient::initialize( client->registerEndpoint(std::move(peripheralEndpoint)); #endif -// Creating wake word audio provider, if necessary + // Create null wake word audio provider and replace with wake word audio provider if KWD is on. + auto wakeWordAudioProvider = alexaClientSDK::capabilityAgents::aip::AudioProvider::null(); #ifdef KWD - bool wakeAlwaysReadable = true; - bool wakeCanOverride = false; - bool wakeCanBeOverridden = true; - - alexaClientSDK::capabilityAgents::aip::AudioProvider wakeWordAudioProvider( - sharedDataStream, - *compatibleAudioFormat, - alexaClientSDK::capabilityAgents::aip::ASRProfile::NEAR_FIELD, - wakeAlwaysReadable, - wakeCanOverride, - wakeCanBeOverridden); - - // This observer is notified any time a keyword is detected and notifies the DefaultClient to start recognizing. - auto keywordObserver = std::make_shared(client, wakeWordAudioProvider); - - m_keywordDetector = alexaClientSDK::kwd::KeywordDetectorProvider::create( - sharedDataStream, - *compatibleAudioFormat, - {keywordObserver}, - std::unordered_set< - std::shared_ptr>(), - pathToInputFolder); - if (!m_keywordDetector) { - ACSDK_CRITICAL(LX("Failed to create keyword detector!")); + // Check if keywordDetector was provided to manufactory and create wakeWordAudioProvider and keywordObserver if that + // is the case. + m_keywordDetector = + manufactory->get>(); + if (m_keywordDetector) { + wakeWordAudioProvider = alexaClientSDK::capabilityAgents::aip::AudioProvider::WakeAudioProvider( + sharedDataStream, *compatibleAudioFormat); + auto keywordObserver = + alexaClientSDK::sampleApp::KeywordObserver::create(client, wakeWordAudioProvider, m_keywordDetector); + } else { + ACSDK_CRITICAL(LX("Failed to create KWD")); + return false; } - - // If wake word is enabled, then creating the interaction manager with a wake word audio provider. - m_interactionManager = std::make_shared( - client, - micWrapper, - userInterfaceManager, -#ifdef ENABLE_PCC - phoneCaller, #endif -#ifdef ENABLE_MCC - meetingClient, - calendarClient, -#endif - holdToTalkAudioProvider, - tapToTalkAudioProvider, - m_guiRenderer, - wakeWordAudioProvider -#ifdef POWER_CONTROLLER - , - m_peripheralEndpointPowerHandler -#endif -#ifdef TOGGLE_CONTROLLER - , - m_peripheralEndpointToggleHandler -#endif -#ifdef RANGE_CONTROLLER - , - m_peripheralEndpointRangeHandler -#endif -#ifdef MODE_CONTROLLER - , - m_peripheralEndpointModeHandler -#endif - , - nullptr, - diagnostics); -#else + // clang-format off - // If wake word is not enabled, then creating the interaction manager without a wake word audio provider. + // Create InteractionManager m_interactionManager = std::make_shared( client, micWrapper, @@ -1301,7 +1235,7 @@ bool PreviewAlexaClient::initialize( holdToTalkAudioProvider, tapToTalkAudioProvider, m_guiRenderer, - capabilityAgents::aip::AudioProvider::null() + wakeWordAudioProvider #ifdef POWER_CONTROLLER , m_peripheralEndpointPowerHandler @@ -1322,7 +1256,6 @@ bool PreviewAlexaClient::initialize( nullptr, diagnostics); // clang-format on -#endif m_shutdownRequiredList.push_back(m_interactionManager); client->addAlexaDialogStateObserver(m_interactionManager); @@ -1350,12 +1283,15 @@ bool PreviewAlexaClient::initialize( #ifdef AUTH_MANAGER m_authManager->setRegistrationManager(client->getRegistrationManager()); + auto cryptoFactory = manufactory->get>(); + auto keyStore = manufactory->get>(); auto httpPost = avsCommon::utils::libcurlUtils::HttpPost::createHttpPostInterface(); m_lwaAdapter = acsdkAuthorization::lwa::LWAAuthorizationAdapter::create( configPtr, std::move(httpPost), deviceInfo, - acsdkAuthorization::lwa::SQLiteLWAAuthorizationStorage::createLWAAuthorizationStorageInterface(configPtr)); + acsdkAuthorization::lwa::LWAAuthorizationStorage::createLWAAuthorizationStorageInterface( + configPtr, "", cryptoFactory, keyStore)); if (!m_lwaAdapter) { ACSDK_CRITICAL(LX("Failed to create LWA Adapter!")); @@ -1658,5 +1594,12 @@ bool PreviewAlexaClient::addControllersToPeripheralEndpoint( } #endif +#ifdef DIAGNOSTICS +bool PreviewAlexaClient::initiateRestart() { + m_userInputManager->onLogout(); + return true; +} +#endif + } // namespace acsdkPreviewAlexaClient } // namespace alexaClientSDK diff --git a/applications/acsdkPreviewAlexaClient/src/PreviewAlexaClientComponent.cpp b/applications/acsdkPreviewAlexaClient/src/PreviewAlexaClientComponent.cpp index 696843ba05..35729349f0 100644 --- a/applications/acsdkPreviewAlexaClient/src/PreviewAlexaClientComponent.cpp +++ b/applications/acsdkPreviewAlexaClient/src/PreviewAlexaClientComponent.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include #include #include @@ -33,9 +35,11 @@ #include #include #include +#include #include #ifdef ENABLE_MC #include +#include #endif #include #include @@ -176,12 +180,12 @@ PreviewAlexaClientComponent getComponent( /** * Although these are the default options for PreviewAlexaClient, applications may modify or replace - * these with custom implementations. These include components like ACL, the logger, and AuthDelegateInterface, - * among others. + * these with custom implementations. These include components like ACL, the logger, and + * AuthDelegateInterface, among others. * - * For example, to replace the default null MetricRecorder with your own implementation, you could remove the - * default applications/acsdkNullMetricRecorder library and instead define your own metric recorder component - * in the same acsdkMetricRecorder namespace. + * For example, to replace the default null MetricRecorder with your own implementation, you could remove + * the default applications/acsdkNullMetricRecorder library and instead define your own metric recorder + * component in the same acsdkMetricRecorder namespace. */ .addComponent(acsdkAlexaCommunications::getComponent()) .addComponent(acsdkApplicationAudioPipelineFactory::getComponent()) @@ -238,6 +242,9 @@ PreviewAlexaClientComponent getComponent( .addComponent(acsdkExternalMediaPlayerAdapters::getComponent()) #endif + /// KWD Component. Default component is the null component. + .addComponent(acsdkKWD::getComponent()) + /// Capability Agents. Some CAs are still created in Default Client. .addComponent(acsdkAlerts::getComponent()) .addComponent(acsdkAudioPlayer::getComponent()) @@ -247,6 +254,7 @@ PreviewAlexaClientComponent getComponent( .addComponent(acsdkExternalMediaPlayer::getComponent()) .addComponent(acsdkInteractionModel::getComponent()) #ifdef ENABLE_MC + .addComponent(acsdkMessenger::getComponent()) .addComponent(acsdkMessagingController::getComponent()) #endif .addComponent(acsdkNotifications::getComponent()) @@ -255,7 +263,13 @@ PreviewAlexaClientComponent getComponent( .addComponent(capabilityAgents::system::getComponent()) .addRetainedFactory(capabilityAgents::templateRuntime::RenderPlayerInfoCardsProviderRegistrar:: createRenderPlayerInfoCardsProviderRegistrarInterface) - .addComponent(acsdkDeviceSetup::getComponent()); + .addComponent(acsdkDeviceSetup::getComponent()) +#ifdef ENABLE_PKCS11 + .addRetainedFactory(acsdkPkcs11::createKeyStore) +#else + .addInstance(std::shared_ptr()) +#endif + .addRetainedFactory(acsdkCrypto::createCryptoFactory); } } // namespace acsdkPreviewAlexaClient diff --git a/applications/acsdkPreviewAlexaClient/src/previewMain.cpp b/applications/acsdkPreviewAlexaClient/src/previewMain.cpp index 429b6577a3..2b09b573bb 100644 --- a/applications/acsdkPreviewAlexaClient/src/previewMain.cpp +++ b/applications/acsdkPreviewAlexaClient/src/previewMain.cpp @@ -32,12 +32,12 @@ using namespace alexaClientSDK::acsdkPreviewAlexaClient; * Function that evaluates if the PreviewAlexaClient invocation uses old-style or new-style opt-arg style invocation. * * @param argc The number of elements in the @c argv array. - * @param argv An array of @argc elements, containing the program name and all command-line arguments. + * @param argv An array of @a argc elements, containing the program name and all command-line arguments. * @return @c true of the invocation uses optarg style argument @c false otherwise. */ bool usesOptStyleArgs(int argc, char* argv[]) { for (int i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-C") || !strcmp(argv[i], "-K") || !strcmp(argv[i], "-L")) { + if (!strcmp(argv[i], "-C") || !strcmp(argv[i], "-L")) { return true; } } @@ -50,12 +50,11 @@ bool usesOptStyleArgs(int argc, char* argv[]) { * user input until the @c run() function returns. * * @param argc The number of elements in the @c argv array. - * @param argv An array of @argc elements, containing the program name and all command-line arguments. + * @param argv An array of @a argc elements, containing the program name and all command-line arguments. * @return @c EXIT_FAILURE if the program failed to initialize correctly, else @c EXIT_SUCCESS. */ int main(int argc, char* argv[]) { std::vector configFiles; - std::string pathToKWDInputFolder; std::string logLevel; if (usesOptStyleArgs(argc, argv)) { @@ -67,12 +66,6 @@ int main(int argc, char* argv[]) { } configFiles.push_back(std::string(argv[++i])); ConsolePrinter::simplePrint("configFile " + std::string(argv[i])); - } else if (strcmp(argv[i], "-K") == 0) { - if (i + 1 == argc) { - ConsolePrinter::simplePrint("No wakeword input specified for -K option"); - return SampleAppReturnCode::ERROR; - } - pathToKWDInputFolder = std::string(argv[++i]); } else if (strcmp(argv[i], "-L") == 0) { if (i + 1 == argc) { ConsolePrinter::simplePrint("No debugLevel specified for -L option"); @@ -82,24 +75,11 @@ int main(int argc, char* argv[]) { } else { ConsolePrinter::simplePrint( "USAGE: " + std::string(argv[0]) + " -C -C ... -C " + - " -K -L "); + " -L "); return SampleAppReturnCode::ERROR; } } } else { -#if defined(KWD_SENSORY) - if (argc < 3) { - ConsolePrinter::simplePrint( - "USAGE: " + std::string(argv[0]) + - " [log_level]"); - return SampleAppReturnCode::ERROR; - } else { - pathToKWDInputFolder = std::string(argv[2]); - if (4 == argc) { - logLevel = std::string(argv[3]); - } - } -#else if (argc < 2) { ConsolePrinter::simplePrint( "USAGE: " + std::string(argv[0]) + " [log_level]"); @@ -108,7 +88,6 @@ int main(int argc, char* argv[]) { if (3 == argc) { logLevel = std::string(argv[2]); } -#endif configFiles.push_back(std::string(argv[1])); ConsolePrinter::simplePrint("configFile " + std::string(argv[1])); @@ -130,7 +109,6 @@ int main(int argc, char* argv[]) { application = PreviewAlexaClient::create( consoleReader, configFiles, - pathToKWDInputFolder, logLevel #ifdef DIAGNOSTICS , diff --git a/applications/acsdkSampleApplicationCBLAuthRequester/src/CMakeLists.txt b/applications/acsdkSampleApplicationCBLAuthRequester/src/CMakeLists.txt index 14407d4543..2e9a4582a4 100644 --- a/applications/acsdkSampleApplicationCBLAuthRequester/src/CMakeLists.txt +++ b/applications/acsdkSampleApplicationCBLAuthRequester/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkSampleApplicationCBLAuthRequester") -add_library(acsdkSampleApplicationCBLAuthRequester SHARED +add_library(acsdkSampleApplicationCBLAuthRequester SampleApplicationCBLAuthRequester.cpp) target_include_directories(acsdkSampleApplicationCBLAuthRequester PUBLIC diff --git a/applications/acsdkSampleMetricRecorder/src/CMakeLists.txt b/applications/acsdkSampleMetricRecorder/src/CMakeLists.txt index cc3a868398..19ac0532b9 100644 --- a/applications/acsdkSampleMetricRecorder/src/CMakeLists.txt +++ b/applications/acsdkSampleMetricRecorder/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkSampleMetricRecorder") -add_library(acsdkSampleMetricRecorder SHARED +add_library(acsdkSampleMetricRecorder MetricRecorderComponent.cpp) target_include_directories(acsdkSampleMetricRecorder PUBLIC diff --git a/applications/acsdkSensoryAdapter/CMakeLists.txt b/applications/acsdkSensoryAdapter/CMakeLists.txt new file mode 100644 index 0000000000..fcc139d009 --- /dev/null +++ b/applications/acsdkSensoryAdapter/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(SENSORY LANGUAGES CXX) + +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +set(TARGET_KWD_LIB "SENSORY" PARENT_SCOPE) +set(KWD_ADAPTER_REGISTRATION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/acsdkKWDProvider/src/SensoryRegistration.cpp" PARENT_SCOPE) +set(KWD_COMPONENT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/acsdkKWD/src/KWDComponent.cpp" PARENT_SCOPE) + +add_subdirectory("src") +add_subdirectory("test") diff --git a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp new file mode 100644 index 0000000000..3e46d4705f --- /dev/null +++ b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp @@ -0,0 +1,68 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkKWD/KWDComponent.h" + +namespace alexaClientSDK { +namespace acsdkKWD { + +/// String to identify log entries originating from this file. +static const std::string TAG{"SensoryKWDComponent"}; + +/** + * 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) + +/// The Sensory Config values from AlexaClientSDKConfig.json +static const std::string SAMPLE_APP_CONFIG_ROOT_KEY("sampleApp"); +static const std::string SENSORY_CONFIG_ROOT_KEY("sensory"); +static const std::string SENSORY_MODEL_FILE_PATH("modelFilePath"); + +static std::shared_ptr createAbstractKeywordDetector( + const std::shared_ptr& stream, + const std::shared_ptr& audioFormat, + std::shared_ptr keywordNotifier, + std::shared_ptr keywordDetectorStateNotifier) { + std::string modelFilePath; + auto config = avsCommon::utils::configuration::ConfigurationNode::getRoot()[SAMPLE_APP_CONFIG_ROOT_KEY] + [SENSORY_CONFIG_ROOT_KEY]; + if (config) { + config.getString(SENSORY_MODEL_FILE_PATH, &modelFilePath); + } + if (modelFilePath.empty()) { + ACSDK_ERROR(LX("createFailed").d("reason", "emptyModelFilePath")); + return nullptr; + } + return kwd::SensoryKeywordDetector::create( + stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier, modelFilePath); +}; + +KWDComponent getComponent() { + return acsdkManufactory::ComponentAccumulator<>() + .addRetainedFactory(createAbstractKeywordDetector) + .addRetainedFactory(acsdkKWDImplementations::KWDNotifierFactories::createKeywordDetectorStateNotifier) + .addRetainedFactory(acsdkKWDImplementations::KWDNotifierFactories::createKeywordNotifier); +} + +} // namespace acsdkKWD +} // namespace alexaClientSDK diff --git a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp new file mode 100644 index 0000000000..d6850a647f --- /dev/null +++ b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * You may not use this file except in compliance with the terms and conditions + * set forth in the accompanying LICENSE.TXT file. + * + * THESE MATERIALS ARE PROVIDED ON AN "AS IS" BASIS. AMAZON SPECIFICALLY + * DISCLAIMS, WITH RESPECT TO THESE MATERIALS, ALL WARRANTIES, EXPRESS, IMPLIED, + * OR STATUTORY, INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR + * A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. + */ + +#include +#include +#include + +#include "KWDProvider/KeywordDetectorProvider.h" + +/** + * @deprecated + * This registration file is not needed if an application uses the manufactory to build its components. For example, + * the SDK Preview App does not use this registration file any longer. + * + * However, for applications that have yet to transition to using the manufactory, this file is provided so those + * applications can continue to use Keyword Detection (for example, the backwards-compatible SDK Sample App does use + * this file). + */ +namespace alexaClientSDK { +namespace kwd { + +/// The Sensory Config values from AlexaClientSDKConfig.json. +static const std::string SAMPLE_APP_CONFIG_ROOT_KEY("sampleApp"); +static const std::string SENSORY_CONFIG_ROOT_KEY("sensory"); +static const std::string SENSORY_MODEL_FILE_PATH("modelFilePath"); + +std::unique_ptr createSensoryKWDAdapter( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers) { + std::string modelFilePath; + auto config = avsCommon::utils::configuration::ConfigurationNode::getRoot()[SAMPLE_APP_CONFIG_ROOT_KEY] + [SENSORY_CONFIG_ROOT_KEY]; + if (config) { + config.getString(SENSORY_MODEL_FILE_PATH, &modelFilePath); + } + return kwd::SensoryKeywordDetector::create( + stream, audioFormat, keyWordObservers, keyWordDetectorStateObservers, modelFilePath); +} + +/// The registration object to register the Sensory adapter's creation method. +static const KeywordDetectorProvider::KWDRegistration g_sensoryAdapterRegistration(createSensoryKWDAdapter); + +} // namespace kwd +} // namespace alexaClientSDK diff --git a/KWD/Sensory/include/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h similarity index 68% rename from KWD/Sensory/include/Sensory/SensoryKeywordDetector.h rename to applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 957995bb6d..7a812dd4af 100644 --- a/KWD/Sensory/include/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -13,20 +13,22 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_KWD_SENSORY_INCLUDE_SENSORY_SENSORYKEYWORDDETECTOR_H_ -#define ALEXA_CLIENT_SDK_KWD_SENSORY_INCLUDE_SENSORY_SENSORYKEYWORDDETECTOR_H_ +#ifndef ACSDKSENSORYADAPTER_SENSORY_SENSORYKEYWORDDETECTOR_H_ +#define ACSDKSENSORYADAPTER_SENSORY_SENSORYKEYWORDDETECTOR_H_ #include #include #include #include -#include +#include +#include +#include #include -#include #include +#include +#include -#include "KWD/AbstractKeywordDetector.h" #include "snsr.h" namespace alexaClientSDK { @@ -36,10 +38,37 @@ using namespace avsCommon; using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; -class SensoryKeywordDetector : public AbstractKeywordDetector { +class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDetector { public: /** - * Creates a @c SensoryKeywordDetector. + * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value under + * sampleApp + * + * @param stream The stream of audio data. This should be formatted in LPCM encoded with 16 bits per sample and + * have a sample rate of 16 kHz. Additionally, the data should be in little endian format. + * @param audioFormat The format of the audio data located within the stream. + * @param keyWordNotifier The object with which to notifiy observers of keyword detections. + * @param KeyWordDetectorStateNotifier The object with which to notify observers of state changes in the engine. + * @param modelFilePath The path to the model file. + * @param msToPushPerIteration The amount of data in milliseconds to push to Sensory at a time. Smaller sizes will + * lead to less delay but more CPU usage. Additionally, larger amounts of data fed into the engine per iteration + * might lead longer delays before receiving keyword detection events. This has been defaulted to 10 milliseconds + * as it is a good trade off between CPU usage and recognition delay. Additionally, this was the amount used by + * Sensory in example code. + * @return A new @c SensoryKeywordDetector, or @c nullptr if the operation failed. + */ + static std::unique_ptr create( + const std::shared_ptr stream, + const std::shared_ptr& audioFormat, + std::shared_ptr keyWordNotifier, + std::shared_ptr KeyWordDetectorStateNotifier, + const std::string& modelFilePath, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * @deprecated + * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value under + * sampleApp * * @param stream The stream of audio data. This should be formatted in LPCM encoded with 16 bits per sample and * have a sample rate of 16 kHz. Additionally, the data should be in little endian format. @@ -55,7 +84,7 @@ class SensoryKeywordDetector : public AbstractKeywordDetector { * @return A new @c SensoryKeywordDetector, or @c nullptr if the operation failed. */ static std::unique_ptr create( - std::shared_ptr stream, + const std::shared_ptr stream, avsCommon::utils::AudioFormat audioFormat, std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers, @@ -74,8 +103,8 @@ class SensoryKeywordDetector : public AbstractKeywordDetector { * @param stream The stream of audio data. This should be formatted in LPCM encoded with 16 bits per sample and * have a sample rate of 16 kHz. Additionally, the data should be in little endian format. * @param audioFormat The format of the audio data located within the stream. - * @param keyWordObservers The observers to notify of keyword detections. - * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. + * @param keywordNotifier The object with which to notifiy observers of keyword detections. + * @param KeywordDetectorStateNotifier The object with which to notify observers of state changes in the engine. * @param msToPushPerIteration The amount of data in milliseconds to push to Sensory at a time. Smaller sizes will * lead to less delay but more CPU usage. Additionally, larger amounts of data fed into the engine per iteration * might lead longer delays before receiving keyword detection events. This has been defaulted to 10 milliseconds @@ -84,8 +113,8 @@ class SensoryKeywordDetector : public AbstractKeywordDetector { */ SensoryKeywordDetector( std::shared_ptr stream, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, + const std::shared_ptr keywordNotifier, + const std::shared_ptr KeywordDetectorStateNotifier, avsCommon::utils::AudioFormat audioFormat, std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); @@ -151,4 +180,4 @@ class SensoryKeywordDetector : public AbstractKeywordDetector { } // namespace kwd } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_KWD_SENSORY_INCLUDE_SENSORY_SENSORYKEYWORDDETECTOR_H_ +#endif // ACSDKSENSORYADAPTER_SENSORY_SENSORYKEYWORDDETECTOR_H_ diff --git a/applications/acsdkSensoryAdapter/src/CMakeLists.txt b/applications/acsdkSensoryAdapter/src/CMakeLists.txt new file mode 100644 index 0000000000..4acb6d91f7 --- /dev/null +++ b/applications/acsdkSensoryAdapter/src/CMakeLists.txt @@ -0,0 +1,16 @@ +add_definitions("-DACSDK_LOG_MODULE=sensoryKeywordDetector") +add_library(SENSORY + SensoryKeywordDetector.cpp) + +target_include_directories(SENSORY PUBLIC + "${SENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR}" + "${SENSORY_SOURCE_DIR}/include/acsdkSensoryAdapter") + +target_link_libraries(SENSORY + acsdkKWDImplementations + acsdkKWDInterfaces + AVSCommon + "${SENSORY_KEY_WORD_DETECTOR_LIB_PATH}") + +# install target +asdk_install() diff --git a/KWD/Sensory/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp similarity index 89% rename from KWD/Sensory/src/SensoryKeywordDetector.cpp rename to applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 851aa0f269..58bbca6003 100644 --- a/KWD/Sensory/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -15,6 +15,7 @@ #include +#include #include #include "Sensory/SensoryKeywordDetector.h" @@ -162,6 +163,7 @@ SnsrRC SensoryKeywordDetector::keyWordDetectedCallback(SnsrSession s, const char return SNSR_RC_OK; } +// Deprecated create method. std::unique_ptr SensoryKeywordDetector::create( std::shared_ptr stream, avsCommon::utils::AudioFormat audioFormat, @@ -170,26 +172,56 @@ std::unique_ptr SensoryKeywordDetector::create( keyWordDetectorStateObservers, const std::string& modelFilePath, std::chrono::milliseconds msToPushPerIteration) { + // Create Notifiers to be used instead of the observers. + auto keywordNotifier = acsdkKWDImplementations::KWDNotifierFactories::createKeywordNotifier(); + for (auto kwObserver : keyWordObservers) { + keywordNotifier->addObserver(kwObserver); + } + + auto keywordDetectorStateNotifier = + acsdkKWDImplementations::KWDNotifierFactories::createKeywordDetectorStateNotifier(); + for (auto kwdStateObserver : keyWordDetectorStateObservers) { + keywordDetectorStateNotifier->addObserver(kwdStateObserver); + } + + return create( + stream, + std::make_shared(audioFormat), + keywordNotifier, + keywordDetectorStateNotifier, + modelFilePath, + msToPushPerIteration); +} + +std::unique_ptr SensoryKeywordDetector::create( + std::shared_ptr stream, + const std::shared_ptr& audioFormat, + const std::shared_ptr keywordNotifier, + const std::shared_ptr keywordDetectorStateNotifier, + const std::string& modelFilePath, + std::chrono::milliseconds msToPushPerIteration) { if (!stream) { ACSDK_ERROR(LX("createFailed").d("reason", "nullStream")); return nullptr; } // TODO: ACSDK-249 - Investigate cpu usage of converting bytes between endianness and if it's not too much, do it. - if (isByteswappingRequired(audioFormat)) { + if (isByteswappingRequired(*audioFormat)) { ACSDK_ERROR(LX("createFailed").d("reason", "endianMismatch")); return nullptr; } - if (!isAudioFormatCompatibleWithSensory(audioFormat)) { + if (!isAudioFormatCompatibleWithSensory(*audioFormat)) { return nullptr; } + std::unique_ptr detector(new SensoryKeywordDetector( - stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat, msToPushPerIteration)); + stream, keywordNotifier, keywordDetectorStateNotifier, *audioFormat, msToPushPerIteration)); if (!detector->init(modelFilePath)) { ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); return nullptr; } + return detector; } @@ -203,11 +235,11 @@ SensoryKeywordDetector::~SensoryKeywordDetector() { SensoryKeywordDetector::SensoryKeywordDetector( std::shared_ptr stream, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, + std::shared_ptr keywordNotifier, + std::shared_ptr keywordDetectorStateNotifier, avsCommon::utils::AudioFormat audioFormat, std::chrono::milliseconds msToPushPerIteration) : - AbstractKeywordDetector(keyWordObservers, keyWordDetectorStateObservers), + acsdkKWDImplementations::AbstractKeywordDetector(keywordNotifier, keywordDetectorStateNotifier), m_stream{stream}, m_session{nullptr}, m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { diff --git a/applications/acsdkSensoryAdapter/test/CMakeLists.txt b/applications/acsdkSensoryAdapter/test/CMakeLists.txt new file mode 100644 index 0000000000..73f8bceb97 --- /dev/null +++ b/applications/acsdkSensoryAdapter/test/CMakeLists.txt @@ -0,0 +1,5 @@ +set(INCLUDES "${SENSORY_SOURCE_DIR}/include" "${KWD_SOURCE_DIR}/include" "${SENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR}") + +set(INPUTFOLDER "${acsdkKWDImplementations_SOURCE_DIR}/inputs") + +discover_unit_tests("${INCLUDES}" SENSORY "${INPUTFOLDER}") diff --git a/KWD/Sensory/test/SensoryKeywordDetectorTest.cpp b/applications/acsdkSensoryAdapter/test/SensoryKeywordDetectorTest.cpp similarity index 65% rename from KWD/Sensory/test/SensoryKeywordDetectorTest.cpp rename to applications/acsdkSensoryAdapter/test/SensoryKeywordDetectorTest.cpp index c732c1e990..97e9c9ab79 100644 --- a/KWD/Sensory/test/SensoryKeywordDetectorTest.cpp +++ b/applications/acsdkSensoryAdapter/test/SensoryKeywordDetectorTest.cpp @@ -24,9 +24,11 @@ #include +#include #include #include #include +#include #include #include "Sensory/SensoryKeywordDetector.h" @@ -38,6 +40,7 @@ namespace test { using namespace avsCommon; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; +using namespace avsCommon::utils::configuration; /// The path to the inputs folder that should be passed in via command line argument. std::string inputsDirPath; @@ -209,54 +212,47 @@ class testStateObserver : public KeyWordDetectorStateObserverInterface { class SensoryKeywordTest : public ::testing::Test { protected: /** - * Reads audio from a WAV file. + * Reads audio from a WAV file and write into audio input stream. * * @param fileName The path of the file to read from. - * @param [out] errorOccurred Lets users know if any errors occurred while parsing the file. - * @return A vector of int16_t containing the raw audio data of the WAV file without the RIFF header. + * @return True if successfully read and written into stream. */ - std::vector readAudioFromFile(const std::string& fileName, bool* errorOccurred) { + bool readAudioFromFileIntoStream(const std::string& fileName) { const int RIFF_HEADER_SIZE = 44; std::ifstream inputFile(fileName.c_str(), std::ifstream::binary); if (!inputFile.good()) { std::cout << "Couldn't open audio file!" << std::endl; - if (errorOccurred) { - *errorOccurred = true; - } - return {}; + return false; } inputFile.seekg(0, std::ios::end); int fileLengthInBytes = inputFile.tellg(); if (fileLengthInBytes <= RIFF_HEADER_SIZE) { std::cout << "File should be larger than 44 bytes, which is the size of the RIFF header" << std::endl; - if (errorOccurred) { - *errorOccurred = true; - } - return {}; + return false; } inputFile.seekg(RIFF_HEADER_SIZE, std::ios::beg); int numSamples = (fileLengthInBytes - RIFF_HEADER_SIZE) / 2; - std::vector retVal(numSamples, 0); + std::vector audioData(numSamples, 0); - inputFile.read((char*)&retVal[0], numSamples * 2); + inputFile.read((char*)&audioData[0], numSamples * 2); if (inputFile.gcount() != numSamples * 2) { std::cout << "Error reading audio file" << std::endl; - if (errorOccurred) { - *errorOccurred = true; - } - return {}; + return false; } inputFile.close(); - if (errorOccurred) { - *errorOccurred = false; + + auto bytesWritten = m_writer->write(audioData.data(), audioData.size()); + if (bytesWritten <= 0) { + std::cout << "Unable to write audio data" << std::endl; + return false; } - return retVal; + return true; } /** @@ -287,77 +283,102 @@ class SensoryKeywordTest : public ::testing::Test { return false; } - std::shared_ptr keyWordObserver1; + std::shared_ptr m_keywordNotifier; - std::shared_ptr keyWordObserver2; + std::shared_ptr m_keywordDetectorStateNotifier; - std::shared_ptr stateObserver; + std::shared_ptr m_keyWordObserver1; - AudioFormat compatibleAudioFormat; + std::shared_ptr m_keyWordObserver2; - std::string modelFilePath; + std::shared_ptr m_stateObserver; - virtual void SetUp() { - keyWordObserver1 = std::make_shared(); - keyWordObserver2 = std::make_shared(); - stateObserver = std::make_shared(); + std::shared_ptr m_compatibleAudioFormat; + + std::shared_ptr m_buffer; - compatibleAudioFormat.sampleRateHz = COMPATIBLE_SAMPLE_RATE; - compatibleAudioFormat.sampleSizeInBits = COMPATIBLE_SAMPLE_SIZE_IN_BITS; - compatibleAudioFormat.numChannels = COMPATIBLE_NUM_CHANNELS; - compatibleAudioFormat.endianness = COMPATIBLE_ENDIANNESS; - compatibleAudioFormat.encoding = COMPATIBLE_ENCODING; + std::shared_ptr m_sds; + + std::unique_ptr m_writer; + + std::unique_ptr m_detector; + + // Create initial detector and writers and assert that they are created successfully. + virtual void SetUp() { + m_keywordNotifier = acsdkKWDImplementations::KWDNotifierFactories::createKeywordNotifier(); + m_keywordDetectorStateNotifier = + acsdkKWDImplementations::KWDNotifierFactories::createKeywordDetectorStateNotifier(); + + m_keyWordObserver1 = std::make_shared(); + m_keyWordObserver2 = std::make_shared(); + m_stateObserver = std::make_shared(); + + m_compatibleAudioFormat = std::make_shared(); + m_compatibleAudioFormat->sampleRateHz = COMPATIBLE_SAMPLE_RATE; + m_compatibleAudioFormat->sampleSizeInBits = COMPATIBLE_SAMPLE_SIZE_IN_BITS; + m_compatibleAudioFormat->numChannels = COMPATIBLE_NUM_CHANNELS; + m_compatibleAudioFormat->endianness = COMPATIBLE_ENDIANNESS; + m_compatibleAudioFormat->encoding = COMPATIBLE_ENCODING; + + m_buffer = std::make_shared(500000); + m_sds = avsCommon::avs::AudioInputStream::create(m_buffer, 2, 1); + ASSERT_TRUE(m_sds); + m_writer = m_sds->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE); + ASSERT_TRUE(m_writer); std::ifstream filePresent((inputsDirPath + MODEL_FILE).c_str()); ASSERT_TRUE(filePresent.good()) << "Unable to find " + inputsDirPath + MODEL_FILE << ". Please place model file within this location."; - modelFilePath = inputsDirPath + MODEL_FILE; + m_detector = SensoryKeywordDetector::create( + m_sds, + m_compatibleAudioFormat, + m_keywordNotifier, + m_keywordDetectorStateNotifier, + inputsDirPath + MODEL_FILE); + ASSERT_TRUE(m_detector); + m_detector->addKeyWordObserver(m_keyWordObserver1); + m_detector->addKeyWordDetectorStateObserver(m_stateObserver); } }; +/// Test that we create a valid detector using deprecated Create method +TEST_F(SensoryKeywordTest, test_createDetectorDeprecated) { + auto buffer = std::make_shared(500000); + auto sds = avsCommon::avs::AudioInputStream::create(buffer, 2, 1); + auto detector = SensoryKeywordDetector::create( + std::move(sds), *m_compatibleAudioFormat, {m_keyWordObserver1}, {m_stateObserver}, inputsDirPath + MODEL_FILE); + ASSERT_NE(detector, nullptr); +} + /// Tests that we don't get back a valid detector if an invalid stream is passed in. TEST_F(SensoryKeywordTest, test_invalidStream) { auto detector = SensoryKeywordDetector::create( - nullptr, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath); - ASSERT_FALSE(detector); + nullptr, + m_compatibleAudioFormat, + m_keywordNotifier, + m_keywordDetectorStateNotifier, + inputsDirPath + MODEL_FILE); + ASSERT_EQ(detector, nullptr); } /// Tests that we don't get back a valid detector if an invalid endianness is passed in. TEST_F(SensoryKeywordTest, test_incompatibleEndianness) { - auto rawBuffer = std::make_shared(500000); - auto uniqueSds = avsCommon::avs::AudioInputStream::create(rawBuffer, 2, 1); - std::shared_ptr sds = std::move(uniqueSds); - - compatibleAudioFormat.endianness = AudioFormat::Endianness::BIG; + m_compatibleAudioFormat->endianness = AudioFormat::Endianness::BIG; - auto detector = - SensoryKeywordDetector::create(sds, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath); - ASSERT_FALSE(detector); + auto detector = SensoryKeywordDetector::create( + m_sds, m_compatibleAudioFormat, m_keywordNotifier, m_keywordDetectorStateNotifier, inputsDirPath + MODEL_FILE); + ASSERT_EQ(detector, nullptr); } /// Tests that we get back the expected number of keywords for the four_alexa.wav file for one keyword observer. TEST_F(SensoryKeywordTest, test_getExpectedNumberOfDetectionsInFourAlexasAudioFileForOneObserver) { - auto fourAlexasBuffer = std::make_shared(500000); - auto fourAlexasSds = avsCommon::avs::AudioInputStream::create(fourAlexasBuffer, 2, 1); - std::shared_ptr fourAlexasAudioBuffer = std::move(fourAlexasSds); - - std::unique_ptr fourAlexasAudioBufferWriter = - fourAlexasAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE); - std::string audioFilePath = inputsDirPath + FOUR_ALEXAS_AUDIO_FILE; - bool error; - std::vector audioData = readAudioFromFile(audioFilePath, &error); - ASSERT_FALSE(error); + ASSERT_TRUE(readAudioFromFileIntoStream(audioFilePath)); - fourAlexasAudioBufferWriter->write(audioData.data(), audioData.size()); - - auto detector = SensoryKeywordDetector::create( - fourAlexasAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath); - ASSERT_TRUE(detector); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); auto detections = - keyWordObserver1->waitForNDetections(END_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE.size(), DEFAULT_TIMEOUT); + m_keyWordObserver1->waitForNDetections(END_INDICES_OF_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE.size(), DEFAULT_TIMEOUT); ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE); for (unsigned int i = 0; i < END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.size(); ++i) { @@ -371,28 +392,12 @@ TEST_F(SensoryKeywordTest, test_getExpectedNumberOfDetectionsInFourAlexasAudioFi /// Tests that we get back the expected number of keywords for the four_alexa.wav file for two keyword observers. TEST_F(SensoryKeywordTest, test_getExpectedNumberOfDetectionsInFourAlexasAudioFileForTwoObservers) { - auto fourAlexasBuffer = std::make_shared(500000); - auto fourAlexasSds = avsCommon::avs::AudioInputStream::create(fourAlexasBuffer, 2, 1); - std::shared_ptr fourAlexasAudioBuffer = std::move(fourAlexasSds); - - std::unique_ptr fourAlexasAudioBufferWriter = - fourAlexasAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE); - std::string audioFilePath = inputsDirPath + FOUR_ALEXAS_AUDIO_FILE; - bool error; - std::vector audioData = readAudioFromFile(audioFilePath, &error); - ASSERT_FALSE(error); + ASSERT_TRUE(readAudioFromFileIntoStream(audioFilePath)); - fourAlexasAudioBufferWriter->write(audioData.data(), audioData.size()); + m_detector->addKeyWordObserver(m_keyWordObserver2); - auto detector = SensoryKeywordDetector::create( - fourAlexasAudioBuffer, - compatibleAudioFormat, - {keyWordObserver1, keyWordObserver2}, - {stateObserver}, - modelFilePath); - ASSERT_TRUE(detector); - auto detections = keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE, DEFAULT_TIMEOUT); + auto detections = m_keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE, DEFAULT_TIMEOUT); ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE); for (unsigned int i = 0; i < END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.size(); ++i) { @@ -403,7 +408,7 @@ TEST_F(SensoryKeywordTest, test_getExpectedNumberOfDetectionsInFourAlexasAudioFi KEYWORD)); } - detections = keyWordObserver2->waitForNDetections(NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE, DEFAULT_TIMEOUT); + detections = m_keyWordObserver2->waitForNDetections(NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE, DEFAULT_TIMEOUT); ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_FOUR_ALEXAS_AUDIO_FILE); for (unsigned int i = 0; i < END_INDICES_OF_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE.size(); ++i) { @@ -420,26 +425,11 @@ TEST_F(SensoryKeywordTest, test_getExpectedNumberOfDetectionsInFourAlexasAudioFi * observer. */ TEST_F(SensoryKeywordTest, test_getExpectedNumberOfDetectionsInAlexaStopAlexaJokeAudioFileForOneObserver) { - auto alexaStopAlexaJokeBuffer = std::make_shared(500000); - auto alexaStopAlexaJokeSds = avsCommon::avs::AudioInputStream::create(alexaStopAlexaJokeBuffer, 2, 1); - std::shared_ptr alexaStopAlexaJokeAudioBuffer = std::move(alexaStopAlexaJokeSds); - - std::unique_ptr alexaStopAlexaJokeAudioBufferWriter = - alexaStopAlexaJokeAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE); - std::string audioFilePath = inputsDirPath + ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE; - bool error; - std::vector audioData = readAudioFromFile(audioFilePath, &error); - ASSERT_FALSE(error); - - alexaStopAlexaJokeAudioBufferWriter->write(audioData.data(), audioData.size()); - - auto detector = SensoryKeywordDetector::create( - alexaStopAlexaJokeAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath); - ASSERT_TRUE(detector); + ASSERT_TRUE(readAudioFromFileIntoStream(audioFilePath)); auto detections = - keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE, DEFAULT_TIMEOUT); + m_keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE, DEFAULT_TIMEOUT); ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE); @@ -454,26 +444,12 @@ TEST_F(SensoryKeywordTest, test_getExpectedNumberOfDetectionsInAlexaStopAlexaJok /// Tests that the detector state changes to ACTIVE when the detector is initialized properly. TEST_F(SensoryKeywordTest, test_getActiveState) { - auto alexaStopAlexaJokeBuffer = std::make_shared(500000); - auto alexaStopAlexaJokeSds = avsCommon::avs::AudioInputStream::create(alexaStopAlexaJokeBuffer, 2, 1); - std::shared_ptr alexaStopAlexaJokeAudioBuffer = std::move(alexaStopAlexaJokeSds); - - std::unique_ptr alexaStopAlexaJokeAudioBufferWriter = - alexaStopAlexaJokeAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE); - std::string audioFilePath = inputsDirPath + ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE; - bool error; - std::vector audioData = readAudioFromFile(audioFilePath, &error); - ASSERT_FALSE(error); - - alexaStopAlexaJokeAudioBufferWriter->write(audioData.data(), audioData.size()); + ASSERT_TRUE(readAudioFromFileIntoStream(audioFilePath)); - auto detector = SensoryKeywordDetector::create( - alexaStopAlexaJokeAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath); - ASSERT_TRUE(detector); bool stateChanged = false; KeyWordDetectorStateObserverInterface::KeyWordDetectorState stateReceived = - stateObserver->waitForStateChange(DEFAULT_TIMEOUT, &stateChanged); + m_stateObserver->waitForStateChange(DEFAULT_TIMEOUT, &stateChanged); ASSERT_TRUE(stateChanged); ASSERT_EQ(stateReceived, KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); } @@ -483,38 +459,23 @@ TEST_F(SensoryKeywordTest, test_getActiveState) { * of the SDS passed in and all keyword detections have occurred. */ TEST_F(SensoryKeywordTest, test_getStreamClosedState) { - auto alexaStopAlexaJokeBuffer = std::make_shared(500000); - auto alexaStopAlexaJokeSds = avsCommon::avs::AudioInputStream::create(alexaStopAlexaJokeBuffer, 2, 1); - std::shared_ptr alexaStopAlexaJokeAudioBuffer = std::move(alexaStopAlexaJokeSds); - - std::unique_ptr alexaStopAlexaJokeAudioBufferWriter = - alexaStopAlexaJokeAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE); - std::string audioFilePath = inputsDirPath + ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE; - bool error; - std::vector audioData = readAudioFromFile(audioFilePath, &error); - ASSERT_FALSE(error); + ASSERT_TRUE(readAudioFromFileIntoStream(audioFilePath)); - alexaStopAlexaJokeAudioBufferWriter->write(audioData.data(), audioData.size()); - - auto detector = SensoryKeywordDetector::create( - alexaStopAlexaJokeAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath); - ASSERT_TRUE(detector); - - // so that when we close the writer, we know for sure that the reader will be closed + // so that when we close the writer, we know for sure that the reader will be closed. auto detections = - keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE, DEFAULT_TIMEOUT); + m_keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE, DEFAULT_TIMEOUT); ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE); bool stateChanged = false; KeyWordDetectorStateObserverInterface::KeyWordDetectorState stateReceived = - stateObserver->waitForStateChange(DEFAULT_TIMEOUT, &stateChanged); + m_stateObserver->waitForStateChange(DEFAULT_TIMEOUT, &stateChanged); ASSERT_TRUE(stateChanged); ASSERT_EQ(stateReceived, KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); - alexaStopAlexaJokeAudioBufferWriter->close(); + m_writer->close(); stateChanged = false; - stateReceived = stateObserver->waitForStateChange(DEFAULT_TIMEOUT, &stateChanged); + stateReceived = m_stateObserver->waitForStateChange(DEFAULT_TIMEOUT, &stateChanged); ASSERT_TRUE(stateChanged); ASSERT_EQ(stateReceived, KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED); } @@ -525,29 +486,14 @@ TEST_F(SensoryKeywordTest, test_getStreamClosedState) { * Sensory wrapper uses is working as expected. */ TEST_F(SensoryKeywordTest, test_getExpectedNumberOfDetectionsInAlexaStopAlexaJokeAudioFileWithRandomDataAtBeginning) { - auto alexaStopAlexaJokeBuffer = std::make_shared(500000); - auto alexaStopAlexaJokeSds = avsCommon::avs::AudioInputStream::create(alexaStopAlexaJokeBuffer, 2, 1); - std::shared_ptr alexaStopAlexaJokeAudioBuffer = std::move(alexaStopAlexaJokeSds); - - std::unique_ptr alexaStopAlexaJokeAudioBufferWriter = - alexaStopAlexaJokeAudioBuffer->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE); - - std::string audioFilePath = inputsDirPath + ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE; - bool error; - std::vector audioData = readAudioFromFile(audioFilePath, &error); - ASSERT_FALSE(error); - std::vector randomData(5000, 0); - alexaStopAlexaJokeAudioBufferWriter->write(randomData.data(), randomData.size()); + m_writer->write(randomData.data(), randomData.size()); - auto detector = SensoryKeywordDetector::create( - alexaStopAlexaJokeAudioBuffer, compatibleAudioFormat, {keyWordObserver1}, {stateObserver}, modelFilePath); - ASSERT_TRUE(detector); - - alexaStopAlexaJokeAudioBufferWriter->write(audioData.data(), audioData.size()); + std::string audioFilePath = inputsDirPath + ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE; + ASSERT_TRUE(readAudioFromFileIntoStream(audioFilePath)); auto detections = - keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE, DEFAULT_TIMEOUT); + m_keyWordObserver1->waitForNDetections(NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE, DEFAULT_TIMEOUT); ASSERT_EQ(detections.size(), NUM_ALEXAS_IN_ALEXA_STOP_ALEXA_JOKE_AUDIO_FILE); diff --git a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Alert.h b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Alert.h index cdf69a4377..5363476384 100644 --- a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Alert.h +++ b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Alert.h @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ACSDKALERTS_ALERT_H_ -#define ACSDKALERTS_ALERT_H_ +#ifndef ALEXA_CLIENT_SDK_ACSDKALERTS_INCLUDE_ACSDKALERTS_ALERT_H_ +#define ALEXA_CLIENT_SDK_ACSDKALERTS_INCLUDE_ACSDKALERTS_ALERT_H_ #include "acsdkAlerts/Renderer/Renderer.h" #include "acsdkAlerts/Renderer/RendererObserverInterface.h" @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -166,7 +167,7 @@ class Alert /** * Constructor. */ - DynamicData() : state{State::SET}, loopCount{0}, hasRenderingFailed{false} { + DynamicData() : state{State::SET}, loopCount{0}, hasRenderingFailed{false}, originalTime(""), label("") { } /// The state of the alert. State state; @@ -180,6 +181,12 @@ class Alert /// A flag to capture if rendering any of asset urls failed. bool hasRenderingFailed; + /// An attribute representing the local time when the alert was originally set. + std::string originalTime; + + /// An attribute representing the content of the alert. + std::string label; + /// The assets associated with this alert. AssetConfiguration assetConfiguration; }; @@ -221,6 +228,23 @@ class Alert std::string scheduledTime_ISO_8601; }; + /** + * A utility function to convert an originalTime string to an optional of @c OriginalTime. + * + * @param originalTimeStr a string of original time. + * @return An optional with a valid originalTime string; an empty optional otherwise. + */ + static avsCommon::utils::Optional + validateOriginalTimeString(const std::string& originalTimeStr); + + /** + * A utility function to convert a label string to an optional. + * + * @param label a string of label. + * @return An optional with a valid label string; an empty optional otherwise. + */ + static avsCommon::utils::Optional validateLabelString(const std::string& label); + /** * A utility function to convert an alert state enum value to a string. * @@ -379,6 +403,34 @@ class Alert */ std::string getScheduledTime_ISO_8601() const; + /** + * Gets the UTC time for when the alert should occur. + * + * @return The UTC time for when the alert should occur. + */ + std::chrono::system_clock::time_point getScheduledTime_Utc_TimePoint() const; + + /** + * Gets the @c Type of the alert. + * + * @return The type of the alert. + */ + acsdkAlertsInterfaces::AlertObserverInterface::Type getType() const; + + /** + * Gets the optional with @c OriginalTime. + * + * @return An optional with @c OriginalTime if originalTime is valid for the alert; an empty optional otherwise. + */ + avsCommon::utils::Optional getOriginalTime() const; + + /** + * Gets the optional with label string. + * + * @return An optional with label string if label is valid for the alert; an empty optional otherwise. + */ + avsCommon::utils::Optional getLabel() const; + /** * Returns the state of the alert. * @@ -469,6 +521,13 @@ class Alert void printDiagnostic(); private: + /** + * Returns the AVS token for the alert. + * + * @return The AVS token for the alert. + */ + std::string getTokenLocked() const; + /** * Returns the alert's scheduled time in Unix. * @@ -483,6 +542,28 @@ class Alert */ std::string getScheduledTime_ISO_8601Locked() const; + /** + * Gets scheduled time point in UTC time. This function should be called with a lock. + * + * @return The alert's scheduled time point in UTC time. + */ + std::chrono::system_clock::time_point getScheduledTime_Utc_TimePointLocked() const; + + /** + * Gets the optional of the original time. This function should be called with a lock. + * + * @return An optional with @c OriginalTime if originalTime is valid for the alert; an empty optional otherwise. + */ + avsCommon::utils::Optional getOriginalTimeLocked() + const; + + /** + * Gets the optional of the label string. This function should be called with a lock. + * + * @return An optional with label string if label is valid for the alert; an empty optional otherwise. + */ + avsCommon::utils::Optional getLabelLocked() const; + /** * Utility function to begin the alert's renderer in an unlocked context */ @@ -592,4 +673,4 @@ inline std::ostream& operator<<(std::ostream& stream, const Alert::ParseFromJson } // namespace acsdkAlerts } // namespace alexaClientSDK -#endif // ACSDKALERTS_ALERT_H_ +#endif // ALEXA_CLIENT_SDK_ACSDKALERTS_INCLUDE_ACSDKALERTS_ALERT_H_ diff --git a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertScheduler.h b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertScheduler.h index 4ec2722b8f..dc63b4fd6d 100644 --- a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertScheduler.h +++ b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertScheduler.h @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ACSDKALERTS_ALERTSCHEDULER_H_ -#define ACSDKALERTS_ALERTSCHEDULER_H_ +#ifndef ALEXA_CLIENT_SDK_ACSDKALERTS_INCLUDE_ACSDKALERTS_ALERTSCHEDULER_H_ +#define ALEXA_CLIENT_SDK_ACSDKALERTS_INCLUDE_ACSDKALERTS_ALERTSCHEDULER_H_ #include "acsdkAlerts/Storage/AlertStorageInterface.h" @@ -62,11 +62,10 @@ class AlertScheduler : public acsdkAlertsInterfaces::AlertObserverInterface { std::chrono::seconds alertPastDueTimeLimitSeconds, std::shared_ptr metricRecorder = nullptr); - void onAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - State state, - const std::string& reason = "") override; + /// @name AlertObserverInterface function. + /// @{ + void onAlertStateChange(const AlertInfo& alertInfo) override; + /// @} /** * Initialization. @@ -225,12 +224,9 @@ class AlertScheduler : public acsdkAlertsInterfaces::AlertObserverInterface { /** * A handler function which will be called by our internal executor when a managed alert changes state. * - * @param alertToken The AVS token identifying the alert. - * @param alertType The type of alert. - * @param state The state of the alert. - * @param reason The reason the the state changed, if applicable. + * @param alertInfo The information of the alert. */ - void executeOnAlertStateChange(std::string alertToken, std::string alertType, State state, std::string reason); + void executeOnAlertStateChange(const AlertInfo& alertInfo); /** * Update an alert with the new Dynamic Data (scheduled time, assets). This function cannot update an active alert @@ -250,30 +246,16 @@ class AlertScheduler : public acsdkAlertsInterfaces::AlertObserverInterface { /** * A utility function which wraps the executor submission to notify our observer. * - * @param alertToken The AVS token identifying the alert. - * @param alertType The type of the alert. - * @param state The state of the alert. - * @param reason The reason the the state changed, if applicable. + * @param alertInfo The information of the alert. */ - void notifyObserver( - const std::string& alertToken, - const std::string& alertType, - acsdkAlertsInterfaces::AlertObserverInterface::State state, - const std::string& reason = ""); + void notifyObserver(const acsdkAlertsInterfaces::AlertObserverInterface::AlertInfo& alertInfo); /** * A handler function which will be called by our internal executor when a managed alert changes state. * - * @param alertToken The AVS token identifying the alert. - * @param alertType The type of the alert. - * @param state The state of the alert. - * @param reason The reason the the state changed, if applicable. + * @param alertInfo The information of the alert. */ - void executeNotifyObserver( - const std::string& alertToken, - const std::string& alertType, - acsdkAlertsInterfaces::AlertObserverInterface::State state, - const std::string& reason = ""); + void executeNotifyObserver(const acsdkAlertsInterfaces::AlertObserverInterface::AlertInfo& alertInfo); /** * Utility function to set the timer for the next scheduled alert. This function requires @c m_mutex be locked. @@ -293,10 +275,9 @@ class AlertScheduler : public acsdkAlertsInterfaces::AlertObserverInterface { /** * Utility function to be called when an alert is ready to activate. * - * @param alertToken The AVS token of the alert that should become active. - * @param alertType The type of the alert. + * @param alertInfo The information of the alert. */ - void onAlertReady(const std::string& alertToken, const std::string& alertType); + void onAlertReady(const acsdkAlertsInterfaces::AlertObserverInterface::AlertInfo& alertInfo); /** * Utility function to query if a given alert is active. This function requires @c m_mutex be locked. @@ -388,4 +369,4 @@ class AlertScheduler : public acsdkAlertsInterfaces::AlertObserverInterface { } // namespace acsdkAlerts } // namespace alexaClientSDK -#endif // ACSDKALERTS_ALERTSCHEDULER_H_ +#endif // ALEXA_CLIENT_SDK_ACSDKALERTS_INCLUDE_ACSDKALERTS_ALERTSCHEDULER_H_ diff --git a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertsCapabilityAgent.h b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertsCapabilityAgent.h index 66ccd65161..0f2b66ea64 100644 --- a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertsCapabilityAgent.h +++ b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertsCapabilityAgent.h @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ACSDKALERTS_ALERTSCAPABILITYAGENT_H_ -#define ACSDKALERTS_ALERTSCAPABILITYAGENT_H_ +#ifndef ALEXA_CLIENT_SDK_ACSDKALERTS_INCLUDE_ACSDKALERTS_ALERTSCAPABILITYAGENT_H_ +#define ALEXA_CLIENT_SDK_ACSDKALERTS_INCLUDE_ACSDKALERTS_ALERTSCAPABILITYAGENT_H_ #include #include @@ -181,12 +181,11 @@ class AlertsCapabilityAgent void onConnectionStatusChanged(const Status status, const ChangedReason reason) override; void onFocusChanged(avsCommon::avs::FocusState focusState, avsCommon::avs::MixingBehavior behavior) override; + /// @} - void onAlertStateChange( - const std::string& token, - const std::string& alertType, - acsdkAlertsInterfaces::AlertObserverInterface::State state, - const std::string& reason) override; + /// @name AlertObserverInterface function. + /// @{ + void onAlertStateChange(const AlertInfo& alertInfo) override; /// @} /// @name FocusManagerObserverInterface Functions @@ -336,16 +335,9 @@ class AlertsCapabilityAgent /** * A handler function which will be called by our internal executor when an alert's status changes. * - * @param alertToken The AVS token identifying the alert. - * @param alertType The type of the alert. - * @param state The state of the alert. - * @param reason The reason the the state changed, if applicable. + * @param alertInfo The information of the alert. */ - void executeOnAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - acsdkAlertsInterfaces::AlertObserverInterface::State state, - const std::string& reason); + void executeOnAlertStateChange(const acsdkAlertsInterfaces::AlertObserverInterface::AlertInfo& alertInfo); /** * A handler function which will be called by our internal executor to add an alert observer. @@ -364,16 +356,9 @@ class AlertsCapabilityAgent /** * A handler function which will be called by our internal executor to notify observers of alert changes. * - * @param alertToken The AVS token identifying the alert. - * @param alertType The type of the alert. - * @param state The state of the alert. - * @param reason The reason the the state changed, if applicable. + * @param alertInfo The information of the alert. */ - void executeNotifyObservers( - const std::string& alertToken, - const std::string& alertType, - acsdkAlertsInterfaces::AlertObserverInterface::State state, - const std::string& reason = ""); + void executeNotifyObservers(const acsdkAlertsInterfaces::AlertObserverInterface::AlertInfo& alertInfo); /** * A handler function which will be called by our internal executor to remove all alerts currently being managed. @@ -646,4 +631,4 @@ class AlertsCapabilityAgent } // namespace acsdkAlerts } // namespace alexaClientSDK -#endif // ACSDKALERTS_ALERTSCAPABILITYAGENT_H_ +#endif // ALEXA_CLIENT_SDK_ACSDKALERTS_INCLUDE_ACSDKALERTS_ALERTSCAPABILITYAGENT_H_ diff --git a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertsComponent.h b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertsComponent.h index c1c97f1cac..ad561ddbd8 100644 --- a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertsComponent.h +++ b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/AlertsComponent.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,7 @@ using AlertsComponent = acsdkManufactory::Component< acsdkManufactory::Import>, + acsdkManufactory::Import>, acsdkManufactory::Import>, acsdkManufactory::Import>, acsdkManufactory::Import>, diff --git a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Renderer/Renderer.h b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Renderer/Renderer.h index 2ca43f6c24..7b92923487 100644 --- a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Renderer/Renderer.h +++ b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Renderer/Renderer.h @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include #include @@ -50,6 +52,7 @@ namespace renderer { */ class Renderer : public RendererInterface + , public avsCommon::sdkInterfaces::InternetConnectionObserverInterface , public avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface , public avsCommon::utils::RequiresShutdown , public std::enable_shared_from_this { @@ -59,6 +62,7 @@ class Renderer * * @param audioPipelineFactory The @c ApplicationAudioPlayerInterface instance to use to create the notifications * media player for rendering audio. + * @param internetConnectionMonitor The object use to monitor connectivity with the internet. * @param metricRecorder the metric recorder. * @param shutdownNotifier the @c ShutdownNotifier to notify if a shutdown occurred. * @return The @c Renderer object. @@ -67,7 +71,8 @@ class Renderer const std::shared_ptr& audioPipelineFactory, const std::shared_ptr& metricRecorder, - const std::shared_ptr& shutdownNotifier); + const std::shared_ptr& shutdownNotifier, + const std::shared_ptr& internetConnectionMonitor); /** * Creates a @c Renderer. @@ -75,11 +80,14 @@ class Renderer * @deprecated Use createAlertRenderer. * @param mediaPlayer the @c MediaPlayerInterface that the @c Renderer object will interact with. * @param metricRecorder the metric recorder. + * @param internetConnectionMonitor The object use to monitor connectivity with the internet. * @return The @c Renderer object. */ static std::shared_ptr create( std::shared_ptr mediaPlayer, - std::shared_ptr metricRecorder = nullptr); + std::shared_ptr metricRecorder = nullptr, + std::shared_ptr internetConnectionMonitor = + nullptr); void start( std::shared_ptr observer, @@ -106,6 +114,8 @@ class Renderer std::string error, const avsCommon::utils::mediaPlayer::MediaPlayerState& state) override; + void onConnectionStatusChanged(bool connected) override; + private: /// A type that identifies which source is currently being operated on. using SourceId = avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId; @@ -118,7 +128,8 @@ class Renderer */ Renderer( std::shared_ptr mediaPlayer, - std::shared_ptr metricRecorder); + std::shared_ptr metricRecorder, + std::shared_ptr internetConnectionMonitor); /** * @name Executor Thread Functions @@ -370,6 +381,12 @@ class Renderer /// The time that the alert started rendering. std::chrono::steady_clock::time_point m_renderStartTime; + + /// Variable to capture if we are currently connected to Wifi. + std::atomic_bool m_isNetworkConnected; + + /// Object providing notification of gaining and losing internet connectivity. + std::shared_ptr m_internetConnectionMonitor; }; } // namespace renderer diff --git a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Storage/SQLiteAlertStorage.h b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Storage/SQLiteAlertStorage.h index 0b5f2e1480..a67f08e269 100644 --- a/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Storage/SQLiteAlertStorage.h +++ b/capabilities/Alerts/acsdkAlerts/include/acsdkAlerts/Storage/SQLiteAlertStorage.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include "acsdkAlerts/Storage/AlertStorageInterface.h" @@ -42,11 +44,13 @@ class SQLiteAlertStorage : public AlertStorageInterface { * * @param configurationRoot The global config object. * @param audioFactory A factory that can produce default alert sounds. + * @param metricRecorder The @c MetricRecorderInterface used to record metrics. * @return Pointer to the SQLiteAlertStorage object, nullptr if there's an error creating it. */ static std::shared_ptr createAlertStorageInterface( const std::shared_ptr& configurationRoot, - const std::shared_ptr& audioFactory); + const std::shared_ptr& audioFactory, + std::shared_ptr metricRecorder = nullptr); /** * Factory method for creating a storage object for Alerts based on an SQLite database. @@ -54,11 +58,13 @@ class SQLiteAlertStorage : public AlertStorageInterface { * @deprecated * @param configurationRoot The global config object. * @param alertsAudioFactory A factory that can produce default alert sounds. + * @param metricRecorder The @c MetricRecorderInterface used to record metrics. * @return Pointer to the SQLiteAlertStorage object, nullptr if there's an error creating it. */ static std::unique_ptr create( const avsCommon::utils::configuration::ConfigurationNode& configurationRoot, - const std::shared_ptr& alertsAudioFactory); + const std::shared_ptr& alertsAudioFactory, + std::shared_ptr metricRecorder = nullptr); /** * On destruction, close the underlying database. @@ -116,14 +122,16 @@ class SQLiteAlertStorage : public AlertStorageInterface { * * @param dbFilePath The location of the SQLite database file. * @param alertsAudioFactory A factory that can produce default alert sounds. + * @param metricRecorder The @c MetricRecorderInterface used to record metrics. */ SQLiteAlertStorage( const std::string& dbFilePath, - const std::shared_ptr& alertsAudioFactory); + const std::shared_ptr& alertsAudioFactory, + std::shared_ptr metricRecorder); /** * A utility function to help us load alerts from different versions of the alerts table. Currently, versions - * 1 and 2 are supported. + * 2 and 3 are supported. * * @param dbVersion The version of the database we wish to load from. * @param[out] alertContainer The container where alerts should be stored. @@ -178,10 +186,11 @@ class SQLiteAlertStorage : public AlertStorageInterface { /** * Query whether an alert is currently stored in the alerts table with the given token. * + * @param dbVersion The version of the alerts table. * @param token The AVS token which uniquely identifies an alert. * @return @c true If the alert is stored in the alerts database, @c false otherwise. */ - bool alertExists(const std::string& token); + bool alertExists(const int dbVersion, const std::string& token); /** * Check whether offline alerts table includes column event_time_iso_8601. @@ -200,18 +209,65 @@ class SQLiteAlertStorage : public AlertStorageInterface { bool offlineAlertExists(const int dbVersion, const std::string& token); /** - * A utility function to migrate an existing offline alerts v1 table to v2. + * A utility function to migrate data from an existing offline alerts v1 table to v2. * - * @return Whether migrate offline alerts data from v1 to v2 succeeds. If v2 table already exists or if there is no - * existing v1 table, return true. + * @return Whether migrating offline alerts data from v1 to v2 succeeds. If v2 table already exists or if there is + * no existing v1 table, return true. */ bool migrateOfflineAlertsDbFromV1ToV2(); + /** + * A utility function to migrate data from an existing alerts v2 table to v3. + * + * @return Whether migrating alerts data from v2 to v3 succeeds. If v3 table already exists or if there is no + * existing v2 table, return true. + */ + bool migrateAlertsDbFromV2ToV3(); + + /** + * Store an alert to alerts v2 table. + * + * @param id The alert id of the alert to be stored. + * @param alert The alert to be stored. + * @return Whether storing the alert to v2 table succeeds. + */ + bool storeAlertToV2(const int id, std::shared_ptr alert); + + /** + * Modify an alert in the databse. + * + * @param dbVersion The version of the alerts table. + * @param alert The alert to be modified. + * @return Whether modifying the alert succeeds. + */ + bool modifyAlert(const int dbVersion, std::shared_ptr alert); + + /** + * Retry data migration for db uplevel by using the @c RetryTimer with a list of expotential back-off retry times. + * + * @tparam Task The type of task to execute. + * @tparam Args The argument types for the task to execute. + * @param task A callable type representing a task. + * @param args The arguments to call the task with. + * @return Whether retrying data migration succeeds. + */ + template + bool retryDataMigration(Task task, Args&&... args); + /// A member that stores a factory that produces audio streams for alerts. std::shared_ptr m_alertsAudioFactory; /// The underlying database class. alexaClientSDK::storage::sqliteStorage::SQLiteDatabase m_db; + + /// The @c MetricRecorderInterface used to record metrics. + std::shared_ptr m_metricRecorder; + + /// The retry timer used to restart a task. + avsCommon::utils::RetryTimer m_retryTimer; + + /// The wait event for a retry. + avsCommon::utils::WaitEvent m_waitRetryEvent; }; } // namespace storage diff --git a/capabilities/Alerts/acsdkAlerts/src/Alert.cpp b/capabilities/Alerts/acsdkAlerts/src/Alert.cpp index e56ac09862..c617534062 100644 --- a/capabilities/Alerts/acsdkAlerts/src/Alert.cpp +++ b/capabilities/Alerts/acsdkAlerts/src/Alert.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -59,12 +58,55 @@ static const std::string KEY_LOOP_COUNT = "loopCount"; static const std::string KEY_LOOP_PAUSE_IN_MILLISECONDS = "loopPauseInMilliSeconds"; /// String for lookup of the backgroundAssetId for an alert, if assets are provided. static const std::string KEY_BACKGROUND_ASSET_ID = "backgroundAlertAsset"; +/// String for lookup of the label for an alert. +static const std::string KEY_LABEL = "label"; +/// String for lookup of the original time for an alert. +static const std::string KEY_ORIGINAL_TIME = "originalTime"; + +/// The length of the hour field in an original time string. +static const int ORIGINAL_TIME_STRING_HOUR_LENGTH = 2; +/// The length of the minute field in an original time string. +static const int ORIGINAL_TIME_STRING_MINUTE_LENGTH = 2; +/// The length of the second field in an original time string. +static const int ORIGINAL_TIME_STRING_SECOND_LENGTH = 2; +/// The length of the millisecond field in an original time string. +static const int ORIGINAL_TIME_STRING_MILLISECOND_LENGTH = 3; +/// The colon separator used in an original time string. +static const std::string ORIGINAL_TIME_STRING_COLON_SEPARATOR = ":"; +/// The dot separator used in an original time string. +static const std::string ORIGINAL_TIME_STRING_DOT_SEPARATOR = "."; +/// The offset into an original time string where the hour begins. +static const unsigned int ORIGINAL_TIME_STRING_HOUR_OFFSET = 0; +/// The offset into an original time string where the minute begins. +static const unsigned int ORIGINAL_TIME_STRING_MINUTE_OFFSET = + ORIGINAL_TIME_STRING_HOUR_OFFSET + ORIGINAL_TIME_STRING_HOUR_LENGTH + ORIGINAL_TIME_STRING_COLON_SEPARATOR.length(); +/// The offset into an original time string where the second begins. +static const unsigned int ORIGINAL_TIME_STRING_SECOND_OFFSET = ORIGINAL_TIME_STRING_MINUTE_OFFSET + + ORIGINAL_TIME_STRING_MINUTE_LENGTH + + ORIGINAL_TIME_STRING_COLON_SEPARATOR.length(); +/// The offset into an original time string where the millisecond begins. +static const unsigned int ORIGINAL_TIME_STRING_MILLISECOND_OFFSET = ORIGINAL_TIME_STRING_SECOND_OFFSET + + ORIGINAL_TIME_STRING_SECOND_LENGTH + + ORIGINAL_TIME_STRING_DOT_SEPARATOR.length(); +/// The total expected length of an original time string. +static const unsigned long ORIGINAL_TIME_STRING_LENGTH = + ORIGINAL_TIME_STRING_MILLISECOND_OFFSET + ORIGINAL_TIME_STRING_MILLISECOND_LENGTH; /// We won't allow an alert to render more than 1 hour. const std::chrono::seconds MAXIMUM_ALERT_RENDERING_TIME = std::chrono::hours(1); /// Length of pause of alert sounds when played in background const auto BACKGROUND_ALERT_SOUND_PAUSE_TIME = std::chrono::seconds(10); +/// String representation of an alert of alarm type. +static const std::string ALERT_TYPE_ALARM_STRING = + AlertObserverInterface::typeToString(AlertObserverInterface::Type::ALARM); +/// String representation of an alert of timer type. +static const std::string ALERT_TYPE_TIMER_STRING = + AlertObserverInterface::typeToString(AlertObserverInterface::Type::TIMER); +/// String representation of an alert of reminder type. +static const std::string ALERT_TYPE_REMINDER_STRING = + AlertObserverInterface::typeToString(AlertObserverInterface::Type::REMINDER); + /// String to identify log entries originating from this file. static const std::string TAG("Alert"); @@ -234,6 +276,29 @@ Alert::ParseFromJsonStatus Alert::parseFromJson(const rapidjson::Value& payload, return ParseFromJsonStatus::INVALID_VALUE; } + std::string label; + // it's ok if the label is not set. + if (!retrieveValue(payload, KEY_LABEL, &label)) { + ACSDK_DEBUG5(LX("parseFromJson : label is not present.")); + } else { + ACSDK_DEBUG5(LX("parseFromJson").d("label", label)); + m_dynamicData.label = label; + } + + std::string originalTime; + // it's ok if the originalTime is not set. + if (!retrieveValue(payload, KEY_ORIGINAL_TIME, &originalTime)) { + ACSDK_DEBUG5(LX("parseFromJson : originalTime is not present.")); + } else { + ACSDK_DEBUG5(LX("parseFromJson").d("originalTime", originalTime)); + /// if originalTime is malformed, ignore the value. + avsCommon::utils::Optional validatedOriginalTime = + validateOriginalTimeString(originalTime); + if (validatedOriginalTime.hasValue()) { + m_dynamicData.originalTime = AlertObserverInterface::originalTimeToString(validatedOriginalTime.value()); + } + } + return parseAlertAssetConfigurationFromJson(payload, &m_dynamicData.assetConfiguration, &m_dynamicData); } @@ -318,8 +383,24 @@ void Alert::activate() { m_dynamicData.state = Alert::State::ACTIVATING; if (!m_maxLengthTimer.isActive()) { - if (!m_maxLengthTimer.start(MAXIMUM_ALERT_RENDERING_TIME, std::bind(&Alert::onMaxTimerExpiration, this)) - .valid()) { + // An alert should only play a set duration from its scheduled time, + // the math is scheduledTime - currentTime + set duration time. + auto renderingTime = std::chrono::seconds{getScheduledTime_UnixLocked() - TimePoint::now().getTime_Unix()} + + MAXIMUM_ALERT_RENDERING_TIME; + if (renderingTime <= std::chrono::seconds(0)) { + lock.unlock(); + ACSDK_ERROR(LX("activate").m("Calculated negative rendering time, alert shouldn't be playing.")); + m_observer->onAlertStateChange(AlertObserverInterface::AlertInfo( + getToken(), + getType(), + AlertObserverInterface::State::ERROR, + getScheduledTime_Utc_TimePoint(), + getOriginalTime(), + getLabel())); + return; + } + ACSDK_INFO(LX("alert").d("renderingTime in seconds:", renderingTime.count())); + if (!m_maxLengthTimer.start(renderingTime, std::bind(&Alert::onMaxTimerExpiration, this)).valid()) { ACSDK_ERROR(LX("executeStartFailed").d("reason", "startTimerFailed")); } } @@ -337,7 +418,13 @@ void Alert::deactivate(StopReason reason) { if (isAlertPaused()) { m_dynamicData.state = Alert::State::STOPPED; - m_observer->onAlertStateChange(m_staticData.token, getTypeName(), AlertObserverInterface::State::STOPPED); + m_observer->onAlertStateChange(AlertObserverInterface::AlertInfo( + getTokenLocked(), + getType(), + AlertObserverInterface::State::STOPPED, + getScheduledTime_Utc_TimePointLocked(), + getOriginalTimeLocked(), + getLabelLocked())); } m_renderer->stop(); @@ -426,12 +513,13 @@ bool Alert::setAlertData(StaticData* staticData, DynamicData* dynamicData) { } void Alert::onRendererStateChange(RendererObserverInterface::State state, const std::string& reason) { - ACSDK_DEBUG1(LX("onRendererStateChange") - .d("state", state) - .d("reason", reason) - .d("m_hasTimerExpired", m_hasTimerExpired) - .d("m_dynamicData.state", m_dynamicData.state) - .d("token", getToken())); + ACSDK_INFO(LX("onRendererStateChange") + .d("state", state) + .d("reason", reason) + .d("m_hasTimerExpired", m_hasTimerExpired) + .d("m_dynamicData.state", m_dynamicData.state)); + ACSDK_DEBUG1(LX("onRendererStateChange").d("token", getToken())); + bool shouldNotifyObserver = false; bool shouldRetryRendering = false; AlertObserverInterface::State notifyState = AlertObserverInterface::State::ERROR; @@ -485,6 +573,20 @@ void Alert::onRendererStateChange(RendererObserverInterface::State state, const } else if (m_startRendererAgainAfterFullStop) { m_startRendererAgainAfterFullStop = false; shouldRetryRendering = true; + } else if ( + !m_dynamicData.assetConfiguration.assetPlayOrderItems.empty() && + !m_dynamicData.hasRenderingFailed) { + // If the renderer failed while handling a url, let's presume there are network issues and render + // the on-device background audio sound instead. + ACSDK_ERROR(LX("onRendererStateChangeFailed") + .d("reason", reason) + .m("Renderer stopped unexpectedly. Retrying with local background audio sound.")); + m_dynamicData.hasRenderingFailed = true; + shouldRetryRendering = true; + } else { + shouldNotifyObserver = true; + notifyState = AlertObserverInterface::State::ERROR; + notifyReason = reason; } } break; @@ -522,7 +624,15 @@ void Alert::onRendererStateChange(RendererObserverInterface::State state, const lock.unlock(); if (shouldNotifyObserver && observerCopy) { - observerCopy->onAlertStateChange(m_staticData.token, getTypeName(), notifyState, notifyReason); + auto alertInfo = AlertObserverInterface::AlertInfo( + getToken(), + getType(), + notifyState, + getScheduledTime_Utc_TimePoint(), + getOriginalTime(), + getLabel(), + notifyReason); + observerCopy->onAlertStateChange(alertInfo); } if (shouldRetryRendering) { @@ -536,6 +646,10 @@ void Alert::onRendererStateChange(RendererObserverInterface::State state, const std::string Alert::getToken() const { std::lock_guard lock(m_mutex); + return getTokenLocked(); +} + +std::string Alert::getTokenLocked() const { return m_staticData.token; } @@ -557,6 +671,34 @@ std::string Alert::getScheduledTime_ISO_8601Locked() const { return m_dynamicData.timePoint.getTime_ISO_8601(); } +std::chrono::system_clock::time_point Alert::getScheduledTime_Utc_TimePoint() const { + std::lock_guard lock(m_mutex); + return getScheduledTime_Utc_TimePointLocked(); +} + +std::chrono::system_clock::time_point Alert::getScheduledTime_Utc_TimePointLocked() const { + return m_dynamicData.timePoint.getTime_Utc_TimePoint(); +} + +avsCommon::utils::Optional Alert::getOriginalTime() const { + std::lock_guard lock(m_mutex); + return getOriginalTimeLocked(); +} + +avsCommon::utils::Optional Alert::getOriginalTimeLocked() + const { + return validateOriginalTimeString(m_dynamicData.originalTime); +} + +avsCommon::utils::Optional Alert::getLabel() const { + std::lock_guard lock(m_mutex); + return getLabelLocked(); +} + +avsCommon::utils::Optional Alert::getLabelLocked() const { + return validateLabelString(m_dynamicData.label); +} + Alert::State Alert::getState() const { std::lock_guard lock(m_mutex); return m_dynamicData.state; @@ -599,7 +741,13 @@ bool Alert::snooze(const std::string& updatedScheduledTime) { if (isAlertPaused()) { m_dynamicData.state = Alert::State::SNOOZED; - m_observer->onAlertStateChange(m_staticData.token, getTypeName(), AlertObserverInterface::State::SNOOZED); + m_observer->onAlertStateChange(AlertObserverInterface::AlertInfo( + getTokenLocked(), + getType(), + AlertObserverInterface::State::SNOOZED, + getScheduledTime_Utc_TimePointLocked(), + getOriginalTimeLocked(), + getLabelLocked())); } m_renderer->stop(); @@ -700,14 +848,21 @@ void Alert::startRendererLocked() { } void Alert::onMaxTimerExpiration() { - ACSDK_DEBUG1(LX("onMaxTimerExpiration").d("token", getToken())); + ACSDK_INFO(LX("onMaxTimerExpiration")); + ACSDK_DEBUG1(LX("expired token").d("token", getToken())); std::lock_guard lock(m_mutex); m_dynamicData.state = Alert::State::STOPPING; m_hasTimerExpired = true; if (isAlertPaused()) { m_dynamicData.state = Alert::State::STOPPED; - m_observer->onAlertStateChange(m_staticData.token, getTypeName(), AlertObserverInterface::State::STOPPED); + m_observer->onAlertStateChange(AlertObserverInterface::AlertInfo( + getTokenLocked(), + getType(), + AlertObserverInterface::State::STOPPED, + getScheduledTime_Utc_TimePointLocked(), + getOriginalTimeLocked(), + getLabelLocked())); } m_renderer->stop(); @@ -717,7 +872,6 @@ bool Alert::isPastDue(int64_t currentUnixTime, std::chrono::seconds timeLimit) { std::lock_guard lock(m_mutex); int64_t cutoffTime = currentUnixTime - timeLimit.count(); - return (m_dynamicData.timePoint.getTime_Unix() < cutoffTime); } @@ -737,11 +891,88 @@ Alert::ContextInfo Alert::getContextInfo() const { return ContextInfo(m_staticData.token, getTypeName(), getScheduledTime_ISO_8601Locked()); } +AlertObserverInterface::Type Alert::getType() const { + if (ALERT_TYPE_ALARM_STRING == getTypeName()) { + return AlertObserverInterface::Type::ALARM; + } else if (ALERT_TYPE_TIMER_STRING == getTypeName()) { + return AlertObserverInterface::Type::TIMER; + } else if (ALERT_TYPE_REMINDER_STRING == getTypeName()) { + return AlertObserverInterface::Type::REMINDER; + } + ACSDK_ERROR(LX("getTypeError").d("invalidTypeString", getTypeName())); + /// If an unrecognized value is received by AlertsCA, it should default to an ALARM. + return AlertObserverInterface::Type::ALARM; +} + bool Alert::isAlertPaused() const { return avsCommon::avs::FocusState::BACKGROUND == m_focusState && avsCommon::avs::MixingBehavior::MUST_PAUSE == m_mixingBehavior; } +avsCommon::utils::Optional Alert::validateOriginalTimeString( + const std::string& originalTimeStr) { + auto optionalOriginalTime = avsCommon::utils::Optional(); + if (originalTimeStr.empty()) { + ACSDK_DEBUG7(LX("validateOriginalTimeString: empty originalTimeStr")); + return optionalOriginalTime; + } + + if (originalTimeStr.length() != ORIGINAL_TIME_STRING_LENGTH) { + ACSDK_ERROR(LX("validateOriginalTimeString: invalid originalTimeStr=" + originalTimeStr)); + return optionalOriginalTime; + } + + int hour = 0, minute = 0, second = 0, millisecond = 0; + + if (!stringToInt( + originalTimeStr.substr(ORIGINAL_TIME_STRING_HOUR_OFFSET, ORIGINAL_TIME_STRING_HOUR_LENGTH), &hour) || + !AlertObserverInterface::withinBounds( + hour, AlertObserverInterface::ORIGINAL_TIME_FIELD_MIN, AlertObserverInterface::ORIGINAL_TIME_HOUR_MAX)) { + ACSDK_ERROR(LX("validateOriginalTimeStringFailed").m("invalid hour: " + originalTimeStr)); + return optionalOriginalTime; + } + + if (!stringToInt( + originalTimeStr.substr(ORIGINAL_TIME_STRING_MINUTE_OFFSET, ORIGINAL_TIME_STRING_MINUTE_LENGTH), &minute) || + !AlertObserverInterface::withinBounds( + minute, + AlertObserverInterface::ORIGINAL_TIME_FIELD_MIN, + AlertObserverInterface::ORIGINAL_TIME_MINUTE_MAX)) { + ACSDK_ERROR(LX("validateOriginalTimeStringFailed").m("invalid minute: " + originalTimeStr)); + return optionalOriginalTime; + } + + if (!stringToInt( + originalTimeStr.substr(ORIGINAL_TIME_STRING_SECOND_OFFSET, ORIGINAL_TIME_STRING_SECOND_LENGTH), &second) || + !AlertObserverInterface::withinBounds( + second, + AlertObserverInterface::ORIGINAL_TIME_FIELD_MIN, + AlertObserverInterface::ORIGINAL_TIME_SECOND_MAX)) { + ACSDK_ERROR(LX("validateOriginalTimeStringFailed").m("invalid second: " + originalTimeStr)); + return optionalOriginalTime; + } + + if (!stringToInt( + originalTimeStr.substr(ORIGINAL_TIME_STRING_MILLISECOND_OFFSET, ORIGINAL_TIME_STRING_MILLISECOND_LENGTH), + &millisecond) || + !AlertObserverInterface::withinBounds( + millisecond, + AlertObserverInterface::ORIGINAL_TIME_FIELD_MIN, + AlertObserverInterface::ORIGINAL_TIME_MILLISECOND_MAX)) { + ACSDK_ERROR(LX("validateOriginalTimeStringFailed").m("invalid millisecond: " + originalTimeStr)); + return optionalOriginalTime; + } + return avsCommon::utils::Optional({hour, minute, second, millisecond}); +} + +avsCommon::utils::Optional Alert::validateLabelString(const std::string& label) { + if (label.empty()) { + ACSDK_DEBUG5(LX("validateLabelString: empty label")); + return avsCommon::utils::Optional(); + } + return avsCommon::utils::Optional(label); +} + std::string Alert::stateToString(Alert::State state) { switch (state) { case Alert::State::UNSET: diff --git a/capabilities/Alerts/acsdkAlerts/src/AlertScheduler.cpp b/capabilities/Alerts/acsdkAlerts/src/AlertScheduler.cpp index 97cc85422b..375144a46b 100644 --- a/capabilities/Alerts/acsdkAlerts/src/AlertScheduler.cpp +++ b/capabilities/Alerts/acsdkAlerts/src/AlertScheduler.cpp @@ -89,19 +89,13 @@ AlertScheduler::AlertScheduler( m_metricRecorder{metricRecorder} { } -void AlertScheduler::onAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - State state, - const std::string& reason) { +void AlertScheduler::onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { ACSDK_DEBUG5(LX("onAlertStateChange") - .d("alertToken", alertToken) - .d("alertType", alertType) - .d("state", state) - .d("reason", reason)); - m_executor.submit([this, alertToken, alertType, state, reason]() { - executeOnAlertStateChange(alertToken, alertType, state, reason); - }); + .d("alertToken", alertInfo.token) + .d("alertType", alertInfo.type) + .d("state", alertInfo.state) + .d("reason", alertInfo.reason)); + m_executor.submit([this, alertInfo]() { executeOnAlertStateChange(alertInfo); }); } bool AlertScheduler::initialize( @@ -233,11 +227,13 @@ bool AlertScheduler::reloadAlertsFromDatabase( bool alertIsCurrentlyActive = m_activeAlert && (m_activeAlert->getToken() == alert->getToken()); if (!alertIsCurrentlyActive) { if (alert->isPastDue(unixEpochNow, m_alertPastDueTimeLimit)) { - notifyObserver( + notifyObserver(AlertInfo( alert->getToken(), - alert->getTypeName(), + alert->getType(), AlertObserverInterface::State::PAST_DUE, - alert->getScheduledTime_ISO_8601()); + alert->getScheduledTime_Utc_TimePoint(), + alert->getOriginalTime(), + alert->getLabel())); ACSDK_DEBUG5(LX(ALERT_PAST_DUE_DURING_SCHEDULING).d("alertId", alert->getToken())); ++alertPastDueDuringSchedulingCount; eraseAlert(alert); @@ -254,11 +250,13 @@ bool AlertScheduler::reloadAlertsFromDatabase( alert->setObserver(this); m_scheduledAlerts.insert(alert); - notifyObserver( + notifyObserver(AlertInfo( alert->getToken(), - alert->getTypeName(), + alert->getType(), AlertObserverInterface::State::SCHEDULED_FOR_LATER, - alert->getScheduledTime_ISO_8601()); + alert->getScheduledTime_Utc_TimePoint(), + alert->getOriginalTime(), + alert->getLabel())); } } } @@ -405,7 +403,13 @@ bool AlertScheduler::deleteAlerts(const std::list& tokenList) { for (auto& alert : alertsToBeRemoved) { m_scheduledAlerts.erase(alert); - notifyObserver(alert->getToken(), alert->getTypeName(), AlertObserverInterface::State::DELETED); + notifyObserver(AlertInfo( + alert->getToken(), + alert->getType(), + AlertObserverInterface::State::DELETED, + alert->getScheduledTime_Utc_TimePoint(), + alert->getOriginalTime(), + alert->getLabel())); } setTimerForNextAlertLocked(); @@ -438,9 +442,13 @@ void AlertScheduler::updateFocus(avsCommon::avs::FocusState focusState, avsCommo case FocusState::FOREGROUND: if (m_activeAlert) { m_activeAlert->setFocusState(m_focusState, m_mixingBehavior); - auto token = m_activeAlert->getToken(); - auto type = m_activeAlert->getTypeName(); - notifyObserver(token, type, AlertObserverInterface::State::FOCUS_ENTERED_FOREGROUND); + notifyObserver(AlertInfo( + m_activeAlert->getToken(), + m_activeAlert->getType(), + AlertObserverInterface::State::FOCUS_ENTERED_FOREGROUND, + m_activeAlert->getScheduledTime_Utc_TimePoint(), + m_activeAlert->getOriginalTime(), + m_activeAlert->getLabel())); } else { activateNextAlertLocked(); } @@ -449,9 +457,13 @@ void AlertScheduler::updateFocus(avsCommon::avs::FocusState focusState, avsCommo case FocusState::BACKGROUND: if (m_activeAlert) { m_activeAlert->setFocusState(m_focusState, m_mixingBehavior); - auto token = m_activeAlert->getToken(); - auto type = m_activeAlert->getTypeName(); - notifyObserver(token, type, AlertObserverInterface::State::FOCUS_ENTERED_BACKGROUND); + notifyObserver(AlertInfo( + m_activeAlert->getToken(), + m_activeAlert->getType(), + AlertObserverInterface::State::FOCUS_ENTERED_BACKGROUND, + m_activeAlert->getScheduledTime_Utc_TimePoint(), + m_activeAlert->getOriginalTime(), + m_activeAlert->getLabel())); } else { activateNextAlertLocked(); } @@ -503,7 +515,13 @@ void AlertScheduler::clearData(Alert::StopReason reason) { } for (const auto& alert : m_scheduledAlerts) { - notifyObserver(alert->getToken(), alert->getTypeName(), AlertObserverInterface::State::DELETED); + notifyObserver(AlertInfo( + alert->getToken(), + alert->getType(), + AlertObserverInterface::State::DELETED, + alert->getScheduledTime_Utc_TimePoint(), + alert->getOriginalTime(), + alert->getLabel())); } m_scheduledAlerts.clear(); @@ -528,47 +546,50 @@ void AlertScheduler::shutdown() { m_scheduledAlerts.clear(); } -void AlertScheduler::executeOnAlertStateChange( - std::string alertToken, - std::string alertType, - State state, - std::string reason) { - ACSDK_DEBUG1(LX("executeOnAlertStateChange").d("alertToken", alertToken).d("state", state).d("reason", reason)); +void AlertScheduler::executeOnAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { + ACSDK_DEBUG1(LX("executeOnAlertStateChange") + .d("alertToken", alertInfo.token) + .d("state", alertInfo.state) + .d("reason", alertInfo.reason)); std::lock_guard lock(m_mutex); - switch (state) { + switch (alertInfo.state) { case State::READY: - notifyObserver(alertToken, alertType, state, reason); + notifyObserver(alertInfo); break; case State::STARTED: if (m_activeAlert && Alert::State::ACTIVATING == m_activeAlert->getState()) { m_activeAlert->setStateActive(); m_alertStorage->modify(m_activeAlert); - notifyObserver(alertToken, alertType, state, m_activeAlert->getScheduledTime_ISO_8601()); + notifyObserver(alertInfo); + AlertInfo infoCopy = alertInfo; // In addition to notifying that an alert started, need to notify what focus state the alert is in. if (FocusState::FOREGROUND == m_focusState) { - notifyObserver(alertToken, alertType, AlertObserverInterface::State::FOCUS_ENTERED_FOREGROUND); + infoCopy.state = AlertObserverInterface::State::FOCUS_ENTERED_FOREGROUND; + notifyObserver(infoCopy); } else { - notifyObserver(alertToken, alertType, AlertObserverInterface::State::FOCUS_ENTERED_BACKGROUND); + infoCopy.state = AlertObserverInterface::State::FOCUS_ENTERED_BACKGROUND; + notifyObserver(infoCopy); } } break; case State::STOPPED: - if (m_activeAlert && m_activeAlert->getToken() == alertToken) { - notifyObserver(alertToken, alertType, state, m_activeAlert->getScheduledTime_ISO_8601()); + if (m_activeAlert && m_activeAlert->getToken() == alertInfo.token) { + notifyObserver(alertInfo); eraseAlert(m_activeAlert); m_activeAlert.reset(); } else { - auto alert = getAlertLocked(alertToken); + auto alert = getAlertLocked(alertInfo.token); if (alert) { - notifyObserver(alertToken, alertType, state, alert->getScheduledTime_ISO_8601()); - ACSDK_DEBUG((LX("erasing a stopped Alert that is no longer active").d("alertToken", alertToken))); + notifyObserver(alertInfo); + ACSDK_DEBUG( + (LX("erasing a stopped Alert that is no longer active").d("alertToken", alertInfo.token))); eraseAlert(alert); } else { - notifyObserver(alertToken, alertType, state, ""); + notifyObserver(alertInfo); } } setTimerForNextAlertLocked(); @@ -577,7 +598,7 @@ void AlertScheduler::executeOnAlertStateChange( case State::COMPLETED: if (m_activeAlert) { - notifyObserver(alertToken, alertType, state, m_activeAlert->getScheduledTime_ISO_8601()); + notifyObserver(alertInfo); } eraseAlert(m_activeAlert); m_activeAlert.reset(); @@ -589,8 +610,7 @@ void AlertScheduler::executeOnAlertStateChange( m_alertStorage->modify(m_activeAlert); m_scheduledAlerts.insert(m_activeAlert); m_activeAlert.reset(); - - notifyObserver(alertToken, alertType, state, reason); + notifyObserver(alertInfo); setTimerForNextAlertLocked(); break; @@ -623,48 +643,38 @@ void AlertScheduler::executeOnAlertStateChange( case State::ERROR: // clear out the alert that had the error, to avoid degenerate repeated alert behavior. - if (m_activeAlert && m_activeAlert->getToken() == alertToken) { + if (m_activeAlert && m_activeAlert->getToken() == alertInfo.token) { eraseAlert(m_activeAlert); m_activeAlert.reset(); setTimerForNextAlertLocked(); } else { - auto alert = getAlertLocked(alertToken); + auto alert = getAlertLocked(alertInfo.token); if (alert) { ACSDK_DEBUG( - (LX("erasing Alert with an error that is no longer active").d("alertToken", alertToken))); + (LX("erasing Alert with an error that is no longer active").d("alertToken", alertInfo.token))); eraseAlert(alert); m_scheduledAlerts.erase(alert); setTimerForNextAlertLocked(); } } - notifyObserver(alertToken, alertType, state, reason); + notifyObserver(alertInfo); break; } } -void AlertScheduler::notifyObserver( - const std::string& alertToken, - const std::string& alertType, - AlertObserverInterface::State state, - const std::string& reason) { +void AlertScheduler::notifyObserver(const AlertObserverInterface::AlertInfo& alertInfo) { ACSDK_DEBUG5(LX("notifyObserver") - .d("alertToken", alertToken) - .d("alertType", alertType) - .d("state", state) - .d("reason", reason)); - m_executor.submit([this, alertToken, alertType, state, reason]() { - executeNotifyObserver(alertToken, alertType, state, reason); - }); + .d("alertToken", alertInfo.token) + .d("alertType", alertInfo.type) + .d("state", alertInfo.state) + .d("reason", alertInfo.reason)); + m_executor.submit([this, alertInfo]() { executeNotifyObserver(alertInfo); }); } -void AlertScheduler::executeNotifyObserver( - const std::string& alertToken, - const std::string& alertType, - AlertObserverInterface::State state, - const std::string& reason) { - m_observer->onAlertStateChange(alertToken, alertType, state, reason); +void AlertScheduler::executeNotifyObserver(const AlertObserverInterface::AlertInfo& alertInfo) { + m_observer->onAlertStateChange(alertInfo); } void AlertScheduler::deactivateActiveAlertHelperLocked(Alert::StopReason reason) { @@ -691,7 +701,7 @@ void AlertScheduler::setTimerForNextAlertLocked() { } if (m_scheduledAlerts.empty()) { - ACSDK_DEBUG8(LX("executeScheduleNextAlertForRendering").m("no work to do.")); + ACSDK_INFO(LX("executeScheduleNextAlertForRendering").m("no work to do.")); return; } @@ -709,29 +719,41 @@ void AlertScheduler::setTimerForNextAlertLocked() { if (secondsToWait < std::chrono::seconds::zero()) { secondsToWait = std::chrono::seconds::zero(); } - if (secondsToWait == std::chrono::seconds::zero()) { - auto token = alert->getToken(); - auto type = alert->getTypeName(); - notifyObserver(token, type, AlertObserverInterface::State::READY); + notifyObserver(AlertInfo( + alert->getToken(), + alert->getType(), + AlertObserverInterface::State::READY, + alert->getScheduledTime_Utc_TimePoint(), + alert->getOriginalTime(), + alert->getLabel())); } else { // start the timer for the next alert. if (!m_scheduledAlertTimer .start( secondsToWait, - std::bind(&AlertScheduler::onAlertReady, this, alert->getToken(), alert->getTypeName())) + std::bind( + &AlertScheduler::onAlertReady, + this, + AlertObserverInterface::AlertInfo( + alert->getToken(), + alert->getType(), + AlertObserverInterface::State::READY, + alert->getScheduledTime_Utc_TimePoint(), + alert->getOriginalTime(), + alert->getLabel()))) .valid()) { ACSDK_ERROR(LX("executeScheduleNextAlertForRenderingFailed").d("reason", "startTimerFailed")); } } } else { - ACSDK_DEBUG5(LX("executeScheduleNextAlertForRenderingSkipped").d("reason", "m_shouldScheduleAlerts is false.")); + ACSDK_INFO(LX("executeScheduleNextAlertForRenderingSkipped").d("reason", "m_shouldScheduleAlerts is false.")); } } -void AlertScheduler::onAlertReady(const std::string& alertToken, const std::string& alertType) { - ACSDK_DEBUG5(LX("onAlertReady").d("alertToken", alertToken).d("alertType", alertType)); - notifyObserver(alertToken, alertType, AlertObserverInterface::State::READY); +void AlertScheduler::onAlertReady(const AlertObserverInterface::AlertInfo& alertInfo) { + ACSDK_DEBUG5(LX("onAlertReady").d("alertToken", alertInfo.token).d("alertType", alertInfo.type)); + notifyObserver(alertInfo); } void AlertScheduler::activateNextAlertLocked() { @@ -803,7 +825,13 @@ void AlertScheduler::eraseAlert(std::shared_ptr alert) { ACSDK_ERROR(LX(__func__).m("Could not erase alert from database").d("token", alertToken)); return; } - notifyObserver(alertToken, alert->getTypeName(), AlertObserverInterface::State::DELETED); + notifyObserver(AlertInfo( + alert->getToken(), + alert->getType(), + AlertObserverInterface::State::DELETED, + alert->getScheduledTime_Utc_TimePoint(), + alert->getOriginalTime(), + alert->getLabel())); } } // namespace acsdkAlerts diff --git a/capabilities/Alerts/acsdkAlerts/src/AlertsCapabilityAgent.cpp b/capabilities/Alerts/acsdkAlerts/src/AlertsCapabilityAgent.cpp index e9f8fd4f3d..c1f0ff5ef8 100644 --- a/capabilities/Alerts/acsdkAlerts/src/AlertsCapabilityAgent.cpp +++ b/capabilities/Alerts/acsdkAlerts/src/AlertsCapabilityAgent.cpp @@ -553,19 +553,13 @@ void AlertsCapabilityAgent::onFocusChanged(const std::string& channelName, avsCo } } -void AlertsCapabilityAgent::onAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - AlertObserverInterface::State state, - const std::string& reason) { +void AlertsCapabilityAgent::onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { ACSDK_DEBUG9(LX("onAlertStateChange") - .d("alertToken", alertToken) - .d("alertType", alertType) - .d("state", state) - .d("reason", reason)); - m_executor.submit([this, alertToken, alertType, state, reason]() { - executeOnAlertStateChange(alertToken, alertType, state, reason); - }); + .d("alertToken", alertInfo.token) + .d("alertType", alertInfo.type) + .d("state", alertInfo.state) + .d("reason", alertInfo.reason)); + m_executor.submit([this, alertInfo]() { executeOnAlertStateChange(alertInfo); }); } void AlertsCapabilityAgent::addObserver(std::shared_ptr observer) { @@ -806,11 +800,13 @@ bool AlertsCapabilityAgent::handleSetAlert( } // Pass the scheduled time to the observers as the reason for the alert created - executeNotifyObservers( + executeNotifyObservers(AlertObserverInterface::AlertInfo( parsedAlert->getToken(), - parsedAlert->getTypeName(), + parsedAlert->getType(), State::SCHEDULED_FOR_LATER, - parsedAlert->getScheduledTime_ISO_8601()); + parsedAlert->getScheduledTime_Utc_TimePoint(), + parsedAlert->getOriginalTime(), + parsedAlert->getLabel())); submitMetric(m_metricRecorder, FAILED_SNOOZE_ALERT, 0); submitMetric(m_metricRecorder, "alarmSnoozeCount", 1); return true; @@ -822,11 +818,13 @@ bool AlertsCapabilityAgent::handleSetAlert( } submitMetric(m_metricRecorder, FAILED_SCHEDULE_ALERT, 0); - executeNotifyObservers( + executeNotifyObservers(AlertObserverInterface::AlertInfo( parsedAlert->getToken(), - parsedAlert->getTypeName(), + parsedAlert->getType(), State::SCHEDULED_FOR_LATER, - parsedAlert->getScheduledTime_ISO_8601()); + parsedAlert->getScheduledTime_Utc_TimePoint(), + parsedAlert->getOriginalTime(), + parsedAlert->getLabel())); updateContextManager(); @@ -1160,23 +1158,20 @@ void AlertsCapabilityAgent::executeOnConnectionStatusChanged(const Status status } } -void AlertsCapabilityAgent::executeOnAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - AlertObserverInterface::State state, - const std::string& reason) { - ACSDK_DEBUG1(LX("executeOnAlertStateChange").d("alertToken", alertToken).d("state", state).d("reason", reason)); +void AlertsCapabilityAgent::executeOnAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { + ACSDK_INFO(LX("executeOnAlertStateChange").d("state", alertInfo.state).d("reason", alertInfo.reason)); + ACSDK_DEBUG1(LX("executeOnAlertStateChange").d("alertToken", alertInfo.token)); bool alertIsActive = false; int alertVolume; - switch (state) { + switch (alertInfo.state) { case AlertObserverInterface::State::READY: acquireChannel(); break; case AlertObserverInterface::State::STARTED: - sendEvent(ALERT_STARTED_EVENT_NAME, alertToken, true, reason, currentISO8601TimeUTC()); + sendEvent(ALERT_STARTED_EVENT_NAME, alertInfo.token, true, alertInfo.reason, currentISO8601TimeUTC()); alertVolume = getAlertVolume(); if ((alertVolume != -1) && (alertVolume < ALERT_VOLUME_METRIC_LIMIT)) { submitMetric(m_metricRecorder, ALERT_RINGING_LESS_THAN_30_PERCENT_MAX_VOLUME, 1); @@ -1184,7 +1179,7 @@ void AlertsCapabilityAgent::executeOnAlertStateChange( submitMetric(m_metricRecorder, ALERT_RINGING_ZERO_VOLUME, 1); } } - submitAlertStartedMetricWithMetadata(alertToken, alertType); + submitAlertStartedMetricWithMetadata(alertInfo.token, AlertObserverInterface::typeToString(alertInfo.type)); updateContextManager(); alertIsActive = true; break; @@ -1195,13 +1190,13 @@ void AlertsCapabilityAgent::executeOnAlertStateChange( break; case AlertObserverInterface::State::STOPPED: - sendEvent(ALERT_STOPPED_EVENT_NAME, alertToken, true, reason, currentISO8601TimeUTC()); + sendEvent(ALERT_STOPPED_EVENT_NAME, alertInfo.token, true, alertInfo.reason, currentISO8601TimeUTC()); releaseChannel(); updateContextManager(); break; case AlertObserverInterface::State::COMPLETED: - sendEvent(ALERT_STOPPED_EVENT_NAME, alertToken, true, reason, currentISO8601TimeUTC()); + sendEvent(ALERT_STOPPED_EVENT_NAME, alertInfo.token, true, alertInfo.reason, currentISO8601TimeUTC()); releaseChannel(); updateContextManager(); break; @@ -1212,18 +1207,19 @@ void AlertsCapabilityAgent::executeOnAlertStateChange( break; case AlertObserverInterface::State::PAST_DUE: - sendEvent(ALERT_STOPPED_EVENT_NAME, alertToken, true, reason, currentISO8601TimeUTC()); - submitAlertCanceledMetricWithMetadata(alertToken, alertType, reason); + sendEvent(ALERT_STOPPED_EVENT_NAME, alertInfo.token, true, alertInfo.reason, currentISO8601TimeUTC()); + submitAlertCanceledMetricWithMetadata( + alertInfo.token, AlertObserverInterface::typeToString(alertInfo.type), alertInfo.reason); break; case AlertObserverInterface::State::FOCUS_ENTERED_FOREGROUND: alertIsActive = true; - sendEvent(ALERT_ENTERED_FOREGROUND_EVENT_NAME, alertToken); + sendEvent(ALERT_ENTERED_FOREGROUND_EVENT_NAME, alertInfo.token); break; case AlertObserverInterface::State::FOCUS_ENTERED_BACKGROUND: alertIsActive = true; - sendEvent(ALERT_ENTERED_BACKGROUND_EVENT_NAME, alertToken); + sendEvent(ALERT_ENTERED_BACKGROUND_EVENT_NAME, alertInfo.token); break; case AlertObserverInterface::State::SCHEDULED_FOR_LATER: case AlertObserverInterface::State::DELETED: @@ -1263,9 +1259,7 @@ void AlertsCapabilityAgent::executeOnAlertStateChange( } } - m_executor.submit([this, alertToken, alertType, state, reason]() { - executeNotifyObservers(alertToken, alertType, state, reason); - }); + m_executor.submit([this, alertInfo]() { executeNotifyObservers(alertInfo); }); } void AlertsCapabilityAgent::executeAddObserver(std::shared_ptr observer) { @@ -1278,18 +1272,14 @@ void AlertsCapabilityAgent::executeRemoveObserver(std::shared_ptronAlertStateChange(alertToken, alertType, state, reason); + observer->onAlertStateChange(alertInfo); } } diff --git a/capabilities/Alerts/acsdkAlerts/src/CMakeLists.txt b/capabilities/Alerts/acsdkAlerts/src/CMakeLists.txt index 8980050742..1a85f0e61d 100644 --- a/capabilities/Alerts/acsdkAlerts/src/CMakeLists.txt +++ b/capabilities/Alerts/acsdkAlerts/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=alerts") -add_library(acsdkAlerts SHARED +add_library(acsdkAlerts Renderer/Renderer.cpp Storage/SQLiteAlertStorage.cpp Alarm.cpp diff --git a/capabilities/Alerts/acsdkAlerts/src/Renderer/Renderer.cpp b/capabilities/Alerts/acsdkAlerts/src/Renderer/Renderer.cpp index 7bb43245e8..753fab71aa 100644 --- a/capabilities/Alerts/acsdkAlerts/src/Renderer/Renderer.cpp +++ b/capabilities/Alerts/acsdkAlerts/src/Renderer/Renderer.cpp @@ -89,7 +89,8 @@ std::shared_ptr Renderer::createAlertRenderer( const std::shared_ptr& audioPipelineFactory, const std::shared_ptr& metricRecorder, - const std::shared_ptr& shutdownNotifier) { + const std::shared_ptr& shutdownNotifier, + const std::shared_ptr& internetConnectionMonitor) { if (!audioPipelineFactory) { ACSDK_ERROR(LX("createFailed").m("audioPipelineFactory parameter was nullptr.")); return nullptr; @@ -113,23 +114,29 @@ std::shared_ptr Renderer::createAlertRenderer( } auto mediaPlayer = applicationMediaInterfaces->mediaPlayer; - auto renderer = std::shared_ptr(new Renderer{mediaPlayer, metricRecorder}); + auto renderer = std::shared_ptr(new Renderer{mediaPlayer, metricRecorder, internetConnectionMonitor}); mediaPlayer->addObserver(renderer); shutdownNotifier->addObserver(renderer); + if (internetConnectionMonitor) { + internetConnectionMonitor->addInternetConnectionObserver(renderer); + } + return renderer; } std::shared_ptr Renderer::create( std::shared_ptr mediaPlayer, - std::shared_ptr metricRecorder) { + std::shared_ptr metricRecorder, + std::shared_ptr internetConnectionMonitor) { if (!mediaPlayer) { ACSDK_ERROR(LX("createFailed").m("mediaPlayer parameter was nullptr.")); return nullptr; } - auto renderer = std::shared_ptr(new Renderer{mediaPlayer, metricRecorder}); + auto renderer = std::shared_ptr(new Renderer{mediaPlayer, metricRecorder, internetConnectionMonitor}); mediaPlayer->addObserver(renderer); + return renderer; } @@ -139,6 +146,10 @@ void Renderer::doShutdown() { if (m_mediaPlayer) { m_mediaPlayer->removeObserver(shared_from_this()); } + + if (m_internetConnectionMonitor) { + m_internetConnectionMonitor->removeInternetConnectionObserver(shared_from_this()); + } m_executor.shutdown(); } @@ -208,7 +219,8 @@ void Renderer::onPlaybackError( Renderer::Renderer( std::shared_ptr mediaPlayer, - std::shared_ptr metricRecorder) : + std::shared_ptr metricRecorder, + std::shared_ptr internetConnectionMonitor) : RequiresShutdown{"Renderer"}, m_mediaPlayer{mediaPlayer}, m_metricRecorder{metricRecorder}, @@ -220,7 +232,9 @@ Renderer::Renderer( m_shouldPauseBeforeRender{false}, m_isStopping{false}, m_isStartPending{false}, - m_volumeRampEnabled{false} { + m_volumeRampEnabled{false}, + m_isNetworkConnected{false}, + m_internetConnectionMonitor{internetConnectionMonitor} { resetSourceId(); } @@ -316,7 +330,8 @@ void Renderer::play() { m_isStartPending = false; - if (shouldPlayDefault()) { + if (shouldPlayDefault() || !m_isNetworkConnected) { + ACSDK_INFO(LX(__func__).d("m_isNetworkConnected", m_isNetworkConnected)); std::shared_ptr stream; avsCommon::utils::MediaType streamFormat = avsCommon::utils::MediaType::UNKNOWN; std::tie(stream, streamFormat) = m_defaultAudioFactory(); @@ -400,10 +415,12 @@ void Renderer::executeStop() { m_isStartPending = false; if (MediaPlayerInterface::ERROR == m_currentSourceId) { - ACSDK_DEBUG5(LX(__func__).m("Nothing to stop, no media playing.")); + ACSDK_ERROR(LX(__func__).m("Nothing to stop, no media playing.")); { std::lock_guard lock(m_waitMutex); m_isStopping = false; + // The alert thinks audio is still rendering, notify them nothing is happening. + notifyObserver(RendererObserverInterface::State::STOPPED); m_observer = nullptr; } return; @@ -576,6 +593,11 @@ void Renderer::handlePlaybackError(const std::string& error) { m_observer = nullptr; } +void Renderer::onConnectionStatusChanged(bool connected) { + ACSDK_DEBUG5(LX(__func__).d("Network connected", connected)); + m_isNetworkConnected = connected; +} + } // namespace renderer } // namespace acsdkAlerts } // namespace alexaClientSDK diff --git a/capabilities/Alerts/acsdkAlerts/src/Storage/SQLiteAlertStorage.cpp b/capabilities/Alerts/acsdkAlerts/src/Storage/SQLiteAlertStorage.cpp index f815ca80c7..8e2e427e15 100644 --- a/capabilities/Alerts/acsdkAlerts/src/Storage/SQLiteAlertStorage.cpp +++ b/capabilities/Alerts/acsdkAlerts/src/Storage/SQLiteAlertStorage.cpp @@ -23,6 +23,8 @@ #include #include +#include +#include #include #include @@ -100,30 +102,57 @@ static const int ALERT_STATE_READY = 10; /// The name of the 'id' field we will use as the primary key in our tables. static const std::string DATABASE_COLUMN_ID_NAME = "id"; +/// The name of the 'token' field used in alerts table. +static const std::string DATABASE_COLUMN_TOKEN_NAME = "token"; +/// The name of the 'type' field used in alerts table. +static const std::string DATABASE_COLUMN_TYPE_NAME = "type"; +/// The name of the 'state' field used in alerts table. +static const std::string DATABASE_COLUMN_STATE_NAME = "state"; +/// The name of the 'scheduled_time_unix' field used in alerts table. +static const std::string DATABASE_COLUMN_SCHEDULED_TIME_UNIX_NAME = "scheduled_time_unix"; +/// The name of the 'scheduled_time_iso_8601' field used in alerts table. +static const std::string DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME = "scheduled_time_iso_8601"; +/// The name of the 'asset_loop_count' field used in alerts table. +static const std::string DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME = "asset_loop_count"; +/// The name of the 'asset_loop_pause_milliseconds' field used in alerts table. +static const std::string DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME = "asset_loop_pause_milliseconds"; +/// The name of the 'background_asset' field used in alerts table. +static const std::string DATABASE_COLUMN_BACKGROUND_ASSET_NAME = "background_asset"; +/// The name of the 'original_time' field used in alerts table. +static const std::string DATABASE_COLUMN_ORIGINAL_TIME_NAME = "original_time"; +/// The name of the 'label' field used in alerts table. +static const std::string DATABASE_COLUMN_LABEL_NAME = "label"; +/// The name of the 'created_time_iso_8601' field used in alerts table. +static const std::string DATABASE_COLUMN_CREATED_TIME_NAME = "created_time_iso_8601"; -/// A symbolic name for version one of our database. -static const int ALERTS_DATABASE_VERSION_ONE = 1; /// A symbolic name for version two of our database. static const int ALERTS_DATABASE_VERSION_TWO = 2; -/// The name of the alerts (v1) table. -static const std::string ALERTS_TABLE_NAME = "alerts"; +/// A symbolic name for version three of our database. +static const int ALERTS_DATABASE_VERSION_THREE = 3; /// The name of the alerts (v2) table. static const std::string ALERTS_V2_TABLE_NAME = "alerts_v2"; + +/// The name of the alerts (v3) table. +static const std::string ALERTS_V3_TABLE_NAME = "alerts_v3"; + /// The SQL string to create the alerts table. // clang-format off static const std::string CREATE_ALERTS_TABLE_SQL_STRING = std::string("CREATE TABLE ") + - ALERTS_V2_TABLE_NAME + " (" + + ALERTS_V3_TABLE_NAME + " (" + DATABASE_COLUMN_ID_NAME + " INT PRIMARY KEY NOT NULL," + - "token TEXT NOT NULL," + - "type INT NOT NULL," + - "state INT NOT NULL," + - "scheduled_time_unix INT NOT NULL," + - "scheduled_time_iso_8601 TEXT NOT NULL," + - "asset_loop_count INT NOT NULL," + - "asset_loop_pause_milliseconds INT NOT NULL," + - "background_asset TEXT NOT NULL);"; + DATABASE_COLUMN_TOKEN_NAME + " TEXT NOT NULL," + + DATABASE_COLUMN_TYPE_NAME + " INT NOT NULL," + + DATABASE_COLUMN_STATE_NAME + " INT NOT NULL," + + DATABASE_COLUMN_SCHEDULED_TIME_UNIX_NAME + " INT NOT NULL," + + DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME + " TEXT NOT NULL," + + DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME + " INT NOT NULL," + + DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME + " INT NOT NULL," + + DATABASE_COLUMN_BACKGROUND_ASSET_NAME + " TEXT NOT NULL," + + DATABASE_COLUMN_ORIGINAL_TIME_NAME + " TEXT NOT NULL," + + DATABASE_COLUMN_LABEL_NAME + " TEXT NOT NULL," + + DATABASE_COLUMN_CREATED_TIME_NAME + " TEXT NOT NULL);"; // clang-format on /// The name of the alertAssets table. @@ -138,7 +167,7 @@ static const std::string CREATE_ALERT_ASSETS_TABLE_SQL_STRING = std::string("CRE "url TEXT NOT NULL);"; // clang-format on -/// The name of the offline alerts table. +/// The name of the offline alerts (v1) table. static const std::string OFFLINE_ALERTS_TABLE_NAME = "offlineAlerts"; /// The name of the offline alerts (v2) table. @@ -151,9 +180,12 @@ static const int OFFLINE_ALERTS_DATABASE_VERSION_TWO = 2; /// The SQL string to create the offline alerts table. // clang-format off -static const std::string CREATE_OFFLINE_ALERTS_TABLE_SQL_STRING = - std::string("CREATE TABLE ") + OFFLINE_ALERTS_V2_TABLE_NAME + " (" + "id INT PRIMARY KEY NOT NULL," + - "token TEXT NOT NULL," + "scheduled_time_iso_8601 TEXT NOT NULL," + "event_time_iso_8601 TEXT NOT NULL);"; +static const std::string CREATE_OFFLINE_ALERTS_TABLE_SQL_STRING = std::string("CREATE TABLE ") + + OFFLINE_ALERTS_V2_TABLE_NAME + " (" + + "id INT PRIMARY KEY NOT NULL," + + "token TEXT NOT NULL," + + "scheduled_time_iso_8601 TEXT NOT NULL," + + "event_time_iso_8601 TEXT NOT NULL);"; // clang-format on /// The name of the alertAssetPlayOrderItems table. @@ -168,6 +200,22 @@ static const std::string CREATE_ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_SQL_STRING = "asset_play_order_token TEXT NOT NULL);"; // clang-format on +/// The prefix for alert metrics. +static const std::string ALERT_METRIC_PREFIX = "ALERT-"; + +/// The event names for metrics. +static const std::string ALERT_DATABASE_OPEN_FAILED = "databaseOpenFailed"; +static const std::string OFFLINE_ALERTS_V1ToV2_MIGRATION_FAILED = "offlineAlertsV1ToV2MigrationFailed"; +static const std::string ALERTS_V2ToV3_MIGRATION_FAILED = "alertsV2ToV3MigrationFailed"; +static const std::string CREATE_OFFLINE_ALERTS_V2_FAILED = "createOfflineAlertsV2Failed"; +static const std::string CREATE_ALERTS_V3_FAILED = "createAlertsV3Failed"; +static const std::string CREATE_DATABASE_FAILED = "createDatabaseFailed"; + +/// The table with entries for retry times in milliseconds. +static const std::vector RETRY_TABLE = {10, 20, 40}; +/// The maximum retry times. +static const size_t RETRY_TIME_MAXIMUM = RETRY_TABLE.size(); + struct AssetOrderItem { int index; std::string name; @@ -179,6 +227,34 @@ struct AssetOrderItemCompare { } }; +/** + * Submits a metric for a given event name. + * + * @param metricRecorder The @c MetricRecorderInterface used to record metrics. + * @param eventName The name of the metric event. + * @param count The number of data point to be added. + */ +static void submitMetric( + const std::shared_ptr metricRecorder, + const std::string& eventName, + const int count) { + if (nullptr == metricRecorder) { + return; + } + + auto metricEvent = + avsCommon::utils::metrics::MetricEventBuilder{} + .setActivityName(ALERT_METRIC_PREFIX + eventName) + .addDataPoint( + avsCommon::utils::metrics::DataPointCounterBuilder{}.setName(eventName).increment(count).build()) + .build(); + if (nullptr == metricEvent) { + ACSDK_ERROR(LX("submitMetricFailed").d("reason", "metricEventNull")); + return; + } + recordMetric(metricRecorder, metricEvent); +} + /** * Utility function to convert an alert type string into a value we can store in the database. * @@ -296,7 +372,8 @@ static bool dbFieldToAlertState(int dbState, Alert::State* state) { std::shared_ptr SQLiteAlertStorage::createAlertStorageInterface( const std::shared_ptr& configurationRoot, - const std::shared_ptr& audioFactory) { + const std::shared_ptr& audioFactory, + std::shared_ptr metricRecorder) { if (!configurationRoot || !audioFactory) { ACSDK_ERROR(LX("createAlertStorageInterfaceFailed") .d("isConfigurationRootNull", !configurationRoot) @@ -306,13 +383,14 @@ std::shared_ptr SQLiteAlertStorage::createAlertStorageInt auto alertsAudioFactory = audioFactory->alerts(); - auto storage = create(*configurationRoot, alertsAudioFactory); + auto storage = create(*configurationRoot, alertsAudioFactory, metricRecorder); return std::move(storage); } std::unique_ptr SQLiteAlertStorage::create( const avsCommon::utils::configuration::ConfigurationNode& configurationRoot, - const std::shared_ptr& alertsAudioFactory) { + const std::shared_ptr& alertsAudioFactory, + std::shared_ptr metricRecorder) { auto alertsConfigurationRoot = configurationRoot[ALERTS_CAPABILITY_AGENT_CONFIGURATION_ROOT_KEY]; if (!alertsConfigurationRoot) { ACSDK_ERROR(LX("createFailed") @@ -330,14 +408,23 @@ std::unique_ptr SQLiteAlertStorage::create( return nullptr; } - return std::unique_ptr(new SQLiteAlertStorage(alertDbFilePath, alertsAudioFactory)); + if (!alertsAudioFactory) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullAlertsAudioFactory")); + return nullptr; + } + + return std::unique_ptr( + new SQLiteAlertStorage(alertDbFilePath, alertsAudioFactory, metricRecorder)); } SQLiteAlertStorage::SQLiteAlertStorage( const std::string& dbFilePath, - const std::shared_ptr& alertsAudioFactory) : + const std::shared_ptr& alertsAudioFactory, + std::shared_ptr metricRecorder) : m_alertsAudioFactory{alertsAudioFactory}, - m_db{dbFilePath} { + m_db{dbFilePath}, + m_metricRecorder{metricRecorder}, + m_retryTimer{RETRY_TABLE} { } SQLiteAlertStorage::~SQLiteAlertStorage() { @@ -427,33 +514,43 @@ static bool createAlertAssetPlayOrderItemsTable(SQLiteDatabase* db) { bool SQLiteAlertStorage::createDatabase() { if (!m_db.initialize()) { ACSDK_ERROR(LX("createDatabaseFailed")); + submitMetric(m_metricRecorder, CREATE_DATABASE_FAILED, 1); return false; } if (!createAlertsTable(&m_db)) { ACSDK_ERROR(LX("createDatabaseFailed").m("Alerts table could not be created.")); close(); + submitMetric(m_metricRecorder, CREATE_ALERTS_V3_FAILED, 1); + submitMetric(m_metricRecorder, CREATE_DATABASE_FAILED, 1); return false; } + submitMetric(m_metricRecorder, CREATE_ALERTS_V3_FAILED, 0); if (!createAlertAssetsTable(&m_db)) { ACSDK_ERROR(LX("createDatabaseFailed").m("AlertAssets table could not be created.")); close(); + submitMetric(m_metricRecorder, CREATE_DATABASE_FAILED, 1); return false; } if (!createOfflineAlertsTable(&m_db)) { ACSDK_ERROR(LX("createDatabaseFailed").m("OfflineAlerts table could not be created.")); close(); + submitMetric(m_metricRecorder, CREATE_OFFLINE_ALERTS_V2_FAILED, 1); + submitMetric(m_metricRecorder, CREATE_DATABASE_FAILED, 1); return false; } + submitMetric(m_metricRecorder, CREATE_OFFLINE_ALERTS_V2_FAILED, 0); if (!createAlertAssetPlayOrderItemsTable(&m_db)) { ACSDK_ERROR(LX("createDatabaseFailed").m("AlertAssetPlayOrderItems table could not be created.")); close(); + submitMetric(m_metricRecorder, CREATE_DATABASE_FAILED, 1); return false; } + submitMetric(m_metricRecorder, CREATE_DATABASE_FAILED, 0); return true; } @@ -462,23 +559,26 @@ bool SQLiteAlertStorage::open() { return false; } /// Check if any tables are missing, if they are, add them. - if (!m_db.tableExists(ALERTS_V2_TABLE_NAME)) { - if (!createAlertsTable(&m_db)) { - ACSDK_ERROR(LX("openFailed").m("Alert table could not be created.")); - close(); - return false; - } + /// Alerts table will be created during migration if it does not exist yet. + if (!migrateAlertsDbFromV2ToV3()) { + ACSDK_ERROR(LX("openFailed").m("migrateAlertsDbFromV2ToV3 failed.")); + close(); + submitMetric(m_metricRecorder, ALERT_DATABASE_OPEN_FAILED, 1); + return false; } + if (!m_db.tableExists(ALERT_ASSETS_TABLE_NAME)) { if (!createAlertAssetsTable(&m_db)) { ACSDK_ERROR(LX("openFailed").m("AlertAssets table could not be created.")); close(); + submitMetric(m_metricRecorder, ALERT_DATABASE_OPEN_FAILED, 1); return false; } } if (!m_db.tableExists(ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_NAME)) { if (!createAlertAssetPlayOrderItemsTable(&m_db)) { ACSDK_ERROR(LX("openFailed").m("AlertAssetPlayOrderItems table could not be created.")); + submitMetric(m_metricRecorder, ALERT_DATABASE_OPEN_FAILED, 1); close(); return false; } @@ -488,72 +588,245 @@ bool SQLiteAlertStorage::open() { if (!migrateOfflineAlertsDbFromV1ToV2()) { ACSDK_ERROR(LX("openFailed").m("migrateOfflineAlertsDbFromV1ToV2 failed.")); close(); + submitMetric(m_metricRecorder, ALERT_DATABASE_OPEN_FAILED, 1); return false; } + submitMetric(m_metricRecorder, ALERT_DATABASE_OPEN_FAILED, 0); return true; } bool SQLiteAlertStorage::migrateOfflineAlertsDbFromV1ToV2() { /// Offline alerts table is up-to-date, no need to migrate. if (m_db.tableExists(OFFLINE_ALERTS_V2_TABLE_NAME)) { - ACSDK_DEBUG8(LX("migrateOfflineAlertsDbFromV1ToV2").m("Offline alerts v2 table already exists.")); + ACSDK_DEBUG5(LX("migrateOfflineAlertsDbFromV1ToV2").m("Offline alerts v2 table already exists.")); return true; } if (!createOfflineAlertsTable(&m_db)) { ACSDK_ERROR(LX("migrateOfflineAlertsDbFromV1ToV2Failed").m("Offline alerts v2 table could not be created.")); + submitMetric(m_metricRecorder, CREATE_OFFLINE_ALERTS_V2_FAILED, 1); return false; } + submitMetric(m_metricRecorder, CREATE_OFFLINE_ALERTS_V2_FAILED, 0); /// Offline alerts v1 table does not exist, nothing to be migrated. if (!m_db.tableExists(OFFLINE_ALERTS_TABLE_NAME)) { - ACSDK_DEBUG8(LX("migrateOfflineAlertsDbFromV1ToV2") + ACSDK_DEBUG5(LX("migrateOfflineAlertsDbFromV1ToV2") .m("Offline alerts v1 table does not exist, nothing to be migrated.")); + submitMetric(m_metricRecorder, OFFLINE_ALERTS_V1ToV2_MIGRATION_FAILED, 0); return true; } + bool success = retryDataMigration([this]() -> bool { + rapidjson::Document offlineAlerts(rapidjson::kObjectType); + auto& allocator = offlineAlerts.GetAllocator(); + rapidjson::Value alertContainer(rapidjson::kArrayType); + loadOfflineAlertsHelper(OFFLINE_ALERTS_DATABASE_VERSION_ONE, &alertContainer, allocator); + bool isLegacyV1 = isOfflineTableV1Legacy(); + + for (const auto& alert : alertContainer.GetArray()) { + std::string token = ""; + if (!avsCommon::utils::json::jsonUtils::retrieveValue(alert, OFFLINE_STOPPED_ALERT_TOKEN_KEY, &token)) { + ACSDK_ERROR(LX("migrateOfflineAlertsDbFromV1ToV2Failed") + .m("Could not retrieve" + OFFLINE_STOPPED_ALERT_TOKEN_KEY)); + return false; + } + std::string scheduledTime_ISO_8601 = ""; + if (!avsCommon::utils::json::jsonUtils::retrieveValue( + alert, OFFLINE_STOPPED_ALERT_SCHEDULED_TIME_KEY, &scheduledTime_ISO_8601)) { + ACSDK_ERROR(LX("migrateOfflineAlertsDbFromV1ToV2Failed") + .m("Could not retrieve" + OFFLINE_STOPPED_ALERT_SCHEDULED_TIME_KEY)); + return false; + } + std::string event_time_iso_8601 = ""; + if (!isLegacyV1 && !avsCommon::utils::json::jsonUtils::retrieveValue( + alert, OFFLINE_STOPPED_ALERT_EVENT_TIME_KEY, &event_time_iso_8601)) { + ACSDK_ERROR(LX("migrateOfflineAlertsDbFromV1ToV2Failed") + .m("Could not retrieve" + OFFLINE_STOPPED_ALERT_EVENT_TIME_KEY)); + return false; + } + if (offlineAlertExists(OFFLINE_ALERTS_DATABASE_VERSION_TWO, token)) { + /// the offline alert may be stored successfully before retry. + ACSDK_DEBUG7(LX("migrateOfflineAlertsDbFromV1ToV2").m("Offline alerts already exists")); + continue; + } + if (!storeOfflineAlertHelper( + OFFLINE_ALERTS_DATABASE_VERSION_TWO, token, scheduledTime_ISO_8601, event_time_iso_8601)) { + ACSDK_ERROR(LX("migrateOfflineAlertsDbFromV1ToV2Failed").m("Failed to store offline alert to V2.")); + return false; + } + } + return true; + }); - rapidjson::Document offlineAlerts(rapidjson::kObjectType); - auto& allocator = offlineAlerts.GetAllocator(); - rapidjson::Value alertContainer(rapidjson::kArrayType); - loadOfflineAlertsHelper(OFFLINE_ALERTS_DATABASE_VERSION_ONE, &alertContainer, allocator); - bool isLegacyV1 = isOfflineTableV1Legacy(); + if (success) { + ACSDK_DEBUG8(LX("migrateOfflineAlertsDbFromV1ToV2Succeeded")); + submitMetric(m_metricRecorder, OFFLINE_ALERTS_V1ToV2_MIGRATION_FAILED, 0); + } else { + ACSDK_ERROR(LX("migrateOfflineAlertsDbFromV1ToV2Failed")); + submitMetric(m_metricRecorder, OFFLINE_ALERTS_V1ToV2_MIGRATION_FAILED, 1); + } - for (const auto& alert : alertContainer.GetArray()) { - std::string token = ""; - if (!avsCommon::utils::json::jsonUtils::retrieveValue(alert, OFFLINE_STOPPED_ALERT_TOKEN_KEY, &token)) { - ACSDK_ERROR( - LX("migrateOfflineAlertsDbFromV1ToV2Failed").m("Could not retrieve" + OFFLINE_STOPPED_ALERT_TOKEN_KEY)); + return success; +} + +bool SQLiteAlertStorage::migrateAlertsDbFromV2ToV3() { + /// Alerts table is up-to-date, no need to migrate. + if (m_db.tableExists(ALERTS_V3_TABLE_NAME)) { + ACSDK_DEBUG5(LX("migrateAlertsDbFromV2ToV3").m("Alerts v3 table already exists.")); + return true; + } + + if (!createAlertsTable(&m_db)) { + ACSDK_ERROR(LX("migrateAlertsDbFromV2toV3Failed").m("Alerts v3 table could not be created.")); + submitMetric(m_metricRecorder, CREATE_ALERTS_V3_FAILED, 1); + return false; + } + submitMetric(m_metricRecorder, CREATE_ALERTS_V3_FAILED, 0); + + /// Alerts v2 table does not exist, nothing to be migrated. + if (!m_db.tableExists(ALERTS_V2_TABLE_NAME)) { + submitMetric(m_metricRecorder, ALERTS_V2ToV3_MIGRATION_FAILED, 0); + ACSDK_DEBUG5(LX("migrateAlertsDbFromV2ToV3").m("Alerts v2 table does not exist, nothing to be migrated.")); + return true; + } + + bool success = retryDataMigration([this]() -> bool { + const std::string loadSqlString = "SELECT * FROM " + ALERTS_V2_TABLE_NAME + ";"; + auto loadStatement = m_db.createStatement(loadSqlString); + if (!loadStatement) { + ACSDK_ERROR(LX("migrateAlertsDbFromV2toV3Failed").m("Could not create loadStatement.")); return false; } - std::string scheduledTime_ISO_8601 = ""; - if (!avsCommon::utils::json::jsonUtils::retrieveValue( - alert, OFFLINE_STOPPED_ALERT_SCHEDULED_TIME_KEY, &scheduledTime_ISO_8601)) { - ACSDK_ERROR(LX("migrateOfflineAlertsDbFromV1ToV2Failed") - .m("Could not retrieve" + OFFLINE_STOPPED_ALERT_SCHEDULED_TIME_KEY)); + + if (!loadStatement->step()) { + ACSDK_ERROR(LX("migrateAlertsDbFromV2toV3Failed").m("Could not perform step.")); return false; } - std::string event_time_iso_8601 = ""; - if (!isLegacyV1 && !avsCommon::utils::json::jsonUtils::retrieveValue( - alert, OFFLINE_STOPPED_ALERT_EVENT_TIME_KEY, &event_time_iso_8601)) { - ACSDK_ERROR(LX("migrateOfflineAlertsDbFromV1ToV2Failed") - .m("Could not retrieve" + OFFLINE_STOPPED_ALERT_EVENT_TIME_KEY)); - return false; + + while (SQLITE_ROW == loadStatement->getStepResult()) { + int numberColumns = loadStatement->getColumnCount(); + int id = 0; + std::string token = ""; + int type = 0; + int state = 0; + int64_t scheduledTime_Unix = 0; + std::string scheduledTime_ISO_8601 = ""; + int loopCount = 0; + int loopPauseInMilliseconds = 0; + std::string backgroundAssetId = ""; + + for (int i = 0; i < numberColumns; i++) { + std::string columnName = loadStatement->getColumnName(i); + + if (DATABASE_COLUMN_ID_NAME == columnName) { + id = loadStatement->getColumnInt(i); + } else if (DATABASE_COLUMN_TOKEN_NAME == columnName) { + token = loadStatement->getColumnText(i); + } else if (DATABASE_COLUMN_TYPE_NAME == columnName) { + type = loadStatement->getColumnInt(i); + } else if (DATABASE_COLUMN_STATE_NAME == columnName) { + state = loadStatement->getColumnInt(i); + } else if (DATABASE_COLUMN_SCHEDULED_TIME_UNIX_NAME == columnName) { + scheduledTime_Unix = loadStatement->getColumnInt64(i); + } else if (DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME == columnName) { + scheduledTime_ISO_8601 = loadStatement->getColumnText(i); + } else if (DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME == columnName) { + loopCount = loadStatement->getColumnInt(i); + } else if (DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME == columnName) { + loopPauseInMilliseconds = loadStatement->getColumnInt(i); + } else if (DATABASE_COLUMN_BACKGROUND_ASSET_NAME == columnName) { + backgroundAssetId = loadStatement->getColumnText(i); + } + } + + if (alertExists(ALERTS_DATABASE_VERSION_THREE, token)) { + loadStatement->step(); + continue; + } + + // clang-format off + const std::string storeSqlString = "INSERT INTO " + ALERTS_V3_TABLE_NAME + " (" + + DATABASE_COLUMN_ID_NAME + ", " + DATABASE_COLUMN_TOKEN_NAME + ", " + + DATABASE_COLUMN_TYPE_NAME + ", " + DATABASE_COLUMN_STATE_NAME + ", " + + DATABASE_COLUMN_SCHEDULED_TIME_UNIX_NAME + ", " + + DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME + ", " + + DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME + ", " + + DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME + ", " + + DATABASE_COLUMN_BACKGROUND_ASSET_NAME + ", " + + DATABASE_COLUMN_ORIGINAL_TIME_NAME + ", " + DATABASE_COLUMN_LABEL_NAME + ", " + + DATABASE_COLUMN_CREATED_TIME_NAME + + ") VALUES (" + + "?, ?, " + /// DATABASE_COLUMN_ID_NAME, DATABASE_COLUMN_TOKEN_NAME + "?, ?, " + /// DATABASE_COLUMN_TYPE_NAME, DATABASE_COLUMN_STATE_NAME + "?, " + /// DATABASE_COLUMN_SCHEDULED_TIME_UNIX_NAME + "?, " + /// DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME + "?, " + /// DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME + "?, " + /// DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME + "?, " + /// DATABASE_COLUMN_BACKGROUND_ASSET_NAME + "?, ?, " + /// DATABASE_COLUMN_ORIGINAL_TIME_NAME, DATABASE_COLUMN_LABEL_NAME + "?" + /// DATABASE_COLUMN_CREATED_TIME_NAME + ");"; + // clang-format on + auto storeStatement = m_db.createStatement(storeSqlString); + + if (!storeStatement) { + ACSDK_ERROR(LX("migrateAlertsDbFromV2toV3Failed").m("Could not create storeStatement.")); + return false; + } + + int boundParam = 1; + if (!storeStatement->bindIntParameter(boundParam++, id) || + !storeStatement->bindStringParameter(boundParam++, token) || + !storeStatement->bindIntParameter(boundParam++, type) || + !storeStatement->bindIntParameter(boundParam++, state) || + !storeStatement->bindInt64Parameter(boundParam++, scheduledTime_Unix) || + !storeStatement->bindStringParameter(boundParam++, scheduledTime_ISO_8601) || + !storeStatement->bindIntParameter(boundParam++, loopCount) || + !storeStatement->bindIntParameter(boundParam++, loopPauseInMilliseconds) || + !storeStatement->bindStringParameter(boundParam++, backgroundAssetId) || + !storeStatement->bindStringParameter(boundParam++, "") || + !storeStatement->bindStringParameter(boundParam++, "") || + !storeStatement->bindStringParameter(boundParam, "")) { + ACSDK_ERROR(LX("migrateAlertsDbFromV2toV3Failed").m("Bind parameter failed in storeStatement.")); + return false; + } + + if (!storeStatement->step()) { + ACSDK_ERROR(LX("migrateAlertsDbFromV2toV3Failed").m("Step failed in storeStatement.")); + return false; + } + + loadStatement->step(); } + return true; + }); - storeOfflineAlertHelper( - OFFLINE_ALERTS_DATABASE_VERSION_TWO, token, scheduledTime_ISO_8601, event_time_iso_8601); + if (success) { + ACSDK_DEBUG8(LX("migrateAlertsDbFromV2toV3Succeeded")); + submitMetric(m_metricRecorder, ALERTS_V2ToV3_MIGRATION_FAILED, 0); + } else { + ACSDK_ERROR(LX("migrateAlertsDbFromV2toV3Failed")); + submitMetric(m_metricRecorder, ALERTS_V2ToV3_MIGRATION_FAILED, 1); } - ACSDK_DEBUG8(LX("migrateOfflineAlertsDbFromV1ToV2Succeeded")); - return true; + return success; } void SQLiteAlertStorage::close() { m_db.close(); } -bool SQLiteAlertStorage::alertExists(const std::string& token) { - const std::string sqlString = "SELECT COUNT(*) FROM " + ALERTS_V2_TABLE_NAME + " WHERE token=?;"; +bool SQLiteAlertStorage::alertExists(const int dbVersion, const std::string& token) { + if (dbVersion != ALERTS_DATABASE_VERSION_TWO && dbVersion != ALERTS_DATABASE_VERSION_THREE) { + ACSDK_ERROR(LX("alertExistsFailed").d("UnsupportedDbVersion", dbVersion)); + return false; + } + + std::string tableName = ALERTS_V3_TABLE_NAME; + if (ALERTS_DATABASE_VERSION_TWO == dbVersion) { + tableName = ALERTS_V2_TABLE_NAME; + } + const std::string sqlString = "SELECT COUNT(*) FROM " + tableName + " WHERE token=?;"; auto statement = m_db.createStatement(sqlString); if (!statement) { @@ -742,29 +1015,41 @@ static bool storeAlertAssetPlayOrderItems( bool SQLiteAlertStorage::store(std::shared_ptr alert) { if (!alert) { - ACSDK_ERROR(LX("storeFailed").m("Alert parameter is nullptr")); + ACSDK_ERROR(LX("storeAlertFailed").m("Alert parameter is nullptr")); return false; } - if (alertExists(alert->getToken())) { - ACSDK_ERROR(LX("storeFailed").m("Alert already exists.").d("token", alert->getToken())); + if (alertExists(ALERTS_DATABASE_VERSION_THREE, alert->getToken())) { + ACSDK_ERROR(LX("storeAlertFailed").m("Alert already exists.").d("token", alert->getToken())); return false; } // clang-format off - const std::string sqlString = "INSERT INTO " + ALERTS_V2_TABLE_NAME + " (" + - "id, token, type, state, " + - "scheduled_time_unix, scheduled_time_iso_8601, asset_loop_count, " + - "asset_loop_pause_milliseconds, background_asset" + const std::string sqlString = "INSERT INTO " + ALERTS_V3_TABLE_NAME + " (" + + DATABASE_COLUMN_ID_NAME + ", " + DATABASE_COLUMN_TOKEN_NAME + ", " + + DATABASE_COLUMN_TYPE_NAME + ", " + DATABASE_COLUMN_STATE_NAME + ", " + + DATABASE_COLUMN_SCHEDULED_TIME_UNIX_NAME + ", " + + DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME + ", " + + DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME + ", " + + DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME + ", " + + DATABASE_COLUMN_BACKGROUND_ASSET_NAME + ", " + + DATABASE_COLUMN_ORIGINAL_TIME_NAME + ", " + DATABASE_COLUMN_LABEL_NAME + ", " + + DATABASE_COLUMN_CREATED_TIME_NAME + ") VALUES (" + - "?, ?, ?, ?, " + - "?, ?, ?," + - "?, ?" + + "?, ?, " + /// DATABASE_COLUMN_ID_NAME, DATABASE_COLUMN_TOKEN_NAME + "?, ?, " + /// DATABASE_COLUMN_TYPE_NAME, DATABASE_COLUMN_STATE_NAME + "?, " + /// DATABASE_COLUMN_SCHEDULED_TIME_UNIX_NAME + "?, " + /// DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME + "?, " + /// DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME + "?, " + /// DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME + "?, " + /// DATABASE_COLUMN_BACKGROUND_ASSET_NAME + "?, ?, " + /// DATABASE_COLUMN_ORIGINAL_TIME_NAME, DATABASE_COLUMN_LABEL_NAME + "?" + /// DATABASE_COLUMN_CREATED_TIME_NAME ");"; // clang-format on int id = 0; - if (!getTableMaxIntValue(&m_db, ALERTS_V2_TABLE_NAME, DATABASE_COLUMN_ID_NAME, &id)) { + if (!getTableMaxIntValue(&m_db, ALERTS_V3_TABLE_NAME, DATABASE_COLUMN_ID_NAME, &id)) { ACSDK_ERROR(LX("storeFailed").m("Cannot generate alert id.")); return false; } @@ -793,6 +1078,12 @@ bool SQLiteAlertStorage::store(std::shared_ptr alert) { auto token = alert->getToken(); auto iso8601 = alert->getScheduledTime_ISO_8601(); auto assetId = alert->getBackgroundAssetId(); + auto originalTime = + alert->getOriginalTime().hasValue() + ? acsdkAlertsInterfaces::AlertObserverInterface::originalTimeToString(alert->getOriginalTime().value()) + : ""; + std::string label = alert->getLabel().valueOr(""); + std::string createdTime = ""; if (!statement->bindIntParameter(boundParam++, id) || !statement->bindStringParameter(boundParam++, token) || !statement->bindIntParameter(boundParam++, alertType) || !statement->bindIntParameter(boundParam++, alertState) || @@ -800,7 +1091,10 @@ bool SQLiteAlertStorage::store(std::shared_ptr alert) { !statement->bindStringParameter(boundParam++, iso8601) || !statement->bindIntParameter(boundParam++, alert->getLoopCount()) || !statement->bindIntParameter(boundParam++, alert->getLoopPause().count()) || - !statement->bindStringParameter(boundParam, assetId)) { + !statement->bindStringParameter(boundParam++, assetId) || + !statement->bindStringParameter(boundParam++, originalTime) || + !statement->bindStringParameter(boundParam++, label) || + !statement->bindStringParameter(boundParam, createdTime)) { ACSDK_ERROR(LX("storeFailed").m("Could not bind parameter.")); return false; } @@ -821,6 +1115,10 @@ bool SQLiteAlertStorage::store(std::shared_ptr alert) { return false; } + if (m_db.tableExists(ALERTS_V2_TABLE_NAME) && !storeAlertToV2(id, alert)) { + ACSDK_WARN(LX("store").m("Could not store alert data to table " + ALERTS_V2_TABLE_NAME)); + } + if (!storeAlertAssets(&m_db, id, alert->getAssetConfiguration().assets)) { ACSDK_ERROR(LX("storeFailed").m("Could not store alertAssets.")); return false; @@ -830,7 +1128,69 @@ bool SQLiteAlertStorage::store(std::shared_ptr alert) { ACSDK_ERROR(LX("storeFailed").m("Could not store alertAssetPlayOrderItems.")); return false; } + ACSDK_DEBUG9(LX("Successfully stored alert to " + ALERTS_V3_TABLE_NAME)); + return true; +} + +bool SQLiteAlertStorage::storeAlertToV2(const int id, std::shared_ptr alert) { + int alertType = ALERT_EVENT_TYPE_ALARM; + if (!alertTypeToDbField(alert->getTypeName(), &alertType)) { + ACSDK_ERROR(LX("storeAlertToV2Failed").m("Could not convert type name to db field.")); + return false; + } + + int alertState = ALERT_STATE_SET; + if (!alertStateToDbField(alert->getState(), &alertState)) { + ACSDK_ERROR(LX("storeAlertToV2Failed").m("Could not convert alert state to db field.")); + return false; + } + + // clang-format off + const std::string sqlString = "INSERT INTO " + ALERTS_V2_TABLE_NAME + " (" + + DATABASE_COLUMN_ID_NAME + ", " + DATABASE_COLUMN_TOKEN_NAME + ", " + + DATABASE_COLUMN_TYPE_NAME + ", " + DATABASE_COLUMN_STATE_NAME + ", " + + DATABASE_COLUMN_SCHEDULED_TIME_UNIX_NAME + ", " + + DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME + ", " + + DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME + ", " + + DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME + ", " + + DATABASE_COLUMN_BACKGROUND_ASSET_NAME + + ") VALUES (" + + "?, ?, " + /// DATABASE_COLUMN_ID_NAME, DATABASE_COLUMN_TOKEN_NAME + "?, ?, " + /// DATABASE_COLUMN_TYPE_NAME, DATABASE_COLUMN_STATE_NAME + "?, " + /// DATABASE_COLUMN_SCHEDULED_TIME_UNIX_NAME + "?, " + /// DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME + "?, " + /// DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME + "?, " + /// DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME + "?" + /// DATABASE_COLUMN_BACKGROUND_ASSET_NAME + ");"; + // clang-format on + auto statement = m_db.createStatement(sqlString); + if (!statement) { + ACSDK_ERROR(LX("storeAlertToV2Failed").m("Could not create statement.")); + return false; + } + + int boundParam = 1; + if (!statement->bindIntParameter(boundParam++, id) || + !statement->bindStringParameter(boundParam++, alert->getToken()) || + !statement->bindIntParameter(boundParam++, alertType) || + !statement->bindIntParameter(boundParam++, alertState) || + !statement->bindInt64Parameter(boundParam++, alert->getScheduledTime_Unix()) || + !statement->bindStringParameter(boundParam++, alert->getScheduledTime_ISO_8601()) || + !statement->bindIntParameter(boundParam++, alert->getLoopCount()) || + !statement->bindIntParameter(boundParam++, alert->getLoopPause().count()) || + !statement->bindStringParameter(boundParam, alert->getBackgroundAssetId())) { + ACSDK_ERROR(LX("storeAlertToV2Failed").m("Could not bind parameter.")); + return false; + } + + if (!statement->step()) { + ACSDK_ERROR(LX("storeAlertToV2Failed").m("Could not perform step.")); + return false; + } + + ACSDK_DEBUG9(LX("Successfully stored alert to " + ALERTS_V2_TABLE_NAME)); return true; } @@ -1067,7 +1427,7 @@ bool SQLiteAlertStorage::loadHelper( return false; } - if (dbVersion != ALERTS_DATABASE_VERSION_ONE && dbVersion != ALERTS_DATABASE_VERSION_TWO) { + if (dbVersion != ALERTS_DATABASE_VERSION_TWO && dbVersion != ALERTS_DATABASE_VERSION_THREE) { ACSDK_ERROR(LX("loadHelperFailed").d("Invalid version", dbVersion)); return false; } @@ -1086,7 +1446,7 @@ bool SQLiteAlertStorage::loadHelper( return false; } - std::string alertsTableName = ALERTS_TABLE_NAME; + std::string alertsTableName = ALERTS_V3_TABLE_NAME; if (ALERTS_DATABASE_VERSION_TWO == dbVersion) { alertsTableName = ALERTS_V2_TABLE_NAME; } @@ -1109,6 +1469,8 @@ bool SQLiteAlertStorage::loadHelper( int loopCount = 0; int loopPauseInMilliseconds = 0; std::string backgroundAssetId; + std::string originalTime; + std::string label; if (!statement->step()) { ACSDK_ERROR(LX("loadHelperFailed").m("Could not perform step.")); @@ -1122,22 +1484,26 @@ bool SQLiteAlertStorage::loadHelper( for (int i = 0; i < numberColumns; i++) { std::string columnName = statement->getColumnName(i); - if ("id" == columnName) { + if (DATABASE_COLUMN_ID_NAME == columnName) { id = statement->getColumnInt(i); - } else if ("token" == columnName) { + } else if (DATABASE_COLUMN_TOKEN_NAME == columnName) { token = statement->getColumnText(i); - } else if ("type" == columnName) { + } else if (DATABASE_COLUMN_TYPE_NAME == columnName) { type = statement->getColumnInt(i); - } else if ("state" == columnName) { + } else if (DATABASE_COLUMN_STATE_NAME == columnName) { state = statement->getColumnInt(i); - } else if ("scheduled_time_iso_8601" == columnName) { + } else if (DATABASE_COLUMN_SCHEDULED_TIME_ISO_8601_NAME == columnName) { scheduledTime_ISO_8601 = statement->getColumnText(i); - } else if ("asset_loop_count" == columnName) { + } else if (DATABASE_COLUMN_ASSET_LOOP_COUNT_NAME == columnName) { loopCount = statement->getColumnInt(i); - } else if ("asset_loop_pause_milliseconds" == columnName) { + } else if (DATABASE_COLUMN_ASSET_LOOP_PAUSE_MILLISECONDS_NAME == columnName) { loopPauseInMilliseconds = statement->getColumnInt(i); - } else if ("background_asset" == columnName) { + } else if (DATABASE_COLUMN_BACKGROUND_ASSET_NAME == columnName) { backgroundAssetId = statement->getColumnText(i); + } else if (DATABASE_COLUMN_ORIGINAL_TIME_NAME == columnName) { + originalTime = statement->getColumnText(i); + } else if (DATABASE_COLUMN_LABEL_NAME == columnName) { + label = statement->getColumnText(i); } } @@ -1167,6 +1533,8 @@ bool SQLiteAlertStorage::loadHelper( dynamicData.loopCount = loopCount; dynamicData.assetConfiguration.loopPause = std::chrono::milliseconds{loopPauseInMilliseconds}; dynamicData.assetConfiguration.backgroundAssetId = backgroundAssetId; + dynamicData.originalTime = originalTime; + dynamicData.label = label; // alertAssetsMap is an alert id to asset map if (alertAssetsMap.find(id) != alertAssetsMap.end()) { @@ -1205,7 +1573,7 @@ bool SQLiteAlertStorage::loadHelper( bool SQLiteAlertStorage::load( std::vector>* alertContainer, std::shared_ptr settingsManager) { - return loadHelper(ALERTS_DATABASE_VERSION_TWO, alertContainer, settingsManager); + return loadHelper(ALERTS_DATABASE_VERSION_THREE, alertContainer, settingsManager); } bool SQLiteAlertStorage::loadOfflineAlerts( @@ -1291,71 +1659,116 @@ bool SQLiteAlertStorage::modify(std::shared_ptr alert) { return false; } - if (!alertExists(alert->getToken())) { - ACSDK_ERROR(LX("modifyFailed").m("Cannot modify alert.").d("token", alert->getToken())); + if (!alertExists(ALERTS_DATABASE_VERSION_THREE, alert->getToken())) { + ACSDK_ERROR(LX("modifyFailed").m("Cannot modify alert").d("token", alert->getToken())); return false; } - const std::string sqlString = "UPDATE " + ALERTS_V2_TABLE_NAME + " SET " + - "state=?, scheduled_time_unix=?, scheduled_time_iso_8601=? " + "WHERE id=?;"; + if (m_db.tableExists(ALERTS_V2_TABLE_NAME) && !modifyAlert(ALERTS_DATABASE_VERSION_TWO, alert)) { + ACSDK_WARN( + LX("modify").m("Cannot modify alert in table " + ALERTS_V2_TABLE_NAME).d("token", alert->getToken())); + } + return modifyAlert(ALERTS_DATABASE_VERSION_THREE, alert); +} + +bool SQLiteAlertStorage::modifyAlert(const int dbVersion, std::shared_ptr alert) { + if (dbVersion != ALERTS_DATABASE_VERSION_TWO && dbVersion != ALERTS_DATABASE_VERSION_THREE) { + ACSDK_ERROR(LX("modifyAlertFailed").d("UnsupportedDbVersion", dbVersion)); + return false; + } + + std::string tableName = ALERTS_V3_TABLE_NAME; + if (ALERTS_DATABASE_VERSION_TWO == dbVersion) { + tableName = ALERTS_V2_TABLE_NAME; + } + + const std::string sqlString = + "UPDATE " + tableName + " SET " + "state=?, scheduled_time_unix=?, scheduled_time_iso_8601=? " + "WHERE id=?;"; int alertState = ALERT_STATE_SET; if (!alertStateToDbField(alert->getState(), &alertState)) { - ACSDK_ERROR(LX("modifyFailed").m("Cannot convert state.")); + ACSDK_ERROR(LX("modifyFailed").m("Cannot convert state.").d("dbVersion", dbVersion)); return false; } auto statement = m_db.createStatement(sqlString); - if (!statement) { - ACSDK_ERROR(LX("modifyFailed").m("Could not create statement.")); + ACSDK_ERROR(LX("modifyFailed").m("Could not create statement.").d("dbVersion", dbVersion)); return false; } int boundParam = 1; - auto iso8601 = alert->getScheduledTime_ISO_8601(); if (!statement->bindIntParameter(boundParam++, alertState) || !statement->bindInt64Parameter(boundParam++, alert->getScheduledTime_Unix()) || - !statement->bindStringParameter(boundParam++, iso8601) || + !statement->bindStringParameter(boundParam++, alert->getScheduledTime_ISO_8601()) || !statement->bindIntParameter(boundParam++, alert->getId())) { - ACSDK_ERROR(LX("modifyFailed").m("Could not bind a parameter.")); + ACSDK_ERROR(LX("modifyFailed").m("Could not bind a parameter.").d("dbVersion", dbVersion)); return false; } if (!statement->step()) { - ACSDK_ERROR(LX("modifyFailed").m("Could not perform step.")); + ACSDK_ERROR(LX("modifyFailed").m("Could not perform step.").d("dbVersion", dbVersion)); return false; } - return true; } +template +bool SQLiteAlertStorage::retryDataMigration(Task task, Args&&... args) { + auto boundTask = std::bind(std::forward(task), std::forward(args)...); + size_t attempt = 0; + m_waitRetryEvent.reset(); + while (attempt < RETRY_TIME_MAXIMUM) { + /// migration succeeded. + if (boundTask()) { + break; + } + // wait before retry. + if (m_waitRetryEvent.wait(m_retryTimer.calculateTimeToRetry(static_cast(attempt)))) { + break; + } + attempt++; + ACSDK_DEBUG5(LX("retryDataMigration").d("attempt", attempt)); + } + return attempt < RETRY_TIME_MAXIMUM; +} + /** * A utility function to delete alert records from the database for a given alert id. * This function will clean up records in the alerts table. * + * @param dbVersion The version of the alerts table. * @param db The database object. * @param alertId The alert id of the alert to be deleted. * @return Whether the delete operation was successful. */ -static bool eraseAlert(SQLiteDatabase* db, int alertId) { - const std::string sqlString = "DELETE FROM " + ALERTS_V2_TABLE_NAME + " WHERE id=?;"; +static bool eraseAlert(int dbVersion, SQLiteDatabase* db, int alertId) { + if (dbVersion != ALERTS_DATABASE_VERSION_TWO && dbVersion != ALERTS_DATABASE_VERSION_THREE) { + ACSDK_ERROR(LX("eraseAlertFailed").d("UnsupportedDbVersion", dbVersion)); + return false; + } + + std::string tableName = ALERTS_V3_TABLE_NAME; + if (ALERTS_DATABASE_VERSION_TWO == dbVersion) { + tableName = ALERTS_V2_TABLE_NAME; + } + const std::string sqlString = "DELETE FROM " + tableName + " WHERE id=?;"; auto statement = db->createStatement(sqlString); if (!statement) { - ACSDK_ERROR(LX("eraseAlertByAlertIdFailed").m("Could not create statement.")); + ACSDK_ERROR(LX("eraseAlertFailed").m("Could not create statement.")); return false; } int boundParam = 1; if (!statement->bindIntParameter(boundParam, alertId)) { - ACSDK_ERROR(LX("eraseAlertByAlertIdFailed").m("Could not bind a parameter.")); + ACSDK_ERROR(LX("eraseAlertFailed").m("Could not bind a parameter.")); return false; } if (!statement->step()) { - ACSDK_ERROR(LX("eraseAlertByAlertIdFailed").m("Could not perform step.")); + ACSDK_ERROR(LX("eraseAlertFailed").m("Could not perform step.")); return false; } @@ -1482,11 +1895,15 @@ static bool eraseAlertByAlertId(SQLiteDatabase* db, int alertId) { return false; } - if (!eraseAlert(db, alertId)) { + if (!eraseAlert(ALERTS_DATABASE_VERSION_THREE, db, alertId)) { ACSDK_ERROR(LX("eraseAlertByAlertIdFailed").m("Could not erase alert table items.")); return false; } + if (db->tableExists(ALERTS_V2_TABLE_NAME) && !eraseAlert(ALERTS_DATABASE_VERSION_TWO, db, alertId)) { + ACSDK_WARN(LX("eraseAlertByAlertIdFailed").m("Could not erase alert from table " + ALERTS_V2_TABLE_NAME)); + } + if (!eraseAlertAssets(db, alertId)) { ACSDK_ERROR(LX("eraseAlertByAlertIdFailed").m("Could not erase alertAsset table items.")); return false; @@ -1506,7 +1923,7 @@ bool SQLiteAlertStorage::erase(std::shared_ptr alert) { return false; } - if (!alertExists(alert->getToken())) { + if (!alertExists(ALERTS_DATABASE_VERSION_THREE, alert->getToken())) { ACSDK_ERROR(LX("eraseFailed").m("Cannot delete alert - not in database.").d("token", alert->getToken())); return false; } @@ -1570,10 +1987,14 @@ bool SQLiteAlertStorage::bulkErase(const std::list>& aler } bool SQLiteAlertStorage::clearDatabase() { - std::vector tablesToClear = {ALERTS_V2_TABLE_NAME, + m_waitRetryEvent.wakeUp(); + std::vector tablesToClear = {ALERTS_V3_TABLE_NAME, ALERT_ASSETS_TABLE_NAME, ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_NAME, OFFLINE_ALERTS_V2_TABLE_NAME}; + if (m_db.tableExists(ALERTS_V2_TABLE_NAME)) { + tablesToClear.push_back(ALERTS_V2_TABLE_NAME); + } if (m_db.tableExists(OFFLINE_ALERTS_TABLE_NAME)) { tablesToClear.push_back(OFFLINE_ALERTS_TABLE_NAME); } @@ -1596,7 +2017,7 @@ bool SQLiteAlertStorage::clearDatabase() { static void printOneLineSummary(SQLiteDatabase* db) { int numberAlerts = 0; - if (!getNumberTableRows(db, ALERTS_V2_TABLE_NAME, &numberAlerts)) { + if (!getNumberTableRows(db, ALERTS_V3_TABLE_NAME, &numberAlerts)) { ACSDK_ERROR(LX("printOneLineSummaryFailed").m("could not read number of alerts.")); return; } diff --git a/capabilities/Alerts/acsdkAlerts/test/AlertSchedulerTest.cpp b/capabilities/Alerts/acsdkAlerts/test/AlertSchedulerTest.cpp index b0283a343d..ff10dc33a0 100644 --- a/capabilities/Alerts/acsdkAlerts/test/AlertSchedulerTest.cpp +++ b/capabilities/Alerts/acsdkAlerts/test/AlertSchedulerTest.cpp @@ -40,7 +40,8 @@ static const std::string ALERT3_TOKEN = "token3"; static const std::string ALERT4_TOKEN = "token4"; /// Test alert type -static const std::string ALERT_TYPE = "TEST_ALERT_TYPE"; +AlertObserverInterface::Type TYPE_ALARM = AlertObserverInterface::Type::ALARM; +static const std::string TYPE_ALARM_STRING = AlertObserverInterface::typeToString(TYPE_ALARM); /// A schedule instant in the past for alerts. static const std::string PAST_INSTANT = "2000-01-01T12:34:56+0000"; @@ -73,14 +74,14 @@ class TestAlert : public Alert { public: TestAlert() : Alert(defaultAudioFactory, shortAudioFactory, nullptr), - m_alertType{ALERT_TYPE}, + m_alertType{TYPE_ALARM_STRING}, m_renderer{std::make_shared()} { this->setRenderer(m_renderer); } TestAlert(const std::string& token, const std::string& schedTime) : Alert(defaultAudioFactory, shortAudioFactory, nullptr), - m_alertType{ALERT_TYPE}, + m_alertType{TYPE_ALARM_STRING}, m_renderer{std::make_shared()} { this->setRenderer(m_renderer); @@ -251,15 +252,11 @@ class TestAlertObserver : public AlertObserverInterface { lock, TEST_TIMEOUT, [this, newState] { return m_previousState == newState; }); } - void onAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - AlertScheduler::State newState, - const std::string& reason) { + void onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { std::lock_guard lock(m_mutex); m_previousState = m_state; m_previousConditionVariable.notify_all(); - m_state = newState; + m_state = alertInfo.state; m_conditionVariable.notify_all(); } @@ -907,7 +904,14 @@ TEST_F(AlertSchedulerTest, test_onAlertStateChangeStartedInactiveAlert) { /// check that we ignore inactive alerts EXPECT_CALL(*(m_alertStorage.get()), modify(testing::_)).Times(0); - m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason); + m_alertScheduler->onAlertStateChange(AlertObserverInterface::AlertInfo( + ALERT1_TOKEN, + TYPE_ALARM, + testState, + std::chrono::system_clock::now(), + avsCommon::utils::Optional(), + avsCommon::utils::Optional(), + testReason)); } /** @@ -923,7 +927,14 @@ TEST_F(AlertSchedulerTest, test_onAlertStateChangeStartedActiveAlert) { /// active alerts should be handled EXPECT_CALL(*(m_alertStorage.get()), modify(testing::_)).Times(1); m_alertScheduler->updateFocus(avsCommon::avs::FocusState::FOREGROUND, avsCommon::avs::MixingBehavior::PRIMARY); - m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason); + m_alertScheduler->onAlertStateChange(AlertObserverInterface::AlertInfo( + ALERT1_TOKEN, + TYPE_ALARM, + testState, + std::chrono::system_clock::now(), + avsCommon::utils::Optional(), + avsCommon::utils::Optional(), + testReason)); /// when an alert starts, we wait for an Alert to send a STARTED event /// followed by the focus state FOCUS_ENTERED_FOREGROUND. So we'll check @@ -943,7 +954,14 @@ TEST_F(AlertSchedulerTest, test_onAlertStateChangeStopped) { doSimpleTestSetup(true, true); EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1); - m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason); + m_alertScheduler->onAlertStateChange(AlertObserverInterface::AlertInfo( + ALERT1_TOKEN, + TYPE_ALARM, + testState, + std::chrono::system_clock::now(), + avsCommon::utils::Optional(), + avsCommon::utils::Optional(), + testReason)); ASSERT_TRUE(m_testAlertObserver->waitFor(testState)); } @@ -957,7 +975,14 @@ TEST_F(AlertSchedulerTest, test_onAlertStateChangeCompleted) { doSimpleTestSetup(true, true); EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1); - m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason); + m_alertScheduler->onAlertStateChange(AlertObserverInterface::AlertInfo( + ALERT1_TOKEN, + TYPE_ALARM, + testState, + std::chrono::system_clock::now(), + avsCommon::utils::Optional(), + avsCommon::utils::Optional(), + testReason)); ASSERT_TRUE(m_testAlertObserver->waitFor(testState)); } @@ -971,7 +996,14 @@ TEST_F(AlertSchedulerTest, test_onAlertStateChangeSnoozed) { doSimpleTestSetup(true, true); EXPECT_CALL(*(m_alertStorage.get()), modify(testing::_)).Times(1); - m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason); + m_alertScheduler->onAlertStateChange(AlertObserverInterface::AlertInfo( + ALERT1_TOKEN, + TYPE_ALARM, + testState, + std::chrono::system_clock::now(), + avsCommon::utils::Optional(), + avsCommon::utils::Optional(), + testReason)); ASSERT_TRUE(m_testAlertObserver->waitFor(testState)); } @@ -985,7 +1017,14 @@ TEST_F(AlertSchedulerTest, test_onAlertStateChangeErrorActiveAlert) { doSimpleTestSetup(true, true); EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1); - m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason); + m_alertScheduler->onAlertStateChange(AlertObserverInterface::AlertInfo( + ALERT1_TOKEN, + TYPE_ALARM, + testState, + std::chrono::system_clock::now(), + avsCommon::utils::Optional(), + avsCommon::utils::Optional(), + testReason)); ASSERT_TRUE(m_testAlertObserver->waitFor(testState)); } @@ -999,7 +1038,14 @@ TEST_F(AlertSchedulerTest, test_onAlertStateChangeErrorInactiveAlert) { doSimpleTestSetup(false, true); EXPECT_CALL(*(m_alertStorage.get()), erase(testing::_)).Times(1); - m_alertScheduler->onAlertStateChange(ALERT1_TOKEN, ALERT_TYPE, testState, testReason); + m_alertScheduler->onAlertStateChange(AlertObserverInterface::AlertInfo( + ALERT1_TOKEN, + TYPE_ALARM, + testState, + std::chrono::system_clock::now(), + avsCommon::utils::Optional(), + avsCommon::utils::Optional(), + testReason)); ASSERT_TRUE(m_testAlertObserver->waitFor(testState)); } diff --git a/capabilities/Alerts/acsdkAlerts/test/AlertTest.cpp b/capabilities/Alerts/acsdkAlerts/test/AlertTest.cpp index 573224313f..6dcc6fc9c0 100644 --- a/capabilities/Alerts/acsdkAlerts/test/AlertTest.cpp +++ b/capabilities/Alerts/acsdkAlerts/test/AlertTest.cpp @@ -15,6 +15,7 @@ #include #include +#include #include "acsdkAlerts/Alert.h" #include "AVSCommon/Utils/Timing/TimeUtils.h" @@ -58,6 +59,13 @@ static const long LOOP_PAUSE_MS{300}; static const std::string DEFAULT_AUDIO{"default audio"}; static const std::string SHORT_AUDIO{"short audio"}; +/// Label for testing. +static const std::string LABEL_TEST("Test label"); + +/// Original time for testing. +static const std::string ORIGINAL_TIME_TEST("17:00:00.000"); +static const std::string INVALID_ORIGINAL_TIME_TEST{"-1:00:00.000"}; + class MockAlert : public Alert { public: MockAlert() : Alert(defaultAudioFactory, shortAudioFactory, nullptr) { @@ -96,6 +104,15 @@ class MockRenderer : public renderer::RendererInterface { MOCK_METHOD0(stop, void()); }; +class MockAlertObserverInterface : public acsdkAlertsInterfaces::AlertObserverInterface { +public: + void onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) override; +}; + +void MockAlertObserverInterface::onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { + return; +} + class AlertTest : public ::testing::Test { public: AlertTest(); @@ -103,13 +120,23 @@ class AlertTest : public ::testing::Test { protected: std::shared_ptr m_alert; std::shared_ptr m_renderer; + MockAlertObserverInterface m_alertObserverInterface; }; -AlertTest::AlertTest() : m_alert{std::make_shared()}, m_renderer{std::make_shared()} { +AlertTest::AlertTest() : + m_alert{std::make_shared()}, + m_renderer{std::make_shared()}, + m_alertObserverInterface{MockAlertObserverInterface()} { m_alert->setRenderer(m_renderer); + m_alert->setObserver(&m_alertObserverInterface); } -const std::string getPayloadJson(bool inclToken, bool inclSchedTime, const std::string& schedTime) { +const std::string getPayloadJson( + bool inclToken, + bool inclSchedTime, + const std::string& schedTime, + const std::string& label = "", + const std::string& originalTime = "") { std::string tokenJson; if (inclToken) { tokenJson = "\"token\": \"" + TOKEN_TEST + "\","; @@ -120,12 +147,24 @@ const std::string getPayloadJson(bool inclToken, bool inclSchedTime, const std:: schedTimeJson = "\"scheduledTime\": \"" + schedTime + "\","; } + std::string labelJson; + if (!label.empty()) { + labelJson = "\"label\": \"" + label + "\","; + } + + std::string originalTimeJson; + if (!originalTime.empty()) { + originalTimeJson = "\"originalTime\": \"" + originalTime + "\","; + } + // clang-format off const std::string payloadJson = "{" + tokenJson + "\"type\": \"" + ALERT_TYPE + "\"," + schedTimeJson + + labelJson + + originalTimeJson + "\"assets\": [" "{" "\"assetId\": \"" + ASSET_ID1 + "\"," @@ -164,7 +203,7 @@ TEST_F(AlertTest, test_defaultShortAudio) { TEST_F(AlertTest, test_parseFromJsonHappyCase) { std::string errorMessage; - const std::string payloadJson = getPayloadJson(true, true, SCHED_TIME); + const std::string payloadJson = getPayloadJson(true, true, SCHED_TIME, LABEL_TEST, ORIGINAL_TIME_TEST); rapidjson::Document payload; payload.Parse(payloadJson); @@ -177,6 +216,8 @@ TEST_F(AlertTest, test_parseFromJsonHappyCase) { ASSERT_EQ(m_alert->getBackgroundAssetId(), BACKGROUND_ALERT_ASSET); ASSERT_EQ(m_alert->getLoopCount(), LOOP_COUNT); ASSERT_EQ(m_alert->getLoopPause(), std::chrono::milliseconds{LOOP_PAUSE_MS}); + ASSERT_EQ(m_alert->getOriginalTime(), m_alert->validateOriginalTimeString(ORIGINAL_TIME_TEST)); + ASSERT_EQ(m_alert->getLabel(), m_alert->validateLabelString(LABEL_TEST)); std::vector assetPlayOrderItems; assetPlayOrderItems.push_back(ASSET_ID1); @@ -225,8 +266,68 @@ TEST_F(AlertTest, test_parseFromJsonBadSchedTimeFormat) { ASSERT_EQ(resultStatus, Alert::ParseFromJsonStatus::INVALID_VALUE); } -TEST_F(AlertTest, test_setStateActive) { +TEST_F(AlertTest, test_parseFromJsonInvalidOriginalTime) { + std::string errorMessage; + const std::string payloadJson = getPayloadJson(true, true, SCHED_TIME, LABEL_TEST, INVALID_ORIGINAL_TIME_TEST); + + rapidjson::Document payload; + payload.Parse(payloadJson); + + Alert::ParseFromJsonStatus resultStatus = m_alert->parseFromJson(payload, &errorMessage); + + ASSERT_EQ(resultStatus, Alert::ParseFromJsonStatus::OK); + ASSERT_FALSE(m_alert->getOriginalTime().hasValue()); + ASSERT_TRUE(m_alert->getLabel().hasValue()); + ASSERT_EQ(m_alert->getLabel().value(), LABEL_TEST); +} + +TEST_F(AlertTest, test_parseFromJsonEmptyOriginalTimeAndLabel) { + std::string errorMessage; + const std::string payloadJson = getPayloadJson(true, true, SCHED_TIME); + + rapidjson::Document payload; + payload.Parse(payloadJson); + + Alert::ParseFromJsonStatus resultStatus = m_alert->parseFromJson(payload, &errorMessage); + + ASSERT_EQ(resultStatus, Alert::ParseFromJsonStatus::OK); + ASSERT_FALSE(m_alert->getOriginalTime().hasValue()); + ASSERT_FALSE(m_alert->getLabel().hasValue()); +} + +TEST_F(AlertTest, test_setStateActiveValid) { m_alert->reset(); + + std::string schedTime{"2030-02-02T12:56:34+0000"}; + Alert::DynamicData dynamicData; + m_alert->getAlertData(nullptr, &dynamicData); + ASSERT_TRUE(dynamicData.timePoint.setTime_ISO_8601(schedTime)); + m_alert->setAlertData(nullptr, &dynamicData); + + // renderer should be started + EXPECT_CALL(*(m_renderer.get()), start(_, _, _, _, _, _, _)).WillRepeatedly(Return()); + ASSERT_EQ(m_alert->getState(), Alert::State::SET); + m_alert->setStateActive(); + ASSERT_NE(m_alert->getState(), Alert::State::ACTIVE); + + m_alert->activate(); + ASSERT_EQ(m_alert->getState(), Alert::State::ACTIVATING); + m_alert->setStateActive(); + ASSERT_EQ(m_alert->getState(), Alert::State::ACTIVE); +} + +TEST_F(AlertTest, test_setStateActiveInvalid) { + m_alert->reset(); + + // set a time in the past + std::string schedTime{"1990-02-02T12:56:34+0000"}; + Alert::DynamicData dynamicData; + m_alert->getAlertData(nullptr, &dynamicData); + ASSERT_TRUE(dynamicData.timePoint.setTime_ISO_8601(schedTime)); + m_alert->setAlertData(nullptr, &dynamicData); + + // renderer shouldn't be started + EXPECT_CALL(*(m_renderer.get()), start(_, _, _, _, _, _, _)).Times(0); ASSERT_EQ(m_alert->getState(), Alert::State::SET); m_alert->setStateActive(); ASSERT_NE(m_alert->getState(), Alert::State::ACTIVE); @@ -253,9 +354,12 @@ TEST_F(AlertTest, test_setTimeISO8601) { m_alert->setAlertData(nullptr, &dynamicData); int64_t unixTime = 0; timeUtils.convert8601TimeStringToUnix(schedTime, &unixTime); + auto sec = + std::chrono::duration_cast(m_alert->getScheduledTime_Utc_TimePoint().time_since_epoch()); ASSERT_EQ(m_alert->getScheduledTime_ISO_8601(), schedTime); ASSERT_EQ(m_alert->getScheduledTime_Unix(), unixTime); + ASSERT_EQ(static_cast(sec.count()), unixTime); } TEST_F(AlertTest, test_updateScheduleActiveFailed) { diff --git a/capabilities/Alerts/acsdkAlerts/test/AlertsCapabilityAgentTest.cpp b/capabilities/Alerts/acsdkAlerts/test/AlertsCapabilityAgentTest.cpp index d9b8ba384b..592906a4ca 100644 --- a/capabilities/Alerts/acsdkAlerts/test/AlertsCapabilityAgentTest.cpp +++ b/capabilities/Alerts/acsdkAlerts/test/AlertsCapabilityAgentTest.cpp @@ -89,6 +89,9 @@ constexpr int LOWER_VOLUME_VALUE = 50; /// The timeout used throughout the tests. static const auto TEST_TIMEOUT = std::chrono::seconds(5); +/// The alert type. +AlertObserverInterface::Type TYPE_ALARM = AlertObserverInterface::Type::ALARM; + // clang-format off /// General test directive payload. @@ -476,7 +479,8 @@ void AlertsCapabilityAgentTest::testStartAlertWithContentVolume( m_alertsCA->onFocusChanged(otherChannel, avsCommon::avs::FocusState::BACKGROUND); // "Start" alert - m_alertsCA->onAlertStateChange("", "", AlertObserverInterface::State::STARTED, ""); + m_alertsCA->onAlertStateChange(AlertObserverInterface::AlertInfo( + "", TYPE_ALARM, AlertObserverInterface::State::STARTED, std::chrono::system_clock::now())); std::unique_lock ulock(m_mutex); waitCV.wait_for(ulock, std::chrono::milliseconds(MAX_WAIT_TIME_MS)); @@ -506,8 +510,8 @@ TEST_F(AlertsCapabilityAgentTest, test_localAlertVolumeChangeNoAlert) { * Test local alert volume changes. With alert sounding. Must not send event, volume is treated as local. */ TEST_F(AlertsCapabilityAgentTest, testTimer_localAlertVolumeChangeAlertPlaying) { - m_alertsCA->onAlertStateChange("", "", AlertObserverInterface::State::STARTED, ""); - + m_alertsCA->onAlertStateChange(AlertObserverInterface::AlertInfo( + "", TYPE_ALARM, AlertObserverInterface::State::STARTED, std::chrono::system_clock::now())); // We have to wait for the alert state to be processed before updating speaker settings. auto future = m_mockMessageSender->getNextMessage(); ASSERT_EQ(future.wait_for(std::chrono::milliseconds(MAX_WAIT_TIME_MS)), std::future_status::ready); @@ -569,7 +573,8 @@ TEST_F(AlertsCapabilityAgentTest, test_avsAlertVolumeChangeAlertPlaying) { *(m_speakerManager.get()), setVolume(ChannelVolumeInterface::Type::AVS_ALERTS_VOLUME, TEST_VOLUME_VALUE, _)) .Times(1); - m_alertsCA->onAlertStateChange("", "", AlertObserverInterface::State::STARTED, ""); + m_alertsCA->onAlertStateChange(AlertObserverInterface::AlertInfo( + "", TYPE_ALARM, AlertObserverInterface::State::STARTED, std::chrono::system_clock::now())); auto future = m_mockMessageSender->getNextMessage(); ASSERT_EQ(future.wait_for(std::chrono::milliseconds(MAX_WAIT_TIME_MS)), std::future_status::ready); diff --git a/capabilities/Alerts/acsdkAlerts/test/Renderer/RendererTest.cpp b/capabilities/Alerts/acsdkAlerts/test/Renderer/RendererTest.cpp index 77e148a48f..59c14d0a4e 100644 --- a/capabilities/Alerts/acsdkAlerts/test/Renderer/RendererTest.cpp +++ b/capabilities/Alerts/acsdkAlerts/test/Renderer/RendererTest.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "acsdkAlerts/Renderer/Renderer.h" @@ -195,6 +196,7 @@ class RendererTest : public ::testing::Test { std::shared_ptr m_renderer; std::shared_ptr m_audioPipelineFactory; std::shared_ptr m_shutdownNotifier; + std::shared_ptr m_mockConnectionMonitor; static std::pair, const avsCommon::utils::MediaType> audioFactoryFunc() { return std::pair, const avsCommon::utils::MediaType>( @@ -205,6 +207,7 @@ class RendererTest : public ::testing::Test { RendererTest::RendererTest() : m_observer{std::make_shared()} { m_audioPipelineFactory = std::make_shared(); m_shutdownNotifier = std::make_shared>(); + m_mockConnectionMonitor = std::make_shared(); m_mediaPlayer = TestMediaPlayer::create(); bool equalizerAvailable = false; @@ -221,7 +224,8 @@ RendererTest::RendererTest() : m_observer{std::make_shared .WillOnce(Return(std::make_shared( m_mediaPlayer, nullptr, nullptr, nullptr))); - m_renderer = Renderer::createAlertRenderer(m_audioPipelineFactory, nullptr, m_shutdownNotifier); + m_renderer = + Renderer::createAlertRenderer(m_audioPipelineFactory, nullptr, m_shutdownNotifier, m_mockConnectionMonitor); } RendererTest::~RendererTest() { @@ -249,17 +253,17 @@ TEST_F(RendererTest, test_createAlertRenderer) { ASSERT_NE(m_renderer, nullptr); /// confirm we return a nullptr if a nullptr was passed in - ASSERT_EQ(Renderer::createAlertRenderer(nullptr, nullptr, nullptr), nullptr); + ASSERT_EQ(Renderer::createAlertRenderer(nullptr, nullptr, nullptr, nullptr), nullptr); } /** * Test if the Renderer class creates an object appropriately and fails when it must */ TEST_F(RendererTest, test_create) { - ASSERT_NE(Renderer::create(m_mediaPlayer, nullptr), nullptr); + ASSERT_NE(Renderer::create(m_mediaPlayer, nullptr, nullptr), nullptr); /// confirm we return a nullptr if a nullptr was passed in - ASSERT_EQ(Renderer::create(nullptr, nullptr), nullptr); + ASSERT_EQ(Renderer::create(nullptr, nullptr, nullptr), nullptr); } /** diff --git a/capabilities/Alerts/acsdkAlerts/test/SQLiteAlertStorageTest.cpp b/capabilities/Alerts/acsdkAlerts/test/SQLiteAlertStorageTest.cpp new file mode 100644 index 0000000000..5af7ef0c56 --- /dev/null +++ b/capabilities/Alerts/acsdkAlerts/test/SQLiteAlertStorageTest.cpp @@ -0,0 +1,860 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAlerts/Alert.h" +#include "acsdkAlerts/Storage/SQLiteAlertStorage.h" + +namespace alexaClientSDK { +namespace acsdkAlerts { +namespace test { + +using namespace acsdkAlerts::storage; +using namespace alexaClientSDK::storage::sqliteStorage; +using namespace avsCommon::avs::initialization; +using namespace avsCommon::sdkInterfaces::audio::test; +using namespace avsCommon::utils::configuration; +using namespace avsCommon::utils::file; +using namespace avsCommon::utils::metrics::test; +using namespace avsCommon::utils::string; +using namespace rapidjson; +using namespace ::testing; + +/// String to identify log entries originating from this file. +static const std::string TAG("SQLiteAlertStorageTest"); + +/** + * 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 name of database file for testing. +static const std::string TEST_DATABASE_FILE_NAME = "SQLiteAlertStorageTest.db"; + +// clang-format off +static const std::string VALID_ALERTS_DB_CONFIG_JSON = R"( + { + "alertsCapabilityAgent": { + "databaseFilePath": ")" + TEST_DATABASE_FILE_NAME + R"(" + } + } +)"; +// clang-format on + +// clang-format off +static const std::string INVALID_ALERTS_DB_CONFIG_JSON = R"( + { + "alertsCapabilityAgent": { + "databaseFilePath": "" + } + } +)"; +// clang-format on + +/// The name of the alerts (v2) table. +static const std::string ALERTS_V2_TABLE_NAME = "alerts_v2"; + +/// The name of the alerts (v3) table. +static const std::string ALERTS_V3_TABLE_NAME = "alerts_v3"; + +/// The SQL string to create the alerts table. +// clang-format off +static const std::string CREATE_ALERTS_V2_TABLE_SQL_STRING = std::string("CREATE TABLE ") + + ALERTS_V2_TABLE_NAME + " (" + + "id INT PRIMARY KEY NOT NULL," + + "token TEXT NOT NULL," + + "type INT NOT NULL," + + "state INT NOT NULL," + + "scheduled_time_unix INT NOT NULL," + + "scheduled_time_iso_8601 TEXT NOT NULL," + + "asset_loop_count INT NOT NULL," + + "asset_loop_pause_milliseconds INT NOT NULL," + + "background_asset TEXT NOT NULL);"; +// clang-format on + +// clang-format off +static const std::string CREATE_ALERTS_V3_TABLE_SQL_STRING = std::string("CREATE TABLE ") + + ALERTS_V3_TABLE_NAME + " (" + + "id INT PRIMARY KEY NOT NULL," + + "token TEXT NOT NULL," + + "type INT NOT NULL," + + "state INT NOT NULL," + + "scheduled_time_unix INT NOT NULL," + + "scheduled_time_iso_8601 TEXT NOT NULL," + + "asset_loop_count INT NOT NULL," + + "asset_loop_pause_milliseconds INT NOT NULL," + + "background_asset TEXT NOT NULL," + + "original_time TEXT NOT NULL," + + "label TEXT NOT NULL," + + "created_time_iso_8601 TEXT NOT NULL);"; +// clang-format on + +/// The name of the alertAssets table. +static const std::string ALERT_ASSETS_TABLE_NAME = "alertAssets"; + +/// The SQL string to create the alertAssets table. +// clang-format off +static const std::string CREATE_ALERT_ASSETS_TABLE_SQL_STRING = std::string("CREATE TABLE ") + + ALERT_ASSETS_TABLE_NAME + " (" + + "id INT PRIMARY KEY NOT NULL," + + "alert_id INT NOT NULL," + + "avs_id TEXT NOT NULL," + + "url TEXT NOT NULL);"; +// clang-format on + +/// The name of the offline alerts (v1) table. +static const std::string OFFLINE_ALERTS_TABLE_NAME = "offlineAlerts"; + +/// The name of the offline alerts (v2) table. +static const std::string OFFLINE_ALERTS_V2_TABLE_NAME = "offlineAlerts_v2"; + +/// The SQL string to create the offline alerts table. +// clang-format off +static const std::string CREATE_OFFLINE_ALERTS_V1_TABLE_SQL_STRING = std::string("CREATE TABLE ") + + OFFLINE_ALERTS_TABLE_NAME + " (" + + "id INT PRIMARY KEY NOT NULL," + + "token TEXT NOT NULL," + + "scheduled_time_iso_8601 TEXT NOT NULL);"; +// clang-format on + +// clang-format off +static const std::string CREATE_OFFLINE_ALERTS_V2_TABLE_SQL_STRING = std::string("CREATE TABLE ") + + OFFLINE_ALERTS_V2_TABLE_NAME + " (" + + "id INT PRIMARY KEY NOT NULL," + + "token TEXT NOT NULL," + + "scheduled_time_iso_8601 TEXT NOT NULL," + + "event_time_iso_8601 TEXT NOT NULL);"; +// clang-format on + +/// The name of the alertAssetPlayOrderItems table. +static const std::string ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_NAME = "alertAssetPlayOrderItems"; + +/// The SQL string to create the alertAssetPlayOrderItems table. +// clang-format off +static const std::string CREATE_ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_SQL_STRING = std::string("CREATE TABLE ") + + ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_NAME + " (" + + "id INT PRIMARY KEY NOT NULL," + + "alert_id INT NOT NULL," + + "asset_play_order_position INT NOT NULL," + + "asset_play_order_token TEXT NOT NULL);"; +// clang-format on + +/// Constants for Alerts. +/// Type of the alert for testing. +static const std::string TEST_ALERT_TYPE_ALARM = "ALARM"; +static const std::string TEST_ALERT_TYPE_TIMER = "TIMER"; +static const std::string TEST_ALERT_TYPE_REMINDER = "REMINDER"; + +/// Schduled time string in ISO 8601 format. +static const std::string SCHEDULED_TIME_ISO_STRING = "2008-08-08T08:00:00+0000"; +static const std::string SCHEDULED_TIME_ISO_STRING_ALARM = "2020-08-08T08:00:00+0000"; +static const std::string SCHEDULED_TIME_ISO_STRING_TIMER = "2020-08-09T08:00:00+0000"; +static const std::string SCHEDULED_TIME_ISO_STRING_REMINDER = "2020-08-10T08:00:00+0000"; + +/// Alerts token. +static const std::string TOKEN_ALARM = "token-alarm"; +static const std::string TOKEN_TIMER = "token-timer"; +static const std::string TOKEN_REMINDER = "token-reminder"; + +/// Original time. +static const std::string ORIGINAL_TIME_ALARM = "16:00:00.000"; +static const std::string ORIGINAL_TIME_REMINDER = "18:00:00.000"; + +/// Label for the alerts. +static const std::string LABEL_TIMER = "coffee"; +static const std::string LABEL_REMINDER = "walk the dog"; + +/** + * Mock class for @c Alert. + */ +class MockAlert : public Alert { +public: + MockAlert(const std::string& typeName) : + Alert(defaultAudioFactory, shortAudioFactory, nullptr), + m_alertType{typeName} { + } + + std::string getTypeName() const override { + return m_alertType; + } + +private: + static std::pair, const avsCommon::utils::MediaType> defaultAudioFactory() { + return std::pair, const avsCommon::utils::MediaType>( + std::unique_ptr(new std::stringstream("default audio")), + avsCommon::utils::MediaType::MPEG); + } + static std::pair, const avsCommon::utils::MediaType> shortAudioFactory() { + return std::pair, const avsCommon::utils::MediaType>( + std::unique_ptr(new std::stringstream("short audio")), + avsCommon::utils::MediaType::MPEG); + } + const std::string m_alertType; +}; + +class SQLiteAlertStorageTest : public Test { +public: + /// Constructor, + SQLiteAlertStorageTest(); + + /// Setup for tests. + void SetUp() override; + + /// Cleanup for test. + void TearDown() override; + + /// Sets up alerts database. + void setUpDatabase(); + + /// Utility function to create the alert; + std::shared_ptr createAlert(const std::string& alertType); + + /// Utility function to check if the alert exists in a specific table. + bool alertExists(SQLiteDatabase* db, const std::string& tableName, const std::string& token); + + /// Utility function to check if a table is empty. + bool isTableEmpty(SQLiteDatabase* db, const std::string& tableName); + + /// The @c SQLiteAlertStorage instance to test. + std::shared_ptr m_alertStorage; + + /// The @c AlertsAudioFactoryInterface instance to provide audio resources. + std::shared_ptr m_mockAlertsAudioFactory; + + /// The @c MetricRecorderInterface instance to record metrics. + std::shared_ptr m_mockMetricRecorder; +}; + +void SQLiteAlertStorageTest::SetUp() { + ASSERT_NE(m_alertStorage, nullptr); +} + +void SQLiteAlertStorageTest::TearDown() { + ConfigurationNode::uninitialize(); + if (m_alertStorage) { + m_alertStorage->close(); + } + m_alertStorage.reset(); + if (fileExists(TEST_DATABASE_FILE_NAME)) { + removeFile(TEST_DATABASE_FILE_NAME); + } +} + +void SQLiteAlertStorageTest::setUpDatabase() { + if (!m_alertStorage) { + return; + } + ASSERT_TRUE(m_alertStorage->createDatabase()); +} + +SQLiteAlertStorageTest::SQLiteAlertStorageTest() { + m_mockAlertsAudioFactory = std::make_shared>(); + m_mockMetricRecorder = std::make_shared>(); + auto configJson = std::make_shared(VALID_ALERTS_DB_CONFIG_JSON); + ConfigurationNode::initialize({configJson}); + m_alertStorage = + SQLiteAlertStorage::create(ConfigurationNode::getRoot(), m_mockAlertsAudioFactory, m_mockMetricRecorder); +} + +std::shared_ptr SQLiteAlertStorageTest::createAlert(const std::string& alertType) { + std::shared_ptr alert = std::make_shared(""); + if (TEST_ALERT_TYPE_ALARM == alertType) { + alert = std::make_shared(TEST_ALERT_TYPE_ALARM); + Alert::StaticData staticDataAlarm; + Alert::DynamicData dynamicDataAlarm; + staticDataAlarm.token = TOKEN_ALARM; + dynamicDataAlarm.timePoint.setTime_ISO_8601(SCHEDULED_TIME_ISO_STRING_ALARM); + dynamicDataAlarm.loopCount = 1; + dynamicDataAlarm.originalTime = ORIGINAL_TIME_ALARM; + alert->setAlertData(&staticDataAlarm, &dynamicDataAlarm); + return alert; + } + + if (TEST_ALERT_TYPE_TIMER == alertType) { + alert = std::make_shared(TEST_ALERT_TYPE_TIMER); + Alert::StaticData staticDataTimer; + Alert::DynamicData dynamicDataTimer; + staticDataTimer.token = TOKEN_TIMER; + dynamicDataTimer.timePoint.setTime_ISO_8601(SCHEDULED_TIME_ISO_STRING_TIMER); + dynamicDataTimer.loopCount = 2; + dynamicDataTimer.label = LABEL_TIMER; + alert->setAlertData(&staticDataTimer, &dynamicDataTimer); + return alert; + } + + if (TEST_ALERT_TYPE_REMINDER == alertType) { + alert = std::make_shared(TEST_ALERT_TYPE_REMINDER); + Alert::StaticData staticDataReminder; + Alert::DynamicData dynamicDataReminder; + staticDataReminder.token = TOKEN_REMINDER; + dynamicDataReminder.timePoint.setTime_ISO_8601(SCHEDULED_TIME_ISO_STRING_REMINDER); + dynamicDataReminder.loopCount = 3; + dynamicDataReminder.originalTime = ORIGINAL_TIME_REMINDER; + dynamicDataReminder.label = LABEL_REMINDER; + alert->setAlertData(&staticDataReminder, &dynamicDataReminder); + return alert; + } + return alert; +} + +bool SQLiteAlertStorageTest::alertExists(SQLiteDatabase* db, const std::string& tableName, const std::string& token) { + const std::string sqlString = "SELECT COUNT(*) FROM " + tableName + " WHERE token=?;"; + auto statement = db->createStatement(sqlString); + if (!statement) { + ACSDK_ERROR(LX("alertExistsFailed").m("Create statement failed.")); + return false; + } + int boundParam = 1; + if (!statement->bindStringParameter(boundParam, token)) { + ACSDK_ERROR(LX("alertExistsFailed").m("bindStringParameter failed.")); + return false; + } + if (!statement->step()) { + ACSDK_ERROR(LX("alertExistsFailed").m("Perform step failed.")); + return false; + } + int countValue = 0; + if (!stringToInt(statement->getColumnText(0).c_str(), &countValue)) { + ACSDK_ERROR(LX("alertExistsFailed").m("stringToInt failed")); + return false; + } + return countValue > 0; +} + +bool SQLiteAlertStorageTest::isTableEmpty(SQLiteDatabase* db, const std::string& tableName) { + const std::string sqlString = "SELECT COUNT(*) FROM " + tableName + ";"; + auto statement = db->createStatement(sqlString); + if (!statement) { + ACSDK_ERROR(LX("alertExistsFailed").m("Create statement failed.")); + return false; + } + if (!statement->step()) { + ACSDK_ERROR(LX("alertExistsFailed").m("Perform step failed.")); + return false; + } + int countValue = 0; + if (!stringToInt(statement->getColumnText(0).c_str(), &countValue)) { + ACSDK_ERROR(LX("alertExistsFailed").m("stringToInt failed")); + return false; + } + return !(countValue > 0); +} + +/** + * Test create with empty @c ConfigurationNode. + */ +TEST_F(SQLiteAlertStorageTest, test_emptyDbConfiguration) { + ConfigurationNode::uninitialize(); + ConfigurationNode::initialize(std::vector>()); + auto alertStorage = + SQLiteAlertStorage::create(ConfigurationNode::getRoot(), m_mockAlertsAudioFactory, m_mockMetricRecorder); + ASSERT_EQ(alertStorage, nullptr); +} + +/** + * Test create with invalid alerts database configuration. + */ +TEST_F(SQLiteAlertStorageTest, test_invalidDbConfiguration) { + ConfigurationNode::uninitialize(); + auto configJson = std::make_shared(INVALID_ALERTS_DB_CONFIG_JSON); + ConfigurationNode::initialize({configJson}); + auto alertStorage = + SQLiteAlertStorage::create(ConfigurationNode::getRoot(), m_mockAlertsAudioFactory, m_mockMetricRecorder); + ASSERT_EQ(alertStorage, nullptr); +} + +/** + * Test create with null @c AlertsAudioFactoryInterface. + */ +TEST_F(SQLiteAlertStorageTest, test_nullAlertsAudioFactory) { + auto alertStorage = SQLiteAlertStorage::create(ConfigurationNode::getRoot(), nullptr, m_mockMetricRecorder); + ASSERT_EQ(alertStorage, nullptr); +} + +/** + * Test create with null @c MetricRecorderInterface. It's okay if metric recorder instance is not provided. + */ +TEST_F(SQLiteAlertStorageTest, test_nullMetricRecorder) { + auto alertStorage = SQLiteAlertStorage::create(ConfigurationNode::getRoot(), m_mockAlertsAudioFactory, nullptr); + ASSERT_NE(alertStorage, nullptr); +} + +/** + * Test if open existing database succeeds. + */ +TEST_F(SQLiteAlertStorageTest, test_openExistingDatabaseSucceeds) { + setUpDatabase(); + m_alertStorage->close(); + ASSERT_TRUE(m_alertStorage->open()); +} + +/** + * Test if create existing database fails. + */ +TEST_F(SQLiteAlertStorageTest, test_createExistingDatabaseFails) { + setUpDatabase(); + ASSERT_FALSE(m_alertStorage->createDatabase()); +} + +/** + * Test if open succeeds when latest alerts table does not exist yet. + */ +TEST_F(SQLiteAlertStorageTest, test_openDatabaseWhenAlertsTableIsMissing) { + setUpDatabase(); + alexaClientSDK::storage::sqliteStorage::SQLiteDatabase db(TEST_DATABASE_FILE_NAME); + ASSERT_TRUE(db.open()); + const std::string dropTableSqlString = "DROP TABLE IF EXISTS " + ALERTS_V3_TABLE_NAME + ";"; + ASSERT_TRUE(db.performQuery(dropTableSqlString)); + ASSERT_FALSE(db.tableExists(ALERTS_V3_TABLE_NAME)); + m_alertStorage->close(); + + /// missing table will be created on open. + ASSERT_TRUE(m_alertStorage->open()); + ASSERT_TRUE(db.tableExists(ALERTS_V3_TABLE_NAME)); + db.close(); +} + +/** + * Test if open succeeds when latest offline alerts table does not exist yet. + */ +TEST_F(SQLiteAlertStorageTest, test_openDatabaseWhenOfflineAlertsTableIsMissing) { + setUpDatabase(); + alexaClientSDK::storage::sqliteStorage::SQLiteDatabase db(TEST_DATABASE_FILE_NAME); + ASSERT_TRUE(db.open()); + const std::string dropTableSqlString = "DROP TABLE IF EXISTS " + OFFLINE_ALERTS_V2_TABLE_NAME + ";"; + ASSERT_TRUE(db.performQuery(dropTableSqlString)); + ASSERT_FALSE(db.tableExists(OFFLINE_ALERTS_V2_TABLE_NAME)); + m_alertStorage->close(); + + /// missing table will be created on open. + ASSERT_TRUE(m_alertStorage->open()); + ASSERT_TRUE(db.tableExists(OFFLINE_ALERTS_V2_TABLE_NAME)); + db.close(); +} + +/** + * Test if open succeeds when 'alertAsset' and 'alertAssetPlayOrderItems' tables do not exist yet. + */ +TEST_F(SQLiteAlertStorageTest, test_openDatabaseWhenAssetTablesAreMissing) { + setUpDatabase(); + alexaClientSDK::storage::sqliteStorage::SQLiteDatabase db(TEST_DATABASE_FILE_NAME); + ASSERT_TRUE(db.open()); + const std::string dropAlertAssetsTable = "DROP TABLE IF EXISTS " + ALERT_ASSETS_TABLE_NAME + ";"; + const std::string dropAlertAssetPlayOrderItemsTable = + "DROP TABLE IF EXISTS " + ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_NAME + ";"; + ASSERT_TRUE(db.performQuery(dropAlertAssetsTable)); + ASSERT_TRUE(db.performQuery(dropAlertAssetPlayOrderItemsTable)); + ASSERT_FALSE(db.tableExists(ALERT_ASSETS_TABLE_NAME)); + ASSERT_FALSE(db.tableExists(ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_NAME)); + m_alertStorage->close(); + + /// missing tables will be created on open. + ASSERT_TRUE(m_alertStorage->open()); + ASSERT_TRUE(db.tableExists(ALERT_ASSETS_TABLE_NAME)); + ASSERT_TRUE(db.tableExists(ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_NAME)); + db.close(); +} + +/** + * Test data migration from alerts v2 table to v3. + */ +TEST_F(SQLiteAlertStorageTest, test_migrateAlertFromV2ToV3) { + alexaClientSDK::storage::sqliteStorage::SQLiteDatabase db(TEST_DATABASE_FILE_NAME); + ASSERT_TRUE(db.initialize()); + /// create alerts v2 table. + ASSERT_TRUE(db.performQuery(CREATE_ALERTS_V2_TABLE_SQL_STRING)); + ASSERT_TRUE(db.performQuery(CREATE_ALERT_ASSETS_TABLE_SQL_STRING)); + ASSERT_TRUE(db.performQuery(CREATE_OFFLINE_ALERTS_V2_TABLE_SQL_STRING)); + ASSERT_TRUE(db.performQuery(CREATE_ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_SQL_STRING)); + const std::string storeToAlertV2 = + "INSERT INTO " + ALERTS_V2_TABLE_NAME + " (" + + "id, token, type, state, scheduled_time_unix, scheduled_time_iso_8601, asset_loop_count, " + + "asset_loop_pause_milliseconds, background_asset" + ") VALUES (" + + "?, ?, ?, ?, ?, ?, ?, " + "?, ?" + ");"; + auto storeStatement = db.createStatement(storeToAlertV2); + ASSERT_NE(storeStatement, nullptr); + int id = 1; + std::string token = "token-abc"; + int type = 1; + int state = 1; + int64_t scheduledTime_Unix = 1218207600; + std::string scheduledTime_ISO_8601 = SCHEDULED_TIME_ISO_STRING; + int loopCount = 3; + int loopPauseInMilliseconds = 0; + std::string backgroundAssetId = "assetId"; + int boundParam = 1; + bool bindResult = storeStatement->bindIntParameter(boundParam++, id) && + storeStatement->bindStringParameter(boundParam++, token) && + storeStatement->bindIntParameter(boundParam++, type) && + storeStatement->bindIntParameter(boundParam++, state) && + storeStatement->bindInt64Parameter(boundParam++, scheduledTime_Unix) && + storeStatement->bindStringParameter(boundParam++, scheduledTime_ISO_8601) && + storeStatement->bindIntParameter(boundParam++, loopCount) && + storeStatement->bindIntParameter(boundParam++, loopPauseInMilliseconds) && + storeStatement->bindStringParameter(boundParam, backgroundAssetId); + ASSERT_TRUE(bindResult); + ASSERT_TRUE(storeStatement->step()); + storeStatement->finalize(); + ASSERT_FALSE(db.tableExists(ALERTS_V3_TABLE_NAME)); + + /// data migration happens on open. + ASSERT_TRUE(m_alertStorage->open()); + ASSERT_TRUE(db.tableExists(ALERTS_V3_TABLE_NAME)); + + /// verify if the alert has been migrated successfully from table v2 to v3. + const std::string loadAlertsV3 = "SELECT * FROM " + ALERTS_V3_TABLE_NAME + ";"; + auto loadStatement = db.createStatement(loadAlertsV3); + ASSERT_NE(loadStatement, nullptr); + ASSERT_TRUE(loadStatement->step()); + while (SQLITE_ROW == loadStatement->getStepResult()) { + int numberOfColumns = loadStatement->getColumnCount(); + for (int i = 0; i < numberOfColumns; i++) { + std::string columnName = loadStatement->getColumnName(i); + + if ("id" == columnName) { + ASSERT_EQ(id, loadStatement->getColumnInt(i)); + } else if ("token" == columnName) { + ASSERT_EQ(token, loadStatement->getColumnText(i)); + } else if ("type" == columnName) { + ASSERT_EQ(type, loadStatement->getColumnInt(i)); + } else if ("state" == columnName) { + ASSERT_EQ(state, loadStatement->getColumnInt(i)); + } else if ("scheduled_time_unix" == columnName) { + ASSERT_EQ(scheduledTime_Unix, loadStatement->getColumnInt64(i)); + } else if ("scheduled_time_iso_8601" == columnName) { + ASSERT_EQ(scheduledTime_ISO_8601, loadStatement->getColumnText(i)); + } else if ("asset_loop_count" == columnName) { + ASSERT_EQ(loopCount, loadStatement->getColumnInt(i)); + } else if ("asset_loop_pause_milliseconds" == columnName) { + ASSERT_EQ(loopPauseInMilliseconds, loadStatement->getColumnInt(i)); + } else if ("background_asset" == columnName) { + ASSERT_EQ(backgroundAssetId, loadStatement->getColumnText(i)); + } + } + loadStatement->step(); + } + loadStatement->finalize(); + db.close(); +} + +/** + * Test data migration from offline alerts v1 table to v2. + */ +TEST_F(SQLiteAlertStorageTest, test_migrateOfflineAlertFromV1ToV2) { + alexaClientSDK::storage::sqliteStorage::SQLiteDatabase db(TEST_DATABASE_FILE_NAME); + ASSERT_TRUE(db.initialize()); + /// create offline alerts v1 table. + ASSERT_TRUE(db.performQuery(CREATE_ALERTS_V3_TABLE_SQL_STRING)); + ASSERT_TRUE(db.performQuery(CREATE_OFFLINE_ALERTS_V1_TABLE_SQL_STRING)); + ASSERT_TRUE(db.performQuery(CREATE_ALERT_ASSETS_TABLE_SQL_STRING)); + ASSERT_TRUE(db.performQuery(CREATE_ALERT_ASSET_PLAY_ORDER_ITEMS_TABLE_SQL_STRING)); + const std::string storeToOfflineAlertV1 = "INSERT INTO " + OFFLINE_ALERTS_TABLE_NAME + " (" + + "id, token, scheduled_time_iso_8601" + ") VALUES (" + + "?, ?, ?" + ");"; + + auto storeStatement = db.createStatement(storeToOfflineAlertV1); + ASSERT_NE(storeStatement, nullptr); + int id = 1; + std::string token = "token-offline"; + std::string scheduledTime_ISO_8601 = SCHEDULED_TIME_ISO_STRING; + int boundParam = 1; + bool bindResult = storeStatement->bindIntParameter(boundParam++, id) && + storeStatement->bindStringParameter(boundParam++, token) && + storeStatement->bindStringParameter(boundParam, scheduledTime_ISO_8601); + ASSERT_TRUE(bindResult); + ASSERT_TRUE(storeStatement->step()); + storeStatement->finalize(); + ASSERT_FALSE(db.tableExists(OFFLINE_ALERTS_V2_TABLE_NAME)); + + /// data migration happens on open. + ASSERT_TRUE(m_alertStorage->open()); + ASSERT_TRUE(db.tableExists(OFFLINE_ALERTS_V2_TABLE_NAME)); + + /// verify if the offline alert has been migrated successfully from table v1 to v2. + const std::string loadOfflineAlertsV2 = "SELECT * FROM " + OFFLINE_ALERTS_V2_TABLE_NAME + ";"; + auto loadStatement = db.createStatement(loadOfflineAlertsV2); + ASSERT_NE(loadStatement, nullptr); + ASSERT_TRUE(loadStatement->step()); + while (SQLITE_ROW == loadStatement->getStepResult()) { + int numberOfColumns = loadStatement->getColumnCount(); + for (int i = 0; i < numberOfColumns; i++) { + std::string columnName = loadStatement->getColumnName(i); + + if ("id" == columnName) { + ASSERT_EQ(id, loadStatement->getColumnInt(i)); + } else if ("token" == columnName) { + ASSERT_EQ(token, loadStatement->getColumnText(i)); + } else if ("scheduled_time_iso_8601" == columnName) { + ASSERT_EQ(scheduledTime_ISO_8601, loadStatement->getColumnText(i)); + } + } + loadStatement->step(); + } + loadStatement->finalize(); + db.close(); +} + +/** + * Test store and load alerts. + */ +TEST_F(SQLiteAlertStorageTest, test_storeAndLoadAlerts) { + setUpDatabase(); + auto alarm = createAlert(TEST_ALERT_TYPE_ALARM); + auto timer = createAlert(TEST_ALERT_TYPE_TIMER); + auto reminder = createAlert(TEST_ALERT_TYPE_REMINDER); + /// store alerts + ASSERT_TRUE(m_alertStorage->store(alarm)); + ASSERT_TRUE(m_alertStorage->store(timer)); + ASSERT_TRUE(m_alertStorage->store(reminder)); + /// load alerts + std::vector> alerts; + m_alertStorage->load(&alerts, nullptr); + /// verify + ASSERT_EQ(static_cast(alerts.size()), 3); + for (auto& alert : alerts) { + if (TEST_ALERT_TYPE_ALARM == alert->getTypeName()) { + Alert::StaticData staticData; + Alert::DynamicData dynamicData; + alert->getAlertData(&staticData, &dynamicData); + ASSERT_EQ(dynamicData.timePoint.getTime_ISO_8601(), SCHEDULED_TIME_ISO_STRING_ALARM); + ASSERT_EQ(dynamicData.loopCount, 1); + ASSERT_EQ(dynamicData.originalTime, ORIGINAL_TIME_ALARM); + ASSERT_EQ(staticData.token, TOKEN_ALARM); + } else if (TEST_ALERT_TYPE_TIMER == alert->getTypeName()) { + Alert::StaticData staticData; + Alert::DynamicData dynamicData; + alert->getAlertData(&staticData, &dynamicData); + ASSERT_EQ(dynamicData.timePoint.getTime_ISO_8601(), SCHEDULED_TIME_ISO_STRING_TIMER); + ASSERT_EQ(dynamicData.loopCount, 2); + ASSERT_EQ(dynamicData.label, LABEL_TIMER); + ASSERT_EQ(staticData.token, TOKEN_TIMER); + } else if (TEST_ALERT_TYPE_REMINDER == alert->getTypeName()) { + Alert::StaticData staticData; + Alert::DynamicData dynamicData; + alert->getAlertData(&staticData, &dynamicData); + ASSERT_EQ(dynamicData.timePoint.getTime_ISO_8601(), SCHEDULED_TIME_ISO_STRING_REMINDER); + ASSERT_EQ(dynamicData.loopCount, 3); + ASSERT_EQ(dynamicData.label, LABEL_REMINDER); + ASSERT_EQ(dynamicData.originalTime, ORIGINAL_TIME_REMINDER); + ASSERT_EQ(staticData.token, TOKEN_REMINDER); + } + } +} + +/** + * Test modify an alert. + */ +TEST_F(SQLiteAlertStorageTest, test_modifyAlerts) { + setUpDatabase(); + auto alarm = createAlert(TEST_ALERT_TYPE_ALARM); + ASSERT_TRUE(m_alertStorage->store(alarm)); + std::vector> alerts; + m_alertStorage->load(&alerts, nullptr); + ASSERT_EQ(static_cast(alerts.size()), 1); + + auto alert = alerts.back(); + Alert::DynamicData dynamicData; + Alert::StaticData staticData; + alert->getAlertData(&staticData, &dynamicData); + ASSERT_EQ(dynamicData.timePoint.getTime_ISO_8601(), SCHEDULED_TIME_ISO_STRING_ALARM); + /// update schedule time. + dynamicData.timePoint.setTime_ISO_8601(SCHEDULED_TIME_ISO_STRING); + alert->setAlertData(&staticData, &dynamicData); + ASSERT_TRUE(m_alertStorage->modify(alert)); + + /// verify the value after modification. + alerts.clear(); + m_alertStorage->load(&alerts, nullptr); + ASSERT_EQ(static_cast(alerts.size()), 1); + alert = alerts.back(); + alert->getAlertData(&staticData, &dynamicData); + ASSERT_EQ(dynamicData.timePoint.getTime_ISO_8601(), SCHEDULED_TIME_ISO_STRING); + + /// modify should fail if the alert does not exist. + alert = std::make_shared(TEST_ALERT_TYPE_ALARM); + staticData.token = "token-invalid"; + alert->setAlertData(&staticData, nullptr); + ASSERT_FALSE(m_alertStorage->modify(alert)); +} + +/** + * Test erase an alert. + */ +TEST_F(SQLiteAlertStorageTest, test_eraseAlert) { + setUpDatabase(); + auto alarm = createAlert(TEST_ALERT_TYPE_ALARM); + ASSERT_TRUE(m_alertStorage->store(alarm)); + std::vector> alerts; + m_alertStorage->load(&alerts, nullptr); + ASSERT_EQ(static_cast(alerts.size()), 1); + alerts.clear(); + + /// start to erase + ASSERT_TRUE(m_alertStorage->erase(alarm)); + m_alertStorage->load(&alerts, nullptr); + ASSERT_TRUE(alerts.empty()); +} + +/** + * Test bulkErase alerts. + */ +TEST_F(SQLiteAlertStorageTest, test_bulkEraseAlert) { + setUpDatabase(); + auto alarm = createAlert(TEST_ALERT_TYPE_ALARM); + auto timer = createAlert(TEST_ALERT_TYPE_TIMER); + auto reminder = createAlert(TEST_ALERT_TYPE_REMINDER); + + ASSERT_TRUE(m_alertStorage->store(alarm)); + ASSERT_TRUE(m_alertStorage->store(timer)); + ASSERT_TRUE(m_alertStorage->store(reminder)); + std::vector> alerts; + m_alertStorage->load(&alerts, nullptr); + ASSERT_EQ(static_cast(alerts.size()), 3); + alerts.clear(); + + /// start to bulkErase + ASSERT_TRUE(m_alertStorage->bulkErase({alarm, timer, reminder})); + m_alertStorage->load(&alerts, nullptr); + ASSERT_TRUE(alerts.empty()); +} + +/** + * Test store and load offline alerts. + */ +TEST_F(SQLiteAlertStorageTest, test_storeAndLoadOfflineAlerts) { + setUpDatabase(); + const std::string offlineToken1 = "token-offline1"; + const std::string offlineToken2 = "token-offline2"; + const std::string offlineToken3 = "token-offline3"; + /// store offline alerts. + ASSERT_TRUE( + m_alertStorage->storeOfflineAlert(offlineToken1, SCHEDULED_TIME_ISO_STRING, SCHEDULED_TIME_ISO_STRING_ALARM)); + ASSERT_TRUE(m_alertStorage->storeOfflineAlert( + offlineToken2, SCHEDULED_TIME_ISO_STRING_ALARM, SCHEDULED_TIME_ISO_STRING_TIMER)); + ASSERT_TRUE(m_alertStorage->storeOfflineAlert( + offlineToken3, SCHEDULED_TIME_ISO_STRING_ALARM, SCHEDULED_TIME_ISO_STRING_REMINDER)); + + rapidjson::Document offlineAlerts(rapidjson::kObjectType); + auto& allocator = offlineAlerts.GetAllocator(); + rapidjson::Value alertContainer(rapidjson::kArrayType); + + /// load offline alerts. + ASSERT_TRUE(m_alertStorage->loadOfflineAlerts(&alertContainer, allocator)); + ASSERT_EQ(static_cast(alertContainer.GetArray().Size()), 3); + for (const auto& alert : alertContainer.GetArray()) { + std::string token = ""; + std::string scheduledTime_ISO_8601 = ""; + std::string event_time_iso_8601 = ""; + ASSERT_TRUE(avsCommon::utils::json::jsonUtils::retrieveValue(alert, "token", &token)); + ASSERT_TRUE(avsCommon::utils::json::jsonUtils::retrieveValue(alert, "scheduledTime", &scheduledTime_ISO_8601)); + ASSERT_TRUE(avsCommon::utils::json::jsonUtils::retrieveValue(alert, "eventTime", &event_time_iso_8601)); + if (offlineToken1 == token) { + ASSERT_EQ(scheduledTime_ISO_8601, SCHEDULED_TIME_ISO_STRING); + ASSERT_EQ(event_time_iso_8601, SCHEDULED_TIME_ISO_STRING_ALARM); + continue; + } + if (offlineToken2 == token) { + ASSERT_EQ(scheduledTime_ISO_8601, SCHEDULED_TIME_ISO_STRING_ALARM); + ASSERT_EQ(event_time_iso_8601, SCHEDULED_TIME_ISO_STRING_TIMER); + continue; + } + if (offlineToken3 == token) { + ASSERT_EQ(scheduledTime_ISO_8601, SCHEDULED_TIME_ISO_STRING_ALARM); + ASSERT_EQ(event_time_iso_8601, SCHEDULED_TIME_ISO_STRING_REMINDER); + continue; + } + } +} + +/** + * Test erase offline alerts. + */ +TEST_F(SQLiteAlertStorageTest, test_eraseOfflineAlerts) { + setUpDatabase(); + /// store offline alerts. + ASSERT_TRUE(m_alertStorage->storeOfflineAlert( + "token-offline1", SCHEDULED_TIME_ISO_STRING, SCHEDULED_TIME_ISO_STRING_ALARM)); + ASSERT_TRUE(m_alertStorage->storeOfflineAlert( + "token-offline2", SCHEDULED_TIME_ISO_STRING_ALARM, SCHEDULED_TIME_ISO_STRING_TIMER)); + + /// erase the offline alert. + ASSERT_TRUE(m_alertStorage->eraseOffline("token-offline1", 1)); + + /// load offline alerts. + rapidjson::Document offlineAlerts(rapidjson::kObjectType); + auto& allocator = offlineAlerts.GetAllocator(); + rapidjson::Value alertContainer(rapidjson::kArrayType); + ASSERT_TRUE(m_alertStorage->loadOfflineAlerts(&alertContainer, allocator)); + ASSERT_EQ(static_cast(alertContainer.GetArray().Size()), 1); + + /// erase the offline alert. + alertContainer.Clear(); + ASSERT_TRUE(m_alertStorage->eraseOffline("token-offline2", 2)); + ASSERT_TRUE(m_alertStorage->loadOfflineAlerts(&alertContainer, allocator)); + ASSERT_TRUE(alertContainer.GetArray().Empty()); +} + +/** + * Test clear databse. + */ +TEST_F(SQLiteAlertStorageTest, test_clearDatabase) { + setUpDatabase(); + alexaClientSDK::storage::sqliteStorage::SQLiteDatabase db(TEST_DATABASE_FILE_NAME); + ASSERT_TRUE(db.open()); + /// include legacy tables. + ASSERT_TRUE(db.performQuery(CREATE_ALERTS_V2_TABLE_SQL_STRING)); + ASSERT_TRUE(db.performQuery(CREATE_OFFLINE_ALERTS_V1_TABLE_SQL_STRING)); + + /// store alerts. + const std::string offlineAlertToken = "token-offline"; + ASSERT_TRUE(m_alertStorage->storeOfflineAlert( + offlineAlertToken, SCHEDULED_TIME_ISO_STRING, SCHEDULED_TIME_ISO_STRING_ALARM)); + auto alarm = createAlert(TEST_ALERT_TYPE_ALARM); + ASSERT_TRUE(m_alertStorage->store(alarm)); + + /// check alerts exist. + ASSERT_TRUE(alertExists(&db, OFFLINE_ALERTS_TABLE_NAME, offlineAlertToken)); + ASSERT_TRUE(alertExists(&db, OFFLINE_ALERTS_V2_TABLE_NAME, offlineAlertToken)); + ASSERT_TRUE(alertExists(&db, ALERTS_V2_TABLE_NAME, TOKEN_ALARM)); + ASSERT_TRUE(alertExists(&db, ALERTS_V3_TABLE_NAME, TOKEN_ALARM)); + /// clear database. + ASSERT_TRUE(m_alertStorage->clearDatabase()); + ASSERT_TRUE(isTableEmpty(&db, ALERTS_V2_TABLE_NAME)); + ASSERT_TRUE(isTableEmpty(&db, ALERTS_V3_TABLE_NAME)); + ASSERT_TRUE(isTableEmpty(&db, OFFLINE_ALERTS_TABLE_NAME)); + ASSERT_TRUE(isTableEmpty(&db, OFFLINE_ALERTS_V2_TABLE_NAME)); + db.close(); +} + +} // namespace test +} // namespace acsdkAlerts +} // namespace alexaClientSDK diff --git a/capabilities/Alerts/acsdkAlertsInterfaces/include/acsdkAlertsInterfaces/AlertObserverInterface.h b/capabilities/Alerts/acsdkAlertsInterfaces/include/acsdkAlertsInterfaces/AlertObserverInterface.h index 2e47a53049..fde258627d 100644 --- a/capabilities/Alerts/acsdkAlertsInterfaces/include/acsdkAlertsInterfaces/AlertObserverInterface.h +++ b/capabilities/Alerts/acsdkAlertsInterfaces/include/acsdkAlertsInterfaces/AlertObserverInterface.h @@ -13,10 +13,14 @@ * permissions and limitations under the License. */ -#ifndef ACSDKALERTSINTERFACES_ALERTOBSERVERINTERFACE_H_ -#define ACSDKALERTSINTERFACES_ALERTOBSERVERINTERFACE_H_ +#ifndef ALEXA_CLIENT_SDK_ACSDKALERTSINTERFACES_INCLUDE_ACSDKALERTSINTERFACES_ALERTOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_ACSDKALERTSINTERFACES_INCLUDE_ACSDKALERTSINTERFACES_ALERTOBSERVERINTERFACE_H_ +#include #include +#include + +#include namespace alexaClientSDK { namespace acsdkAlertsInterfaces { @@ -26,6 +30,30 @@ namespace acsdkAlertsInterfaces { */ class AlertObserverInterface { public: + /// The minimum value for the field in @c OriginalTime. + static const int ORIGINAL_TIME_FIELD_MIN = 0; + /// The maximum value for the hour field in @c OriginalTime. + static const int ORIGINAL_TIME_HOUR_MAX = 23; + /// The maximum value for the minute field in @c OriginalTime. + static const int ORIGINAL_TIME_MINUTE_MAX = 59; + /// The maximum value for the second field in @c OriginalTime. + static const int ORIGINAL_TIME_SECOND_MAX = 59; + /// The maximum value for the millisecond field in @c OriginalTime. + static const int ORIGINAL_TIME_MILLISECOND_MAX = 999; + + /** + * Check whether a value is within the bounds. + * + * @param value The value to check. + * @param minVal The minimum value. + * @param maxVal The maximum value. + * @param true if the value is within the bounds, false otherwise. + */ + template + static bool withinBounds(T value, T minVal, T maxVal) { + return value >= minVal && value <= maxVal; + } + /** * An enum class to represent the states an alert can be in. */ @@ -54,6 +82,138 @@ class AlertObserverInterface { SCHEDULED_FOR_LATER }; + /** + * An enum class to represent the type of an alert. + */ + enum class Type { + /// The alarm type. + ALARM, + /// The timer type. + TIMER, + /// The reminder type. + REMINDER + }; + + /** + * Struct that represents the local time in current timezone when the alert was originally set. If the timezone is + * updated after the alert was set, the value of @c OriginalTime remains unchanged. Users have to check if all + * alerts match the desired times after a timezone change. Also, snoozing or deferring an alert will not modify + * the value of this struct. This struct is supposed to be read only by the alert observers and can be used for + * display purpose on a screen-based device, e.g. displaying the original time on a ringing screen for an alarm. + * For example, when a user says "Alexa, set an alarm at 5PM"(PST timezone), the originalTime string included in + * SetAlert directive would be "17:00:00.000" and the corresponding scheduledTime in ISO 8601 format is + * "2021-08-08T01:00:00+0000"(UTC timezone). When the alert is triggered and snoozed for 9 minutes, the + * scheduledTime would be updated to "2021-08-08T01:09:00+0000" (UTC timezone) and the originalTime remains + * unchanged as "17:00:00.000". + * The @c OriginalTime should not be used to infer the date of the alert. The "scheduledTime" in @c AlertInfo should + * be used for this purpose. The original time is an optional field in SetAlert directive and currently used for + * ALARM and REMINDER types. + */ + struct OriginalTime { + /** + * Constructor. All fields will be set to ORIGINAL_TIME_FIELD_MIN if an invalid value is provided. + * + * @param hour Hour within [ORIGINAL_TIME_FIELD_MIN, ORIGINAL_TIME_HOUR_MAX] + * @param minute Minute within [ORIGINAL_TIME_FIELD_MIN, ORIGINAL_TIME_MINUTE_MAX] + * @param second Second within [ORIGINAL_TIME_FIELD_MIN, ORIGINAL_TIME_SECOND_MAX] + * @param millisecond Millisecond within [ORIGINAL_TIME_FIELD_MIN, ORIGINAL_TIME_MILLISECOND_MAX] + */ + OriginalTime( + int hour = ORIGINAL_TIME_FIELD_MIN, + int minute = ORIGINAL_TIME_FIELD_MIN, + int second = ORIGINAL_TIME_FIELD_MIN, + int millisecond = ORIGINAL_TIME_FIELD_MIN) : + hour{hour}, + minute{minute}, + second{second}, + millisecond{millisecond} { + if (!withinBounds(hour, ORIGINAL_TIME_FIELD_MIN, ORIGINAL_TIME_HOUR_MAX) || + !withinBounds(minute, ORIGINAL_TIME_FIELD_MIN, ORIGINAL_TIME_MINUTE_MAX) || + !withinBounds(second, ORIGINAL_TIME_FIELD_MIN, ORIGINAL_TIME_SECOND_MAX) || + !withinBounds(millisecond, ORIGINAL_TIME_FIELD_MIN, ORIGINAL_TIME_MILLISECOND_MAX)) { + this->hour = ORIGINAL_TIME_FIELD_MIN; + this->minute = ORIGINAL_TIME_FIELD_MIN; + this->second = ORIGINAL_TIME_FIELD_MIN; + this->millisecond = ORIGINAL_TIME_FIELD_MIN; + } + } + + /** + * Operator overload to compare two @c OriginalTime objects. + * + * @param rhs The right hand side of the == operation. + * @return Whether or not this instance and @c rhs are equivalent. + */ + bool operator==(const OriginalTime& rhs) const { + return hour == rhs.hour && minute == rhs.minute && second == rhs.second && millisecond == rhs.millisecond; + } + + /// Hours in [0-23]. + int hour; + /// Minutes in [0-59]. + int minute; + /// Seconds in [0-59]. + int second; + /// Milliseconds in [0-999]. + int millisecond; + }; + + /** + * This struct includes the information of an alert. + * Note that attributes originalTime and label reflect the optional fields in the SetAlert directive. + * @see https://developer.amazon.com/en-US/docs/alexa/alexa-voice-service/alerts.html + * Refer to documentation of @c OriginalTime struct for details about originalTime. Attribute label includes the + * content of the alert. For example, when the user creates a named timer "Alexa, set a coffee timer for 3 minutes", + * "coffee" would be the label of the timer. When the user create a regular timer "Alexa, set a timer for 3 + * minutes", the label field would be empty in SetAlert directive. The label attribute can be used for display + * purpose on a screen-based device, e.g. showing the label of the timer on an alert ringing screen. + */ + struct AlertInfo { + /** + * Constructor. + * + * @param token An opaque token that uniquely identifies the alert. + * @param type The type of the alert. + * @param state The state of the alert. + * @param scheduledTime The UTC timestamp for when the alert is scheduled. + * @param originalTime An optional @c OriginalTime for the local time when the alert was originally set. + * @param label An optional label for the content of an alert. + * @param reason The reason for the state change. + */ + AlertInfo( + const std::string& token, + const Type type, + const State state, + const std::chrono::system_clock::time_point& scheduledTime, + const avsCommon::utils::Optional& originalTime = avsCommon::utils::Optional(), + const avsCommon::utils::Optional& label = avsCommon::utils::Optional(), + const std::string& reason = "") : + token{token}, + type{type}, + state{state}, + scheduledTime{scheduledTime}, + originalTime{originalTime}, + label{label}, + reason{reason} { + } + + /// An opaque token that uniquely identifies the alert. + std::string token; + /// The type of the alert. + Type type; + /// The state of the alert. + State state; + /// UTC timestamp for when the alert is scheduled. + std::chrono::system_clock::time_point scheduledTime; + /// An optional @c OriginalTime for the local time when the alert was originally set. This value remains + /// unchanged when the alert is snoozed. + avsCommon::utils::Optional originalTime; + /// An optional label for the content of an alert. + avsCommon::utils::Optional label; + /// The reason for the state change. + std::string reason; + }; + /** * Destructor. */ @@ -62,16 +222,9 @@ class AlertObserverInterface { /** * A callback function to notify an object that an alert has updated its state. * - * @param alertToken The AVS token of the alert. - * @param alertType The type of the alert. - * @param state The state of the alert. - * @param reason The reason for the state change. + * @param alertInfo The information of the updated alert. */ - virtual void onAlertStateChange( - const std::string& alertToken, - const std::string& alertType, - State state, - const std::string& reason = "") = 0; + virtual void onAlertStateChange(const AlertInfo& alertInfo) = 0; /** * Convert a @c State to a @c std::string. @@ -80,6 +233,22 @@ class AlertObserverInterface { * @return The string representation of @c state. */ static std::string stateToString(State state); + + /** + * Convert a @c Type to a @c std::string. + * + * @param type The @c Type to convert. + * @return The string representation of a @c Type. + */ + static std::string typeToString(Type type); + + /** + * Convert a @c OriginalTime to a @c std::string. + * + * @param type The @c OriginalTime to convert. + * @return The string representation of a @c OriginalTime. + */ + static std::string originalTimeToString(const OriginalTime& originalTime); }; inline std::string AlertObserverInterface::stateToString(State state) { @@ -107,7 +276,27 @@ inline std::string AlertObserverInterface::stateToString(State state) { case State::SCHEDULED_FOR_LATER: return "SCHEDULED_FOR_LATER"; } - return "unknown State"; + return "Unknown State"; +} + +inline std::string AlertObserverInterface::typeToString(Type type) { + switch (type) { + case Type::ALARM: + return "ALARM"; + case Type::TIMER: + return "TIMER"; + case Type::REMINDER: + return "REMINDER"; + } + return "Unknown Type"; +} + +inline std::string AlertObserverInterface::originalTimeToString(const OriginalTime& originalTime) { + std::stringstream ss; + ss << std::setfill('0') << std::setw(2) << originalTime.hour << ":" << std::setfill('0') << std::setw(2) + << originalTime.minute << ":" << std::setfill('0') << std::setw(2) << originalTime.second << "." + << std::setfill('0') << std::setw(3) << originalTime.millisecond; + return ss.str(); } /** @@ -121,7 +310,29 @@ inline std::ostream& operator<<(std::ostream& stream, const AlertObserverInterfa return stream << AlertObserverInterface::stateToString(state); } +/** + * Write a @c Type value to an @c ostream. + * + * @param stream The stream to write the value to. + * @param state The @c Type 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 AlertObserverInterface::Type& type) { + return stream << AlertObserverInterface::typeToString(type); +} + +/** + * Write a @c OriginalTime value to an @c ostream. + * + * @param stream The stream to write the value to. + * @param state The @c OriginalTime 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 AlertObserverInterface::OriginalTime& originalTime) { + return stream << AlertObserverInterface::originalTimeToString(originalTime); +} + } // namespace acsdkAlertsInterfaces } // namespace alexaClientSDK -#endif // ACSDKALERTSINTERFACES_ALERTOBSERVERINTERFACE_H_ +#endif // ALEXA_CLIENT_SDK_ACSDKALERTSINTERFACES_INCLUDE_ACSDKALERTSINTERFACES_ALERTOBSERVERINTERFACE_H_ diff --git a/capabilities/AssetManager/.clang-format b/capabilities/AssetManager/.clang-format new file mode 100644 index 0000000000..f6ea4f5839 --- /dev/null +++ b/capabilities/AssetManager/.clang-format @@ -0,0 +1,69 @@ +--- +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: false +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +FixNamespaceComments: true +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Single +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyReturnTypeOnItsOwnLine: 20000 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 4 +UseTab: Never +... + diff --git a/capabilities/AssetManager/CMakeLists.txt b/capabilities/AssetManager/CMakeLists.txt new file mode 100644 index 0000000000..95f6380b4c --- /dev/null +++ b/capabilities/AssetManager/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.1) + +add_subdirectory("acsdkAssetManagerClientInterfaces") + +if (ASSET_MANAGER) + add_subdirectory("acsdkAssetManagerClient") + add_subdirectory("acsdkAssetManager") +endif() diff --git a/capabilities/AssetManager/acsdkAssetManager/CMakeLists.txt b/capabilities/AssetManager/acsdkAssetManager/CMakeLists.txt new file mode 100644 index 0000000000..5aa7e03af3 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkAssetManager LANGUAGES CXX) + +if (DEFINED AVS_CMAKE_BUILD) + include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +endif () + +add_subdirectory("src") +add_subdirectory("test") diff --git a/capabilities/AssetManager/acsdkAssetManager/include/acsdkAssetManager/AssetManager.h b/capabilities/AssetManager/acsdkAssetManager/include/acsdkAssetManager/AssetManager.h new file mode 100644 index 0000000000..2f39c1f765 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/include/acsdkAssetManager/AssetManager.h @@ -0,0 +1,192 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_ASSETMANAGER_H_ +#define ACSDKASSETMANAGER_ASSETMANAGER_H_ + +#include +#include +#include +#include +#include +#include + +#include "acsdkAssetManager/UrlAllowListWrapper.h" +#include "acsdkAssetsInterfaces/Communication/AmdCommunicationInterface.h" +#include "acsdkDavsClient/DavsClient.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +class RequesterFactory; +class StorageManager; +class Requester; + +enum class IdleState { ACTIVE = 0, IDLE = 1 }; + +class AssetManager + : public std::enable_shared_from_this + , public acsdkCommunicationInterfaces::FunctionInvokerInterface { +public: + /** + * Creates a a new Asset Manager with a davs client handle and base directory to work off of. + * + * @param communicationHandler REQUIRED, used to communicate the values of properties and functions + * @param davsClient REQUIRED, used to register artifact and communicate with DAVS. + * @param artifactsDirectory REQUIRED, base directory where the artifacts are to be stored. + * @param authDelegate REQUIRED, the Authentication Delegate to generate the authentication token. + * @param allowList REQUIRED, URL allow list wrapper used for managing downloads from URLs. + * @return NULLABLE, pointer to new AssetManager, null if failed. + */ + static std::shared_ptr create( + const std::shared_ptr& communicationHandler, + const std::shared_ptr& davsClient, + const std::string& artifactsDirectory, + const std::shared_ptr& authDelegate, + const std::shared_ptr& allowList); + + ~AssetManager() override; + + /** + * Requests a creation of a new requester if it does not already exists based off a json request. + * Use an existing requester if it matches the json request. + * + * @param request REQUIRED, a request that represents an artifact in DAVS. + * @return weather the operation was successful. + */ + bool downloadArtifact(const std::shared_ptr& request); + + /** Queues up a call to downloadArtifact in an executor. Returns immediately. */ + inline void queueDownloadArtifact(const std::shared_ptr& request) { + m_executor.submit([this, request] { downloadArtifact(request); }); + } + + /** Takes string and tries to make it a ArtifactRequest + * Queues up a call to downloadArtifact in an executor. + * Returns immediately. + * @param requestString string that represents the ArtifactRequest + * @return true, if successfully able to queue up call, false otherwise + */ + bool queueDownloadArtifact(const std::string& requestString); + + /** + * Requests the deletion and removal of an existing artifact by deleting its requester. + * + * @param summaryString REQUIRED, summary of an existing artifact. + */ + void deleteArtifact(const std::string& summaryString); + + /** Queues up a call to deleteArtifact in an executor. Returns immediately. */ + inline void queueDeleteArtifact(const std::string& summaryString) { + m_executor.submit([this, summaryString] { deleteArtifact(summaryString); }); + } + + /** + * Handles the pending update for a specific requester given its summary string. + * + * @param summaryString for an artifact request that maps to a requester handling the update. + * @param acceptUpdate weather to accept the update or reject it. + */ + void handleUpdate(const std::string& summaryString, bool acceptUpdate); + + /** Queues up a call to handleUpdate in an executor. Returns immediately. */ + inline void queueHandleUpdate(const std::string& summaryString, bool acceptUpdate) { + m_executor.submit([this, summaryString, acceptUpdate] { handleUpdate(summaryString, acceptUpdate); }); + } + + /** + * Goes through the available requesters and deletes unused requesters and their artifacts based on used time and + * priority. + * @note this operation could take a while as it goes through and deletes artifacts until the requested amount is + * satisfied. + * + * @param requestedAmount size in bytes that is needed to be cleared. + * @return true if the requested amount was freed up, false otherwise. + */ + bool freeUpSpace(size_t requestedAmount); + + /** Queues up a call to freeUpSpace in an executor. Returns immediately. */ + inline void queueFreeUpSpace(size_t requestedAmount) { + m_executor.submit([this, requestedAmount] { freeUpSpace(requestedAmount); }); + } + + /** + * Callback method when application changes idle state. + * @param value, int value that will be parsed to a bool + */ + void onIdleChanged(int value); + + /** + * @return Get the current budget in MB + */ + size_t getBudget(); + + /** + * This method sets the budget for asset manager in megabytes. + * If the new budget is set to a number less than the current data stored + * asset manager will attempt to clear as many artifacts as possible to be + * within the threshold. + * @param valueMB, int the number of MB for the new budget. + */ + void setBudget(uint32_t valueMB); + + // override functionInvokerInterface for download and delete. + bool functionToBeInvoked(const std::string& name, std::string arg) override; + +private: + explicit AssetManager( + std::shared_ptr communicationHandler, + std::shared_ptr davsClient, + const std::string& baseDirectory, + std::shared_ptr authDelegate, + std::shared_ptr allowUrlList); + + /** + * Initializes the directory structure and initializes any downloaded requesters and artifacts. + * + * @return true if the operation succeeded and the directory structure was initialized. + */ + bool init(); + + /** + * Searches an existing artifact list for an artifact handling a given request. + * + * @param request REQUIRED, request used to identify an artifact. + * @return NULLABLE, an existing artifact if found, null otherwise. + */ + std::shared_ptr findRequesterLocked( + const std::shared_ptr& request) const; + +private: + const std::shared_ptr m_communicationHandler; + const std::shared_ptr m_davsClient; + const std::string m_resourcesDirectory; + const std::string m_requestsDirectory; + const std::string m_urlTmpDirectory; + const std::shared_ptr m_authDelegate; + const std::shared_ptr m_urlAllowList; + std::unique_ptr m_requesterFactory; + alexaClientSDK::avsCommon::utils::threading::Executor m_executor; + std::mutex m_requestersMutex; + std::unordered_set> m_requesters; + std::shared_ptr m_storageManager; +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_ASSETMANAGER_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/include/acsdkAssetManager/UrlAllowListWrapper.h b/capabilities/AssetManager/acsdkAssetManager/include/acsdkAssetManager/UrlAllowListWrapper.h new file mode 100644 index 0000000000..086e5aae0a --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/include/acsdkAssetManager/UrlAllowListWrapper.h @@ -0,0 +1,84 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_URLALLOWLISTWRAPPER_H_ +#define ACSDKASSETMANAGER_URLALLOWLISTWRAPPER_H_ +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +class UrlAllowListWrapper { +public: + /** + * Creates a UrlAllowListWrapper used to define which urls are allowed + * @param newAllowList The list of allowed urls. + * @param allowAllUrls, if we will allow all the urls to be downloaded from. defaults to false + * @return Nullable, pointer to a new UrlAllowListWrapper, null if newAllowList is invalid. + */ + static std::shared_ptr create( + std::vector newAllowList, + bool allowAllUrls = false); + + /** + * Checks to see if the url is allowed to be downloaded from. + * @param url, the url that we want to check if we can download from. + * @return true, if url is in the allow list, false otherwise. + */ + bool isUrlAllowed(const std::string& url); + + /** + * Set the allow list to a new list. + * @param newAllowList, the new list that we will allow downloads from. + */ + void setUrlAllowList(std::vector newAllowList); + + /** + * Add a new url to the allow list. + * @param url, the url that we want to add to the allow list. + */ + void addUrlToAllowList(std::string url); + + /** + * Set the bool flag to allow all urls + * @param allow, if we are going to allow all urls or not. + * @return true, if we are able to set allowAllUrls. False otherwise + */ + bool allowAllUrls(bool allow); + + ~UrlAllowListWrapper() = default; + +private: + UrlAllowListWrapper(std::vector newAllowList, bool allowAllUrls) : + m_allowList(std::move(newAllowList)), + m_allowAllUrls(allowAllUrls) { + } + + /// The stored allow list of urls. + std::vector m_allowList; + /// The mutex that protects reads and writes to the allow list. + std::mutex m_allowListMutex; + /// The flag that tells us if we will download all urls + bool m_allowAllUrls; +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_URLALLOWLISTWRAPPER_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/src/AssetManager.cpp b/capabilities/AssetManager/acsdkAssetManager/src/AssetManager.cpp new file mode 100644 index 0000000000..a6292acb69 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/AssetManager.cpp @@ -0,0 +1,336 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetManager/AssetManager.h" + +#include +#include + +#include "RequestFactory.h" +#include "RequesterFactory.h" +#include "StorageManager.h" +#include "acsdkAssetsCommon/AmdMetricWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; +using namespace chrono; +using namespace davs; +using namespace client; +using namespace common; +using namespace commonInterfaces; +using namespace alexaClientSDK::avsCommon::utils; +using namespace alexaClientSDK::avsCommon::utils::metrics; +using namespace alexaClientSDK::avsCommon::sdkInterfaces; + +static const auto s_metrics = AmdMetricsWrapper::creator("assetManager"); + +/// String to identify log entries originating from this file. +static const std::string LOGGER_TAG{"AssetManager"}; + +/** + * 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(LOGGER_TAG, event) + +shared_ptr AssetManager::create( + const shared_ptr& communicationHandler, + const shared_ptr& davsClient, + const string& artifactsDirectory, + const shared_ptr& authDelegate, + const shared_ptr& allowUrlList) { + if (communicationHandler == nullptr) { + ACSDK_CRITICAL(LX("create").m("Null Communication Handler")); + return nullptr; + } + if (davsClient == nullptr) { + ACSDK_CRITICAL(LX("create").m("Null Davs Client")); + return nullptr; + } + if (artifactsDirectory.empty() || artifactsDirectory == "/") { + ACSDK_CRITICAL(LX("create").m("Invalid artifacts home directory")); + return nullptr; + } + if (!filesystem::makeDirectory(artifactsDirectory)) { + ACSDK_CRITICAL( + LX("create").m("Could not create AssetManager's base directory").d("directory", artifactsDirectory)); + + return nullptr; + } + if (authDelegate == nullptr) { + ACSDK_CRITICAL(LX("create").m("Null Auth Delegate")); + return nullptr; + } + if (allowUrlList == nullptr) { + ACSDK_CRITICAL(LX("create").m("Null Url Allow List Wrapper")); + return nullptr; + } + + auto assetManager = shared_ptr( + new AssetManager(communicationHandler, davsClient, artifactsDirectory, authDelegate, allowUrlList)); + + if (!assetManager->init()) { + ACSDK_CRITICAL(LX("create").m("Failed to initialize AssetManager")); + return nullptr; + } + + return assetManager; +} + +AssetManager::AssetManager( + shared_ptr communicationHandler, + shared_ptr davsClient, + const string& baseDirectory, + shared_ptr authDelegate, + shared_ptr allowList) : + m_communicationHandler(move(communicationHandler)), + m_davsClient(move(davsClient)), + m_resourcesDirectory(baseDirectory + "/resources"), + m_requestsDirectory(baseDirectory + "/requests"), + m_urlTmpDirectory(baseDirectory + "/urlWorkingDir"), + m_authDelegate(move(authDelegate)), + m_urlAllowList(move(allowList)) { +} + +AssetManager::~AssetManager() { + m_executor.shutdown(); +} + +bool AssetManager::init() { + if (!filesystem::makeDirectory(m_resourcesDirectory)) { + ACSDK_CRITICAL(LX("init").m("Could not make resources directory")); + return false; + } + if (!filesystem::makeDirectory(m_requestsDirectory)) { + ACSDK_CRITICAL(LX("init").m("Could not make requesters directory")); + return false; + } + + m_storageManager = StorageManager::create(m_resourcesDirectory, shared_from_this()); + if (m_storageManager == nullptr) { + ACSDK_CRITICAL(LX("init").m("Could not create Storage Manager")); + return false; + } + + m_requesterFactory = RequesterFactory::create( + m_storageManager, m_communicationHandler, m_davsClient, m_urlTmpDirectory, m_authDelegate, m_urlAllowList); + if (m_requesterFactory == nullptr) { + ACSDK_CRITICAL(LX("init").m("Could not create Requester Factory")); + return false; + } + + milliseconds latestTime(0); + auto metadataFiles = filesystem::list(m_requestsDirectory, filesystem::FileType::REGULAR_FILE); + for (const auto& metadataFile : metadataFiles) { + auto metadataFilePath = m_requestsDirectory + "/" + metadataFile; + auto requester = m_requesterFactory->createFromStorage(metadataFilePath); + if (requester != nullptr) { + ACSDK_INFO(LX("init").m("Loaded stored requester").d("requester", requester->name())); + latestTime = max(latestTime, requester->getLastUsed()); + m_requesters.emplace(move(requester)); + } else { + ACSDK_ERROR(LX("init").m("Failed to load stored requester, cleaning it up!")); + filesystem::removeAll(metadataFilePath); + } + } + + // be sure to have the storage manager erase any artifacts that got unreferenced. + m_storageManager->purgeUnreferenced(); + + // update the start time offset based on the latest requester that was stored. + Requester::START_TIME_OFFSET = latestTime; + + m_communicationHandler->registerFunction(AMD::REGISTER_PROP, shared_from_this()); + m_communicationHandler->registerFunction(AMD::REMOVE_PROP, shared_from_this()); + + return true; +} + +bool AssetManager::downloadArtifact(const shared_ptr& request) { + if (request == nullptr) { + ACSDK_ERROR(LX("downloadArtifact").m("Received null request")); + return false; + } + + lock_guard lock(m_requestersMutex); + auto requester = findRequesterLocked(request); + if (requester == nullptr) { + ACSDK_INFO(LX("downloadArtifact").m("Creating new requester").d("request", request->getSummary())); + auto metadataFilePath = m_requestsDirectory + "/" + request->getSummary(); + requester = m_requesterFactory->createFromMetadata(RequesterMetadata::create(request), metadataFilePath); + + if (requester == nullptr) { + ACSDK_ERROR(LX("downloadArtifact").m("Could not create requester").d("request", request->getSummary())); + return false; + } + + m_requesters.emplace(requester); + } else { + ACSDK_INFO(LX("downloadArtifact").m("Requester already registered").d("requester", requester->name())); + } + return requester->download(); +} + +bool AssetManager::queueDownloadArtifact(const std::string& requestString) { + auto request = RequestFactory::create(requestString); + if (request == nullptr) { + ACSDK_ERROR(LX("queueDownloadArtifact").d("Received invalid request", requestString)); + return false; + } + queueDownloadArtifact(request); + return true; +} +void AssetManager::deleteArtifact(const string& summaryString) { + ACSDK_INFO(LX("deleteArtifact").m("Deleting requester").d("requester", summaryString)); + + lock_guard lock(m_requestersMutex); + for (const auto& requester : m_requesters) { + if (requester->getArtifactRequest()->getSummary() == summaryString) { + requester->deleteAndCleanup(); + m_requesters.erase(requester); + return; + } + } + + ACSDK_ERROR(LX("deleteArtifact")); +} + +void AssetManager::handleUpdate(const std::string& summaryString, bool acceptUpdate) { + ACSDK_INFO(LX("handleUpdate") + .m("Artifact Update for requester") + .d("acceptUpdate", acceptUpdate ? "Applying" : "Rejecting") + .d("requester", summaryString)); + lock_guard lock(m_requestersMutex); + for (const auto& requester : m_requesters) { + if (requester->getArtifactRequest()->getSummary() == summaryString) { + requester->handleUpdate(acceptUpdate); + return; + } + } + + ACSDK_ERROR(LX("handleUpdate") + .m("Could not find a requester to handle update with summary") + .d("summary", summaryString)); +} + +shared_ptr AssetManager::findRequesterLocked(const shared_ptr& request) const { + if (request == nullptr) { + return nullptr; + } + + for (const auto& requester : m_requesters) { + if (*requester->getArtifactRequest() == *request) { + return requester; + } + } + return nullptr; +} + +bool AssetManager::freeUpSpace(size_t requestedAmount) { + ACSDK_DEBUG(LX("freeUpSpace").m("Requesting space").d("numberOfBytes", requestedAmount)); + if (requestedAmount == 0) { + return true; + } + + lock_guard lock(m_requestersMutex); + + // delete all nullptrs in m_requester set + for (const auto& requester : m_requesters) { + if (requester == nullptr) { + ACSDK_ERROR(LX("freeUpSpace").m("Nullptr found in m_requesters")); + s_metrics().addCounter("nullptrFoundInRequesters"); + m_requesters.erase(requester); + } + } + + vector> sortedRequesters(m_requesters.begin(), m_requesters.end()); + sort(sortedRequesters.begin(), + sortedRequesters.end(), + [](const shared_ptr& lhs, const shared_ptr& rhs) { + if (lhs->getPriority() > rhs->getPriority()) { + return true; + } + return lhs->getPriority() == rhs->getPriority() && lhs->getLastUsed() < rhs->getLastUsed(); + }); + + size_t deletedAmount = 0; + for (auto& requester : sortedRequesters) { + if (!requester->isDownloaded()) { + ACSDK_DEBUG( + LX("freeUpSpace").m("Skipping over since it's not downloaded").d("requester", requester->name())); + continue; + } + + // we've reached the end of our list and there is no more inactive sortedRequesters to delete, we've failed. + if (requester->getPriority() == Priority::ACTIVE || requester->getPriority() == Priority::PENDING_ACTIVATION) { + ACSDK_ERROR(LX("freeUpSpace").m("No more inactive requesters found, cannot delete any more artifacts")); + break; + } + + auto size = requester->deleteAndCleanup(); + auto name = requester->name(); + m_requesters.erase(requester); + deletedAmount += size; + ACSDK_INFO(LX("freeUpSpace") + .m("Deleted request and cleared up of space") + .d("name", name) + .d("space cleared", size)); + if (deletedAmount >= requestedAmount) { + ACSDK_INFO(LX("freeUpSpace") + .m("Successfully cleared") + .d("cleared bytes", deletedAmount) + .d("requested bytes", requestedAmount)); + return true; + } + } + + auto remainingAmount = requestedAmount - deletedAmount; + ACSDK_ERROR(LX("freeUpSpace").m("Could not free up enough space").d("bytes remaining", remainingAmount)); + s_metrics().addCounter(METRIC_PREFIX_ERROR("freeUpSpaceFailed")).addString("remaining", to_string(remainingAmount)); + return false; +} + +void AssetManager::onIdleChanged(int value) { + bool isIdle = (static_cast(value) != IdleState::ACTIVE); + m_davsClient->setIdleState(isIdle); +} + +size_t AssetManager::getBudget() { + return m_storageManager->getBudget(); +} + +void AssetManager::setBudget(uint32_t value) { + m_storageManager->setBudget(value); +} + +bool AssetManager::functionToBeInvoked(const std::string& name, std::string value) { + if (name == AMD::REGISTER_PROP) { + return queueDownloadArtifact(value); + } + if (name == AMD::REMOVE_PROP) { + deleteArtifact(value); + return true; + } + ACSDK_ERROR(LX("functionToBeInvoked").m("Invalid function name").d("name", name)); + return false; +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/AssetManager/acsdkAssetManager/src/CMakeLists.txt b/capabilities/AssetManager/acsdkAssetManager/src/CMakeLists.txt new file mode 100644 index 0000000000..6bb47f16f1 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/CMakeLists.txt @@ -0,0 +1,34 @@ +add_definitions("-DACSDK_LOG_MODULE=AssetManager") + +set(acsdkAssetManager_SOURCES + AssetManager.cpp + DavsRequester.cpp + Requester.cpp + RequesterFactory.cpp + RequesterMetadata.cpp + RequestFactory.cpp + Resource.cpp + StorageManager.cpp + UrlAllowListWrapper.cpp + UrlRequester.cpp + ) +set(acsdkAssetManager_INCLUDES + ${acsdkAssetManager_SOURCE_DIR}/include + ) +set(acsdkAssetManager_LIBRARIES + AVSCommon + acsdkAssetManagerClient + ) + +add_library(acsdkAssetManager ${acsdkAssetManager_SOURCES}) +target_include_directories(acsdkAssetManager PUBLIC ${acsdkAssetManager_INCLUDES}) +target_link_libraries(acsdkAssetManager ${acsdkAssetManager_LIBRARIES} acsdkDavsClient) + +# install target +asdk_install() + +# Setup the Testing library +add_library(acsdkAssetManagerForTesting ${acsdkAssetManager_SOURCES}) +target_include_directories(acsdkAssetManagerForTesting PUBLIC ${acsdkAssetManager_INCLUDES}) +target_link_libraries(acsdkAssetManagerForTesting ${acsdkAssetManager_LIBRARIES} acsdkDavsClientForTesting) +target_compile_definitions(acsdkAssetManagerForTesting PUBLIC UNIT_TEST=1) diff --git a/capabilities/AssetManager/acsdkAssetManager/src/DavsRequester.cpp b/capabilities/AssetManager/acsdkAssetManager/src/DavsRequester.cpp new file mode 100644 index 0000000000..3a0877b149 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/DavsRequester.cpp @@ -0,0 +1,239 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "DavsRequester.h" + +#include +#include +#include + +#include "acsdkAssetsCommon/AmdMetricWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; +using namespace chrono; +using namespace client; +using namespace common; +using namespace commonInterfaces; +using namespace alexaClientSDK::avsCommon::utils; +using namespace alexaClientSDK::avsCommon::utils::error; + +static const auto s_metrics = AmdMetricsWrapper::creator("davsRequester"); + +/// String to identify log entries originating from this file. +static const std::string TAG{"DavsRequester"}; + +/** + * 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) + +bool DavsRequester::download() { + ACSDK_INFO(LX("download").m("Requesting download").d("requester", name())); + unique_lock lock(m_eventMutex); + auto state = getState(); + if (state != State::INVALID && state != State::INIT) { + ACSDK_INFO(LX("download").m("Download is unnecessary").d("file", name()).d("Already in state", state)); + return true; + } + + auto davsRequest = static_pointer_cast(m_metadata->getRequest()); + if (getPriority() == Priority::ACTIVE) { + m_davsRequestId = m_davsClient->registerArtifact( + davsRequest, + static_pointer_cast(shared_from_this()), + static_pointer_cast(shared_from_this()), + true); + } else { + m_davsRequestId = m_davsClient->downloadOnce( + davsRequest, + static_pointer_cast(shared_from_this()), + static_pointer_cast(shared_from_this())); + } + + if (m_davsRequestId.empty()) { + ACSDK_ERROR(LX("download").m("Could not register request with DAVS Client").d("requester", name())); + handleDownloadFailureLocked(lock); + return false; + } + + if (!registerCommunicationHandlerPropsLocked()) { + ACSDK_ERROR(LX("download").m("Could not register Communication Handler properties").d("requester", name())); + handleDownloadFailureLocked(lock); + return false; + } + + setStateLocked(State::REQUESTING); + ACSDK_INFO(LX("download").m("Creating a request").d("requester", name())); + return true; +} + +size_t DavsRequester::deleteAndCleanupLocked(unique_lock& lock) { + ACSDK_DEBUG(LX("deleteAndCleanupLocked").m("Deregistering Artifact from DavsClient").d("requester", name())); + m_davsClient->deregisterArtifact(m_davsRequestId); + m_davsRequestId.clear(); + return Requester::deleteAndCleanupLocked(lock); +} + +void DavsRequester::adjustAutoUpdateBasedOnPriority(Priority newPriority) { + lock_guard lock(m_eventMutex); + if (getState() != State::LOADED) { + return; + } + if (newPriority != Priority::ACTIVE) { + m_davsClient->enableAutoUpdate(m_davsRequestId, false); + return; + } + + // if for some reason we don't have a request ID for this ACTIVE artifact, then register our request with DAVS to + // get updates. + if (m_davsRequestId.empty()) { + m_davsRequestId = m_davsClient->registerArtifact( + static_pointer_cast(m_metadata->getRequest()), + static_pointer_cast(shared_from_this()), + static_pointer_cast(shared_from_this()), + false); + + } else { + m_davsClient->enableAutoUpdate(m_davsRequestId, true); + } +} + +bool DavsRequester::checkIfOkToDownload(shared_ptr availableArtifact, size_t freeSpaceNeeded) { + ACSDK_INFO(LX("checkIfOkToDownload").d("requester", name())); + + auto newUUID = availableArtifact->getUniqueIdentifier(); + unique_lock lock(m_eventMutex); + if (m_davsRequestId.empty()) { + ACSDK_WARN(LX("checkIfOkToDownload").m("Got a check response from Davs Client even though we deregistered")); + handleDownloadFailureLocked(lock); + return false; + } + + // if the current available artifact is the same as the one stored, then there's nothing to change + if (newUUID == m_metadata->getResourceId()) { + ACSDK_INFO(LX("checkIfOkToDownload").m("Artifact is already downloaded").d("requester", name())); + return false; + } + + if (m_pendingUpdate != nullptr && newUUID == m_pendingUpdate->getId()) { + ACSDK_INFO(LX("checkIfOkToDownload").m("Artifact is already pending update").d("requester", name())); + return false; + } + + auto newResource = m_storageManager->acquireResource(newUUID); + if (newResource != nullptr) { + ACSDK_INFO(LX("checkIfOkToDownload") + .m("We already have this artifact available from another request") + .d("request", newResource->getPath())); + handleAcquiredResourceLocked(lock, newResource); + return false; + } + + m_storageReservationToken.reset(); + lock.unlock(); // don't need the lock for this and the operation can be time consuming. + // only reserve as much space as is required for the artifact (regardless if we're going to unpack it or not) + // we may need to request a bit more space later after unpacking, but it's difficult to estimate how much up front + auto reservation = m_storageManager->reserveSpace(availableArtifact->getArtifactSizeBytes()); + lock.lock(); // re-acquire the lock + if (reservation == nullptr) { + ACSDK_ERROR(LX("checkIfOkToDownload").m("Could not free up enough space").d("requester", name())); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("insufficientSpace")); + handleDownloadFailureLocked(lock); + return false; + } + m_storageReservationToken = move(reservation); + + // if we already have an artifact available and there's a new one + ACSDK_INFO(LX("checkIfOkToDownload") + .d("Requesting artifact", name()) + .d("state", (getState() == State::LOADED) ? "update" : "download")); + return true; +} + +void DavsRequester::onCheckFailure(ResultCode errorCode) { + ACSDK_ERROR(LX("onCheckFailure").m("Check failed").d("artifact", name()).d("error", static_cast(errorCode))); + + unique_lock lock(m_eventMutex); + handleDownloadFailureLocked(lock); +} + +void DavsRequester::onStart() { + ACSDK_INFO(LX("onStart").m("Download has started").d("requester", name())); + + lock_guard lock(m_eventMutex); + if (getState() == State::LOADED) { + return; + } + setStateLocked(State::DOWNLOADING); +} + +void DavsRequester::onArtifactDownloaded(shared_ptr downloadedArtifact, const string& path) { + ACSDK_INFO(LX("onArtifactDownloaded").m("Download finished").d("requester", name())); + + FinallyGuard cleanupDirectories([&] { // ok and required to pass by reference + filesystem::removeAll(path); + }); + + unique_lock lock(m_eventMutex); + if (m_davsRequestId.empty()) { + ACSDK_WARN( + LX("onArtifactDownloaded").m("Got a download response from Davs Client even though we deregistered")); + handleDownloadFailureLocked(lock); + return; + } + + auto newResource = m_storageManager->registerAndAcquireResource( + move(m_storageReservationToken), downloadedArtifact->getUniqueIdentifier(), path); + if (newResource == nullptr) { + ACSDK_ERROR(LX("onArtifactDownloaded").m("Failed to register and acquire the resource").d("resource", name())); + handleDownloadFailureLocked(lock); + return; + } + + handleAcquiredResourceLocked(lock, newResource); +} + +void DavsRequester::onDownloadFailure(ResultCode errorCode) { + ACSDK_ERROR( + LX("onDownloadFailure").m("Download failed").d("artifact", name()).d("error", static_cast(errorCode))); + unique_lock lock(m_eventMutex); + handleDownloadFailureLocked(lock); +} + +void DavsRequester::onProgressUpdate(int progress) { + ACSDK_INFO(LX("onProgressUpdate").m("Download progress").d("artifact", name()).d("progress", progress)); +} + +bool DavsRequester::validateWriteRequest(const std::string& name, int newValue) { + auto summary = m_metadata->getRequest()->getSummary(); + auto priorityProp = summary + AMD::PRIORITY_SUFFIX; + auto result = Requester::validateWriteRequest(name, newValue); + if (result && name == priorityProp) { + adjustAutoUpdateBasedOnPriority(static_cast(newValue)); + return true; + } + // Default return false. + return false; +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/AssetManager/acsdkAssetManager/src/DavsRequester.h b/capabilities/AssetManager/acsdkAssetManager/src/DavsRequester.h new file mode 100644 index 0000000000..93c30a05e8 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/DavsRequester.h @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_SRC_DAVSREQUESTER_H_ +#define ACSDKASSETMANAGER_SRC_DAVSREQUESTER_H_ + +#include "Requester.h" +#include "acsdkAssetsInterfaces/Communication/AmdCommunicationInterface.h" +#include "acsdkDavsClientInterfaces/ArtifactHandlerInterface.h" +#include "acsdkDavsClientInterfaces/DavsCheckCallbackInterface.h" +#include "acsdkDavsClientInterfaces/DavsDownloadCallbackInterface.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +class DavsRequester + : public Requester + , public davsInterfaces::DavsCheckCallbackInterface + , public davsInterfaces::DavsDownloadCallbackInterface { +public: + /** + * Deregister this artifact from DAVS Client. + */ + ~DavsRequester() override { + m_davsClient->deregisterArtifact(m_davsRequestId); + }; + + /// @name Requester Functions + /// @{ + bool download() override; + bool validateWriteRequest(const std::string& name, int newValue) override; + /// @} + + /// @name DavsCheckCallbackInterface Functions + /// @{ + bool checkIfOkToDownload( + std::shared_ptr availableArtifact, + size_t freeSpaceNeeded) override; + void onCheckFailure(commonInterfaces::ResultCode errorCode) override; + /// @} + + /// @name DavsDownloadCallbackInterface Functions + /// @{ + void onStart() override; + void onArtifactDownloaded( + std::shared_ptr downloadedArtifact, + const std::string& path) override; + void onDownloadFailure(commonInterfaces::ResultCode errorCode) override; + void onProgressUpdate(int progress) override; + /// @} + +private: + DavsRequester( + std::shared_ptr storageManager, + std::shared_ptr communicationHandler, + std::shared_ptr metadata, + const std::string& metadataFilePath, + std::shared_ptr davsClient) : + Requester( + std::move(storageManager), + std::move(communicationHandler), + std::move(metadata), + std::move(metadataFilePath)), + m_davsClient(std::move(davsClient)) { + } + /// @name Requester Functions + /// @{ + size_t deleteAndCleanupLocked(std::unique_lock& lock) override; + /// @} + + /// Set auto update for the artifact based on the current priority. + void adjustAutoUpdateBasedOnPriority(commonInterfaces::Priority newPriority); + +private: + // DAVS Client that will be used to download/update the artifacts. + const std::shared_ptr m_davsClient; + // Request ID from DAVS Client after registration. + std::string m_davsRequestId; + + friend RequesterFactory; +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_SRC_DAVSREQUESTER_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/src/RequestFactory.cpp b/capabilities/AssetManager/acsdkAssetManager/src/RequestFactory.cpp new file mode 100644 index 0000000000..0c2d56ac67 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/RequestFactory.cpp @@ -0,0 +1,153 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "RequestFactory.h" + +#include + +#include "acsdkAssetsInterfaces/DavsRequest.h" +#include "acsdkAssetsInterfaces/UrlRequest.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; +using namespace rapidjson; +using namespace commonInterfaces; + +static const std::string TAG{"RequestFactory"}; +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +static const char* ARTIFACT_TYPE = "artifactType"; +static const char* ARTIFACT_KEY = "artifactKey"; +static const char* ARTIFACT_FILTERS = "filters"; +static const char* ARTIFACT_UNPACK = "unpack"; +static const char* ARTIFACT_ENDPOINT = "endpoint"; + +static const char* ARTIFACT_URL = "url"; +static const char* ARTIFACT_FILENAME = "filename"; +static const char* ARTIFACT_CERT_PATH = "certPath"; + +shared_ptr createDavsRequest(const Document& document) { + if (document.HasParseError() || !document.IsObject()) { + ACSDK_ERROR(LX("createDavsRequest").m("Can't parse JSON Document")); + return nullptr; + } + + if (!document.HasMember(ARTIFACT_TYPE) || !document.HasMember(ARTIFACT_KEY) || + !document.HasMember(ARTIFACT_FILTERS)) { + ACSDK_WARN(LX("createDavsRequest").m("Information missing from metadata, not a proper DAVS Request")); + return nullptr; + } + + DavsRequest::FilterMap filterMap; + for (auto& filters : document[ARTIFACT_FILTERS].GetObject()) { + auto& filterSet = filterMap[filters.name.GetString()]; + if (!filters.value.IsArray()) { + filterSet.insert(filters.value.GetString()); + continue; + } + + for (auto& filter : filters.value.GetArray()) { + filterSet.insert(filter.GetString()); + } + } + + // get optional fields + auto endpoint = Region::NA; + if (document.HasMember(ARTIFACT_ENDPOINT)) { + auto& endpointMember = document[ARTIFACT_ENDPOINT]; + if (endpointMember.IsInt()) { + endpoint = static_cast(endpointMember.GetInt()); + } + } + auto unpack = false; + if (document.HasMember(ARTIFACT_UNPACK)) { + auto& unpackMember = document[ARTIFACT_UNPACK]; + if (unpackMember.IsBool()) { + unpack = unpackMember.GetBool(); + } + }; + + auto request = DavsRequest::create( + document[ARTIFACT_TYPE].GetString(), document[ARTIFACT_KEY].GetString(), filterMap, endpoint, unpack); + if (request == nullptr) { + ACSDK_ERROR(LX("createDavsRequest").m("Could not create the appropriate Artifact Request")); + return nullptr; + } + + return request; +} + +shared_ptr createUrlRequest(const Document& document) { + if (document.HasParseError() || !document.IsObject()) { + ACSDK_ERROR(LX("createUrlRequest").m("Can't parse JSON Document")); + return nullptr; + } + + if (!document.HasMember(ARTIFACT_URL) || !document.HasMember(ARTIFACT_FILENAME)) { + ACSDK_WARN(LX("createUrlRequest").m("Information missing from metadata, not a proper URL Request")); + return nullptr; + } + + auto unpack = false; + if (document.HasMember(ARTIFACT_UNPACK)) { + auto& unpackMember = document[ARTIFACT_UNPACK]; + if (unpackMember.IsBool()) { + unpack = unpackMember.GetBool(); + } + } + + auto certPath = ""; + if (document.HasMember(ARTIFACT_CERT_PATH)) { + ACSDK_INFO(LX("createUrlRequest") + .m("document using custom cert from path") + .d("Path", document[ARTIFACT_CERT_PATH].GetString())); + certPath = document[ARTIFACT_CERT_PATH].GetString(); + } + + auto request = UrlRequest::create( + document[ARTIFACT_URL].GetString(), document[ARTIFACT_FILENAME].GetString(), unpack, certPath); + if (request == nullptr) { + ACSDK_ERROR(LX("createUrlRequest").m("Could not create the appropriate Artifact Request")); + return nullptr; + } + + return request; +} + +std::shared_ptr RequestFactory::create(const Document& document) { + std::shared_ptr request = createDavsRequest(document); + if (request != nullptr) { + return request; + } + + request = createUrlRequest(document); + if (request != nullptr) { + return request; + } + + return nullptr; +} + +std::shared_ptr RequestFactory::create(const string& jsonString) { + rapidjson::Document document; + return create(document.Parse(jsonString)); +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManager/src/RequestFactory.h b/capabilities/AssetManager/acsdkAssetManager/src/RequestFactory.h new file mode 100644 index 0000000000..ac0c346f18 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/RequestFactory.h @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_SRC_REQUESTFACTORY_H_ +#define ACSDKASSETMANAGER_SRC_REQUESTFACTORY_H_ + +#include +#include +#include + +#include "acsdkAssetsInterfaces/DavsRequest.h" +#include "acsdkAssetsInterfaces/UrlRequest.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +class RequestFactory { +public: + /** + * Creates an Artifact Request from a json Document. + * + * @param document REQUIRED, document containing json information for the request. + * @return NULLABLE, a smart pointer to a request of the document if parsed correctly. + */ + static std::shared_ptr create(const rapidjson::Document& document); + + /** + * Creates an Artifact Request from a json string. + * + * @param json REQUIRED, json string containing information for the request. + * @return NULLABLE, a smart pointer to a request of the json string if parsed correctly. + */ + static std::shared_ptr create(const std::string& jsonString); +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_SRC_REQUESTFACTORY_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/src/Requester.cpp b/capabilities/AssetManager/acsdkAssetManager/src/Requester.cpp new file mode 100644 index 0000000000..4729aeaa84 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/Requester.cpp @@ -0,0 +1,326 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "Requester.h" + +#include +#include +#include + +#include "acsdkAssetManagerClient/AMD.h" +#include "acsdkAssetsCommon/AmdMetricWrapper.h" +#include "acsdkAssetsCommon/ArchiveWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; +using namespace chrono; +using namespace client; +using namespace common; +using namespace commonInterfaces; +using namespace alexaClientSDK::avsCommon::utils::timing; + +static const auto s_metrics = AmdMetricsWrapper::creator("requester"); + +/// String to identify log entries originating from this file. +static const std::string TAG{"Requester"}; + +/** + * 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) +#if UNIT_TEST == 1 +// For tests, because we don't want to wait hours for it to finish... +static constexpr auto MAX_UPDATE_NOTIFICATIONS = 2; +static constexpr auto UPDATE_RETRY_INTERVAL = milliseconds(100); +#else +static constexpr auto MAX_UPDATE_NOTIFICATIONS = 10; +static constexpr auto UPDATE_RETRY_INTERVAL = seconds(30); +#endif + +milliseconds Requester::START_TIME_OFFSET = milliseconds(0); + +Requester::Requester( + shared_ptr storageManager, + shared_ptr communicationHandler, + shared_ptr metadata, + string metadataFilePath) : + m_storageManager(move(storageManager)), + m_communicationHandler(move(communicationHandler)), + m_metadata(move(metadata)), + m_metadataFilePath(move(metadataFilePath)), + m_updateNotificationsSent(0), + m_communicationHandlerRegistered(false) { +} + +Requester::~Requester() { + ACSDK_DEBUG9(LX("~Requester").m("Destroying requester").d("requester", name())); + unique_lock lock(m_eventMutex); +} + +bool Requester::initializeFromStorage() { + lock_guard lock(m_eventMutex); + m_resource = m_storageManager->acquireResource(m_metadata->getResourceId()); + if (m_resource == nullptr) { + return false; + } + + setStateLocked(State::LOADED); + return true; +} + +bool Requester::registerCommunicationHandlerPropsLocked() { + if (m_communicationHandlerRegistered) { + return true; + } + + auto summary = m_metadata->getRequest()->getSummary(); + auto stateProp = summary + AMD::STATE_SUFFIX; + auto priorityProp = summary + AMD::PRIORITY_SUFFIX; + auto pathProp = summary + AMD::PATH_SUFFIX; + + if (!(m_stateProperty = + m_communicationHandler->registerProperty(stateProp, static_cast(State::INIT), nullptr))) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("CommunicationHandlerRegisterFailed")); + ACSDK_ERROR(LX("registerCommunicationHandlerPropsLocked").m("failed to register state property")); + return false; + } + + if (!(m_priorityProperty = m_communicationHandler->registerProperty( + priorityProp, static_cast(Priority::UNUSED), shared_from_this()))) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("CommunicationHandlerRegisterFailed")); + ACSDK_ERROR(LX("registerCommunicationHandlerPropsLocked").m("failed to register priority property")); + return false; + } + + if (!m_communicationHandler->registerFunction(pathProp, shared_from_this())) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("CommunicationHandlerRegisterFailed")); + ACSDK_ERROR(LX("registerCommunicationHandlerPropsLocked").m("failed to register path function")); + return false; + } + if (!(m_updateProperty = + m_communicationHandler->registerProperty(summary + AMD::UPDATE_SUFFIX, summary, nullptr))) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("CommunicationHandlerRegisterFailed")); + ACSDK_ERROR( + LX("registerCommunicationHandlerPropsLocked").m("failed to register path updated changed property")); + return false; + } + + m_communicationHandlerRegistered = true; + return true; +} + +void Requester::deregisterCommunicationHandlerPropsLocked(unique_lock& lock) { + if (!m_communicationHandlerRegistered) { + return; + } + + auto summary = m_metadata->getRequest()->getSummary(); + auto stateProp = summary + AMD::STATE_SUFFIX; + auto priorityProp = summary + AMD::PRIORITY_SUFFIX; + auto pathProp = summary + AMD::PATH_SUFFIX; + + lock.unlock(); + + m_communicationHandler->deregisterProperty(stateProp, m_stateProperty); + m_communicationHandler->deregisterProperty(priorityProp, m_priorityProperty); + m_communicationHandler->deregister(pathProp, shared_from_this()); + m_communicationHandler->deregisterProperty(summary + AMD::UPDATE_SUFFIX, m_updateProperty); + + lock.lock(); + + m_communicationHandlerRegistered = false; +} + +size_t Requester::deleteAndCleanupLocked(unique_lock& lock) { + ACSDK_DEBUG(LX("deleteAndCleanupLocked").m("Releasing resources").d("requester", name())); + m_storageReservationToken.reset(); + auto clearedTotal = m_storageManager->releaseResource(m_resource); + m_resource.reset(); + clearedTotal += m_storageManager->releaseResource(m_pendingUpdate); + m_pendingUpdate.reset(); + + if (clearedTotal > 0) { + ACSDK_INFO(LX("deleteAndCleanupLocked").m("Deleted resource referenced").d("requester", name())); + } + + ACSDK_DEBUG(LX("deleteAndCleanupLocked").m("Clearing metadata").d("requester", name())); + m_metadata->clear(m_metadataFilePath); + + ACSDK_DEBUG(LX("deleteAndCleanupLocked").m("Clearing properties")); + setStateLocked(State::INVALID); + // it's important to deregister the properties as soon as the deletion happens + + deregisterCommunicationHandlerPropsLocked(lock); + + ACSDK_INFO(LX("deleteAndCleanupLocked").m("Requester has been cleared").d("requester", name())); + return clearedTotal; +} + +bool Requester::handleAcquiredResourceLocked(unique_lock& lock, const shared_ptr& newResource) { + // if we already have a downloaded artifact, then announce of a pending upgrade + if (getState() == State::LOADED) { + ACSDK_INFO( + LX("handleAcquiredResourceLocked").m("Acquired an update, awaiting validation").d("artifact", name())); + m_pendingUpdate = newResource; + notifyUpdateIsAvailableLocked(lock); + return true; + } + + // resource should be null, but release this just to be on the safe side + m_storageManager->releaseResource(m_resource); + m_resource = newResource; + m_metadata->setResourceId(newResource->getId()); + updateLastUsedTimestampLocked(); + if (!m_metadata->saveToFile(m_metadataFilePath)) { + ACSDK_CRITICAL(LX("handleAcquiredResourceLocked") + .m("Failed to save the appropriate metadata for requester") + .d("requester", name())); + handleDownloadFailureLocked(lock); + return false; + } + + ACSDK_INFO(LX("handleAcquiredResourceLocked").m("Downloaded artifact is ready")); + setStateLocked(State::LOADED); + return true; +} + +void Requester::handleDownloadFailureLocked(unique_lock& lock) { + if (getState() == State::LOADED) { + ACSDK_ERROR(LX("handleDownloadFailureLocked").m("Failed to update artifact").d("requester", name())); + return; + } + + deleteAndCleanupLocked(lock); +} + +void Requester::setPriority(Priority newPriority) { + ACSDK_INFO(LX("setPriority").m("Updating priority").d("artifact", name()).d("newPriority", toString(newPriority))); + lock_guard lock(m_eventMutex); + if (m_communicationHandlerRegistered && m_priorityProperty != nullptr) { + m_priorityProperty->setValue(static_cast(newPriority)); + } +} + +string Requester::getArtifactPath() { + lock_guard lock(m_eventMutex); + if (m_resource == nullptr) { + return ""; + } + if (getState() == State::LOADED && !m_resource->getPath().empty()) { + updateLastUsedTimestampLocked(); + m_metadata->saveToFile(m_metadataFilePath); + } + return m_resource->getPath(); +} + +void Requester::updateLastUsedTimestampLocked() { + auto lastUsed = START_TIME_OFFSET + duration_cast(steady_clock::now().time_since_epoch()); + ACSDK_DEBUG(LX("updateLastUsedTimestampLocked") + .m("Changing usage timestamp") + .d("artifact", name()) + .d("New timestamp", lastUsed.count())); + m_metadata->setLastUsed(lastUsed); +} + +void Requester::notifyUpdateIsAvailableLocked(unique_lock& lock) { + if (m_pendingUpdate == nullptr) { + m_timer.stop(); + return; + } + // setup retry in case we do not get a response + if (m_updateNotificationsSent == 0) { + m_timer.start(UPDATE_RETRY_INTERVAL, Timer::PeriodType::RELATIVE, MAX_UPDATE_NOTIFICATIONS, [this] { + unique_lock lock(m_eventMutex); + notifyUpdateIsAvailableLocked(lock); + }); + } + if (++m_updateNotificationsSent > MAX_UPDATE_NOTIFICATIONS) { + ACSDK_ERROR(LX("notifyUpdateIsAvailableLocked") + .m("Tried notifying client, rejecting update") + .d("Times Notified ", MAX_UPDATE_NOTIFICATIONS) + .d("artifact", name())); + handleUpdateLocked(lock, false); + return; + } + auto updateEvent = m_metadata->getRequest()->getSummary() + AMD::UPDATE_SUFFIX; + auto newPath = m_pendingUpdate->getPath(); + m_updateProperty->setValue(newPath); +} + +void Requester::handleUpdate(bool accept) { + unique_lock lock(m_eventMutex); + handleUpdateLocked(lock, accept); +} + +void Requester::handleUpdateLocked(unique_lock& lock, bool accept) { + m_timer.stop(); + m_updateNotificationsSent = 0; + if (!accept) { + ACSDK_WARN(LX("handleUpdateLocked").m("Rejecting update").d("requester", name())); + s_metrics().addCounter("updateRejected").addString("request", name()); + m_storageManager->releaseResource(m_pendingUpdate); + m_pendingUpdate.reset(); + return; + } + + if (m_pendingUpdate == nullptr) { + ACSDK_ERROR(LX("handleUpdateLocked").m("There is no update to apply").d("requester", name())); + s_metrics() + .addCounter(METRIC_PREFIX_ERROR("updateFailed")) + .addString("error", "nullPendingUpdate") + .addString("request", name()); + return; + } + + ACSDK_INFO(LX("handleUpdateLocked").m("Applying update").d("requester", name())); + s_metrics().addCounter("updateAccepted").addString("request", name()); + m_metadata->setResourceId(m_pendingUpdate->getId()); + m_metadata->saveToFile(m_metadataFilePath); + m_storageManager->releaseResource(m_resource); + m_resource = move(m_pendingUpdate); +} +bool Requester::validateWriteRequest(const std::string& name, int newValue) { + auto summary = m_metadata->getRequest()->getSummary(); + auto priorityProp = summary + AMD::PRIORITY_SUFFIX; + if (name == priorityProp) { + if (!isValidPriority(newValue)) { + ACSDK_ERROR(LX("validateWriteRequest").d("invalid priority", newValue)); + return false; + } + ACSDK_DEBUG(LX("validateWriteRequest").m("Valid Priority")); + return true; + } + + // Default return false. + return false; +} +string Requester::functionToBeInvoked(const std::string& name) { + auto summary = m_metadata->getRequest()->getSummary(); + auto pathProp = summary + AMD::PATH_SUFFIX; + + if (name == pathProp) { + return this->getArtifactPath(); + } + return ""; +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/AssetManager/acsdkAssetManager/src/Requester.h b/capabilities/AssetManager/acsdkAssetManager/src/Requester.h new file mode 100644 index 0000000000..c5fbc2ffd1 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/Requester.h @@ -0,0 +1,245 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_SRC_REQUESTER_H_ +#define ACSDKASSETMANAGER_SRC_REQUESTER_H_ + +#include +#include +#include + +#include "RequesterMetadata.h" +#include "Resource.h" +#include "StorageManager.h" +#include "acsdkAssetManagerClient/AMD.h" +#include "acsdkAssetsInterfaces/Communication/AmdCommunicationInterface.h" +#include "acsdkAssetsInterfaces/State.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +class RequesterFactory; + +/** + * This class represents an artifact as it exists on the system or as it is being downloaded. Given an artifact + * directory, it will maintain a metadata json file that will maintain its state and description on the same path. The + * artifact will be stored or unzipped inside this directory to handle its update and maintenance. + */ +class Requester + : public acsdkCommunicationInterfaces::CommunicationPropertyValidatorInterface + , public acsdkCommunicationInterfaces::FunctionInvokerInterface + , public std::enable_shared_from_this { +public: + /** + * steady_clock starting offset based off of the previous artifact times. + * @note this is done to get around the issue with changing system_clock times and synchronizations. + */ + static std::chrono::milliseconds START_TIME_OFFSET; + + /** + * Ensures that the Communication Handler properties are all cleared away. + */ + virtual ~Requester(); + + /** + * Issues a download request if not already in progress. + * + * @return true if the artifact is already downloaded or can download. + */ + virtual bool download() = 0; + + /** + * Deletes the artifact and deregisters Communication Handler properties accordingly. + */ + inline size_t deleteAndCleanup() { + std::unique_lock lock(m_eventMutex); + return deleteAndCleanupLocked(lock); + } + + /** + * Handles pendingUpdate resource according to this function call. + * @param accept the pendingUpdate change or not. + * If accepted, release the old resource and set the new one. + * If rejected, then release the pendingUpdate. + */ + void handleUpdate(bool accept); + + /** + * @return name of this artifact based on the summary. + */ + inline std::string name() const { + return m_metadata->getRequest()->getSummary(); + } + + /** + * @return original request which describes this artifact. + */ + inline const std::shared_ptr& getArtifactRequest() const { + return m_metadata->getRequest(); + } + + /** + * @return the current state for this artifact; + */ + inline commonInterfaces::State getState() const { + if (m_communicationHandlerRegistered) { + auto state = static_cast(m_stateProperty->getValue()); + return state; + } + return commonInterfaces::State::INVALID; + } + + /** + * @return last time the artifact was created or used. + */ + inline std::chrono::milliseconds getLastUsed() const { + return m_metadata->getLastUsed(); + } + + /** + * @return the current priority for this artifact. + */ + inline commonInterfaces::Priority getPriority() const { + if (m_communicationHandlerRegistered && m_priorityProperty != nullptr) { + return static_cast(m_priorityProperty->getValue()); + } + return commonInterfaces::Priority::UNUSED; + } + + /** + * @return true if the artifact is downloaded on the system. + */ + inline bool isDownloaded() { + return m_resource != nullptr; + } + + /** + * Returns the path where the artifact is stored and updates the last used timestamp if the path exists. + * + * @return path of the artifact if exists, empty string otherwise. + */ + std::string getArtifactPath(); + + /** + * Sets the priority of this artifact to a new value. + */ + virtual void setPriority(commonInterfaces::Priority newPriority); + + /// Override of the CommunicationPropertyValidatorInterface + bool validateWriteRequest(const std::string& name, int newValue) override; + + /// Override of the InvokeFunctionInterface + std::string functionToBeInvoked(const std::string& Name) override; + +protected: + Requester( + std::shared_ptr storageManager, + std::shared_ptr communicationHandler, + std::shared_ptr metadata, + std::string metadataFilePath); + + /** + * Attempts to fetch the resource from storage manager. + * + * @return true if resource is available, false otherwise. + */ + bool initializeFromStorage(); + + /** + * Registers the Communication Handler properties, if not already registered, for state, priority, and path. + * + * @return true if the properties were registered successfully, false otherwise. + */ + bool registerCommunicationHandlerPropsLocked(); + + /** + * Deletes the artifact and deregisters communication properties accordingly. + */ + void deregisterCommunicationHandlerPropsLocked(std::unique_lock& lock); + + /** + * Sets the existing state and informs the manager of the new state. + */ + inline void setStateLocked(commonInterfaces::State newState) { + if (nullptr == m_metadata) { + return; + } + + if (m_communicationHandlerRegistered) { + m_stateProperty->setValue(static_cast(newState)); + } + } + + /** + * Sets the last used timestamp to the current time. + */ + void updateLastUsedTimestampLocked(); + + void notifyUpdateIsAvailableLocked(std::unique_lock& lock); + + bool handleAcquiredResourceLocked(std::unique_lock& lock, const std::shared_ptr& newResource); + void handleDownloadFailureLocked(std::unique_lock& lock); + void handleUpdateLocked(std::unique_lock& lock, bool accept); + + /** + * Deletes the artifact and deregisters from DAVS Client accordingly. + */ + virtual size_t deleteAndCleanupLocked(std::unique_lock& lock); + +protected: + // Manager used to free up space when needed. + const std::shared_ptr m_storageManager; + // Communication Property Handler used for communicating with external processes. + const std::shared_ptr m_communicationHandler; + + // Artifact metadata containing request and other information for this artifact. + const std::shared_ptr m_metadata; + // Path to the directory where this artifact is stored. + const std::string m_metadataFilePath; + + // A storage manager space reservation token that (while alive) holds a certain space in storage manager + std::unique_ptr m_storageReservationToken; + // Pointer to the actual resource used for this request. + std::shared_ptr m_resource; + // Pointer to the resource that will be held for updating this request. + std::shared_ptr m_pendingUpdate; + // Total number of update notifications sent for this request + int m_updateNotificationsSent; + // The Timer used to schedule updates. + alexaClientSDK::avsCommon::utils::timing::Timer m_timer; + + // Mutex for synchronizing event states. + std::mutex m_eventMutex; + + // Are our communication Handler properties registered or not? + bool m_communicationHandlerRegistered; + + // Allow the requester factory to create requesters + friend RequesterFactory; + + // Communication Property for state. + std::shared_ptr> m_stateProperty; + // Communication Property for priority + std::shared_ptr> m_priorityProperty; + // Communication Property for Updates + std::shared_ptr> m_updateProperty; +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_SRC_REQUESTER_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/src/RequesterFactory.cpp b/capabilities/AssetManager/acsdkAssetManager/src/RequesterFactory.cpp new file mode 100644 index 0000000000..63bc8245c3 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/RequesterFactory.cpp @@ -0,0 +1,191 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "RequesterFactory.h" + +#include +#include + +#include "acsdkAssetsCommon/AmdMetricWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; +using namespace client; +using namespace common; +using namespace commonInterfaces; +using namespace davsInterfaces; +using namespace alexaClientSDK::avsCommon::utils; +using namespace alexaClientSDK::avsCommon::sdkInterfaces; + +static const auto s_metrics = AmdMetricsWrapper::creator("requesterFactory"); + +/// String to identify log entries originating from this file. +static const std::string TAG{"RequesterFactory"}; +/// Tag to represent the power resource for URL requesters. +static const std::string URL_POWER_RESOURCE_TAG = "UrlDownloader"; + +/** + * 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) + +unique_ptr RequesterFactory::create( + shared_ptr storageManager, + shared_ptr communicationHandler, + shared_ptr davsClient, + string urlTmpDirectory, + shared_ptr authDelegate, + shared_ptr allowList) { + if (storageManager == nullptr) { + ACSDK_ERROR(LX("create").m("Null Storage Manager")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullStorageManager")); + return nullptr; + } + if (nullptr == communicationHandler) { + ACSDK_ERROR(LX("create").m("Null Communication Handler")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullCommunicationHandler")); + return nullptr; + } + if (davsClient == nullptr) { + ACSDK_ERROR(LX("create").m("Null Davs Client")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullDavsClient")); + return nullptr; + } + if (urlTmpDirectory.empty()) { + ACSDK_ERROR(LX("create").m("Working directory not provided")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("invalidWorkingDirectory")); + return nullptr; + } + filesystem::removeAll(urlTmpDirectory); + if (!filesystem::makeDirectory(urlTmpDirectory)) { + ACSDK_ERROR(LX("create").m("Could not recreate working directory")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("createWorkingDirectory")); + return nullptr; + } + if (authDelegate == nullptr) { + ACSDK_ERROR(LX("create").m("Empty Auth Delegate")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullAuthDelegate")); + return nullptr; + } + if (allowList == nullptr) { + ACSDK_ERROR(LX("create").m("Null Url Allow List Wrapper")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullUrlAllowList")); + return nullptr; + } + + return unique_ptr(new RequesterFactory( + move(storageManager), + move(communicationHandler), + move(davsClient), + move(urlTmpDirectory), + authDelegate, + allowList)); +} + +RequesterFactory::RequesterFactory( + std::shared_ptr storageManager, + std::shared_ptr communicationHandler, + std::shared_ptr davsClient, + std::string urlTmpDirectory, + std::shared_ptr authDelegate, + std::shared_ptr allowList) : + m_storageManager(std::move(storageManager)), + m_communicationHandler(std::move(communicationHandler)), + m_davsClient(std::move(davsClient)), + m_urlTmpDirectory(std::move(urlTmpDirectory)), + m_authDelegate(move(authDelegate)), + m_urlPowerResource(power::PowerMonitor::getInstance()->createLocalPowerResource(URL_POWER_RESOURCE_TAG)), + m_allowedUrlList(move(allowList)) { +} + +shared_ptr RequesterFactory::createFromStorage(const string& metadataFilePath) { + shared_ptr metadata = RequesterMetadata::createFromFile(metadataFilePath); + if (metadata == nullptr) { + ACSDK_ERROR(LX("createFromStorage").m("Null Metadata")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullMetadata")); + return nullptr; + } + + auto summary = metadata->getRequest()->getSummary(); + auto requester = createFromMetadata(metadata, metadataFilePath); + if (requester == nullptr) { + ACSDK_ERROR(LX("createFromStorage").m("Failed to create requester for stored metadata").d("request", summary)); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullRequester")); + return nullptr; + } + + if (!requester->initializeFromStorage()) { + ACSDK_CRITICAL(LX("createFromStorage") + .m("This should never happen, failed to acquire resource based on id") + .d("request", summary)); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("acquireResource")); + requester->deleteAndCleanup(); + return nullptr; + } + + return requester; +} + +shared_ptr RequesterFactory::createFromMetadata( + const shared_ptr& metadata, + const string& metadataFilePath) { + if (metadata == nullptr) { + ACSDK_ERROR(LX("createFromMetadata").m("Null Requester Metadata")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullRequesterMetadata")); + return nullptr; + } + if (metadataFilePath.empty()) { + ACSDK_ERROR(LX("createFromMetadata").m("Invalid Metadata File Path")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("invalidMetadataFilePath")); + return nullptr; + } + + shared_ptr requester; + auto type = metadata->getRequest()->getRequestType(); + if (type == Type::DAVS) { + requester.reset( + new DavsRequester(m_storageManager, m_communicationHandler, metadata, metadataFilePath, m_davsClient)); + } else if (type == Type::URL) { + requester.reset(new UrlRequester( + m_storageManager, + m_communicationHandler, + metadata, + metadataFilePath, + m_urlTmpDirectory, + m_authDelegate, + m_urlPowerResource, + m_allowedUrlList)); + } else { + return nullptr; + } + + if (!requester->registerCommunicationHandlerPropsLocked()) { + ACSDK_ERROR(LX("createFromMetadata").m("Failed to register Communication Handler Properties")); + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("communicationHandlerPropsRegisterFailed")); + return nullptr; + } + + ACSDK_DEBUG9(LX("createFromMetadata").d("Requester created", requester->name()).d("ID", metadata->getResourceId())); + return requester; +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/AssetManager/acsdkAssetManager/src/RequesterFactory.h b/capabilities/AssetManager/acsdkAssetManager/src/RequesterFactory.h new file mode 100644 index 0000000000..c7626a95d6 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/RequesterFactory.h @@ -0,0 +1,95 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_SRC_REQUESTERFACTORY_H_ +#define ACSDKASSETMANAGER_SRC_REQUESTERFACTORY_H_ + +#include "DavsRequester.h" +#include "UrlRequester.h" +#include "acsdkAssetManager/UrlAllowListWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +/** + * Requester factory responsible for creating requesters based on requests metadata. + */ +class RequesterFactory { +public: + /** + * Creates a requester factory used to identify which requester can handle a certain request. + * + * @param storageManager REQUIRED, a module that manages disk space and frees up artifacts accordingly. + * @param communicationHandler REQUIRED, used to communicate the values of properties and invoke functions + * @param davsClient REQUIRED, client used to fetch the artifact object. + * @param urlTmpDirectory REQUIRED, folder for storing temporary url downloads. + * @param authDelegate REQUIRED, the Authentication Delegate to generate the authentication token + * @return NULLABLE, pointer to a new requester factory, null if inputs are invalid. + */ + static std::unique_ptr create( + std::shared_ptr storageManager, + std::shared_ptr communicationHandler, + std::shared_ptr davsClient, + std::string urlTmpDirectory, + std::shared_ptr authDelegate, + std::shared_ptr allowList); + + /** + * Creates an artifact based off of the given storage path. Tries to open the metadata file inside the given + * directory to parse out the artifact information, if found and parsed, then create the artifact object with LOADED + * state, otherwise return nullptr. + * + * @param metadataFilePath REQUIRED, path to the artifact metadata file used to load this request. + * @return NULLABLE, pointer to a new artifact based off of the storage. + */ + std::shared_ptr createFromStorage(const std::string& metadataFilePath); + + /** + * Creates an artifact in an init state with the provided metadata information. + * + * @param metadata REQUIRED, used to form the DAVS Client request which describes the desired artifact file. + * @param metadataFilePath REQUIRED, path to the artifact metadata file for this request. + * @return NULLABLE, pointer to a new artifact based off of the DAVS Request. + */ + std::shared_ptr createFromMetadata( + const std::shared_ptr& metadata, + const std::string& metadataFilePath); + +private: + RequesterFactory( + std::shared_ptr storageManager, + std::shared_ptr communicationHandler, + std::shared_ptr davsClient, + std::string urlTmpDirectory, + std::shared_ptr authDelegate, + std::shared_ptr allowList); + +private: + const std::shared_ptr m_storageManager; + const std::shared_ptr m_communicationHandler; + const std::shared_ptr m_davsClient; + /// Temporary directory used to store artifacts downloaded from a url + const std::string m_urlTmpDirectory; + const std::shared_ptr m_authDelegate; + const std::shared_ptr m_urlPowerResource; + std::shared_ptr m_allowedUrlList; +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_SRC_REQUESTERFACTORY_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/src/RequesterMetadata.cpp b/capabilities/AssetManager/acsdkAssetManager/src/RequesterMetadata.cpp new file mode 100644 index 0000000000..ca5ea0e3bc --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/RequesterMetadata.cpp @@ -0,0 +1,151 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "RequesterMetadata.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "RequestFactory.h" +#include "acsdkAssetsCommon/AmdMetricWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; +using namespace chrono; +using namespace rapidjson; +using namespace common; +using namespace commonInterfaces; +using namespace alexaClientSDK::avsCommon::utils; + +static const char* RESOURCE_ID = "resourceId"; +static const char* USED_TIMESTAMP = "usedTimestamp"; + +static const string TMP_FILE_POSTFIX = ".tmp"; + +static const auto s_metrics = AmdMetricsWrapper::creator("requesterMetadata"); + +/// String to identify log entries originating from this file. +static const std::string TAG{"RequesterMetadata"}; + +/** + * 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) + +unique_ptr RequesterMetadata::create( + shared_ptr request, + string resourceId, + milliseconds lastUsed) { + if (request == nullptr) { + ACSDK_ERROR(LX("create").m("metadata null")); + return nullptr; + } + + return unique_ptr(new RequesterMetadata(move(request), move(resourceId), lastUsed)); +} + +RequesterMetadata::RequesterMetadata(shared_ptr request, string resourceId, milliseconds lastUsed) : + m_request(move(request)), + m_resourceId(move(resourceId)), + m_lastUsed(lastUsed) { +} + +unique_ptr RequesterMetadata::createFromFile(const string& metadataFile) { + Document document; + + if (equal(TMP_FILE_POSTFIX.rbegin(), TMP_FILE_POSTFIX.rend(), metadataFile.rbegin())) { + ACSDK_ERROR(LX("createFromFile").m("Cannot use a temp file")); + s_metrics().addCounter(METRIC_PREFIX_ERROR("tmpMetadataFound")).addString("file", metadataFile); + return nullptr; + } + + ifstream ifs(metadataFile); + IStreamWrapper is(ifs); + + if (document.ParseStream(is).HasParseError()) { + ACSDK_ERROR(LX("createFromFile").m("Error parsing the metadata file")); + return nullptr; + } + + string resourceId; + auto usedTimestamp = milliseconds::zero(); + + auto request = RequestFactory::create(document); + if (request == nullptr) { + ACSDK_ERROR(LX("createFromFile").m("Could not create request from json document")); + return nullptr; + } + if (document.HasMember(RESOURCE_ID)) { + resourceId = document[RESOURCE_ID].GetString(); + } else { + ACSDK_ERROR(LX("createFromFile").d("Missing member", RESOURCE_ID)); + return nullptr; + } + + if (document.HasMember(USED_TIMESTAMP)) { + auto& usedTimestampMember = document[USED_TIMESTAMP]; + if (usedTimestampMember.IsInt64()) { + usedTimestamp = milliseconds(document[USED_TIMESTAMP].GetInt64()); + } + } else { + ACSDK_WARN(LX("createFromFile").d("Missing member", USED_TIMESTAMP).d("using default", usedTimestamp.count())); + } + + return unique_ptr(new RequesterMetadata(move(request), move(resourceId), usedTimestamp)); +} + +bool RequesterMetadata::saveToFile(const string& metadataFile) { + Document document; + document.Parse(m_request->toJsonString()); + auto& allocator = document.GetAllocator(); + + document.AddMember(StringRef(RESOURCE_ID), StringRef(m_resourceId), allocator); + document.AddMember(StringRef(USED_TIMESTAMP), static_cast(m_lastUsed.count()), allocator); + + StringBuffer buffer; + Writer writer(buffer); + document.Accept(writer); + + auto tmpFile = metadataFile + TMP_FILE_POSTFIX; + ofstream fileHandle(tmpFile, ios::out); + if (!fileHandle.is_open()) { + ACSDK_ERROR(LX("saveToFile").m("Failed to open metadata file")); + return false; + } + fileHandle << buffer.GetString(); + fileHandle.close(); + if (!fileHandle.good() || !filesystem::move(tmpFile, metadataFile)) { + ACSDK_ERROR(LX("saveToFile").m("Failed to close the file").d("file", metadataFile.c_str())); + s_metrics().addCounter(METRIC_PREFIX_ERROR("metadataSave")).addString("file", metadataFile); + return false; + } + return true; +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManager/src/RequesterMetadata.h b/capabilities/AssetManager/acsdkAssetManager/src/RequesterMetadata.h new file mode 100644 index 0000000000..66b8ec3468 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/RequesterMetadata.h @@ -0,0 +1,103 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_SRC_REQUESTERMETADATA_H_ +#define ACSDKASSETMANAGER_SRC_REQUESTERMETADATA_H_ + +#include + +#include +#include + +#include "acsdkAssetsInterfaces/ArtifactRequest.h" +#include "acsdkAssetsInterfaces/Priority.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +class RequesterMetadata { +public: + /** + * Creates a metadata file given a valid artifact request and storage metadata. + * + * @param request REQUIRED, full request containing all the data used to identify an artifact on davs. + * @param resourceId OPTIONAL, resourceId which uniquely identifies the downloaded resource or content. + * @param lastUsed OPTIONAL, last time this artifact was referenced. + * @return NULLABLE, a valid pointer to Artifact Metadata, null otherwise. + */ + static std::unique_ptr create( + std::shared_ptr request, + std::string resourceId = "", + std::chrono::milliseconds lastUsed = std::chrono::milliseconds(0)); + + /** + * Read the metadata info from disk and construct ArtifactMetadata object. + * + * @param metadataFile REQUIRED, path of the metadata json file containing ArtifactMetadata info. + * @return return ArtifactMetadata if read from file is success or return nullptr. + */ + static std::unique_ptr createFromFile(const std::string& metadataFile); + + /** + * Creates a metadata file which has artifact request info. + * + * @param metadataFile full path of the file to save the metadata to. + * @return returns true if successful + */ + bool saveToFile(const std::string& metadataFile); + + inline const std::shared_ptr& getRequest() { + return m_request; + } + + inline const std::string& getResourceId() const { + return m_resourceId; + } + + inline std::chrono::milliseconds getLastUsed() const { + return m_lastUsed; + } + + inline void setResourceId(const std::string& value) { + m_resourceId = value; + } + + inline void setLastUsed(const std::chrono::milliseconds value) { + m_lastUsed = value; + } + + inline void clear(const std::string& metadataFile) { + m_resourceId.clear(); + alexaClientSDK::avsCommon::utils::filesystem::removeAll(metadataFile); + } + +private: + RequesterMetadata( + std::shared_ptr request, + std::string resourceId, + std::chrono::milliseconds lastUsed); + +private: + const std::shared_ptr m_request; + std::string m_resourceId; + std::chrono::milliseconds m_lastUsed; +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_SRC_REQUESTERMETADATA_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/src/Resource.cpp b/capabilities/AssetManager/acsdkAssetManager/src/Resource.cpp new file mode 100644 index 0000000000..bfa0022693 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/Resource.cpp @@ -0,0 +1,233 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "Resource.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; +using namespace rapidjson; +using namespace alexaClientSDK::avsCommon::utils; + +static const char* METADATA_FILE_NAME = "metadata.json"; +static const char* RESOURCE_NAME = "name"; +static const char* RESOURCE_ID = "id"; +static const char* RESOURCE_SIZE = "size"; + +/// String to identify log entries originating from this file. +static const std::string TAG{"Resource"}; + +/** + * 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 Resource::createFromConfigFile(const std::string& resourceDirectory) { + Document document; + auto metaDataFilePath = resourceDirectory + "/" + METADATA_FILE_NAME; + ifstream ifs(metaDataFilePath); + IStreamWrapper is(ifs); + + if (document.ParseStream(is).HasParseError()) { + ACSDK_ERROR( + LX("createFromConfigFile").m("Error parsing the metadata file").d("file", metaDataFilePath.c_str())); + return nullptr; + } + + if (!document.HasMember(RESOURCE_NAME)) { + ACSDK_ERROR(LX("createFromConfigFile").d("Missing member", RESOURCE_NAME)); + return nullptr; + } + if (!document.HasMember(RESOURCE_ID)) { + ACSDK_ERROR(LX("createFromConfigFile").d("Missing member", RESOURCE_ID)); + return nullptr; + } + if (!document.HasMember(RESOURCE_SIZE)) { + ACSDK_ERROR(LX("createFromConfigFile").d("Missing member", RESOURCE_SIZE)); + return nullptr; + } + + string name = document[RESOURCE_NAME].GetString(); + if (name.empty()) { + ACSDK_ERROR(LX("createFromConfigFile").d("Empty member", RESOURCE_NAME)); + return nullptr; + } + string id = document[RESOURCE_ID].GetString(); + if (id.empty()) { + ACSDK_ERROR(LX("createFromConfigFile").d("Empty member", RESOURCE_ID)); + return nullptr; + } + auto& sizeMember = document[RESOURCE_SIZE]; + if (!sizeMember.IsUint64()) { + ACSDK_ERROR(LX("createFromConfigFile").d("Invalid member %s", RESOURCE_SIZE)); + return nullptr; + } + + return shared_ptr( + new Resource(resourceDirectory, move(name), move(id), static_cast(sizeMember.GetUint64()))); +} + +std::shared_ptr Resource::createFromStorage(const std::string& resourceDirectory) { + auto resource = createFromConfigFile(resourceDirectory); + if (resource != nullptr) { + ACSDK_DEBUG(LX("createFromStorage") + .d("Loaded resource", resource->m_resourceName) + .d("metadataFile", resourceDirectory.c_str())); + return resource; + } + + ACSDK_WARN(LX("createFromStorage") + .m("Could not load artifact metadata, will try to generate metadata from ") + .d("file", resourceDirectory)); + auto id = filesystem::basenameOf(resourceDirectory); + if (id.empty()) { + ACSDK_ERROR(LX("createFromStorage") + .m("Failed to get the resource id from directory") + .d("directory", resourceDirectory)); + return nullptr; + } + + auto fileList = filesystem::list(resourceDirectory); + fileList.erase(remove(fileList.begin(), fileList.end(), METADATA_FILE_NAME), fileList.end()); + if (fileList.size() != 1) { + ACSDK_ERROR(LX("createFromStorage") + .m("Failed to get resource name from directory") + .d("directory", resourceDirectory)); + ACSDK_ERROR(LX("createFromStorage") + .m("Found wrong number of files, expecting only 1") + .d("files found", fileList.size())); + return nullptr; + } + + auto name = fileList[0]; + auto size = filesystem::sizeOf(resourceDirectory + "/" + name); + if (size == 0) { + ACSDK_ERROR(LX("createFromStorage") + .m("Failed to get the resource size from directory") + .d("directory", resourceDirectory)); + return nullptr; + } + + ACSDK_DEBUG(LX("createFromStorage") + .m("Loaded and generated resource info, caching in metadata file") + .d("loadedFile", resourceDirectory)); + resource = shared_ptr(new Resource(resourceDirectory, name, id, size)); + resource->saveMetadata(); + return resource; +} + +std::shared_ptr Resource::create( + const std::string& parentDirectory, + const std::string& id, + const std::string& source) { + if (!filesystem::makeDirectory(parentDirectory)) { + ACSDK_ERROR(LX("create").m("Could not create parent directory").d("directory", parentDirectory)); + return nullptr; + } + if (id.empty()) { + ACSDK_ERROR(LX("create").m("Empty id for artifact").d("artifact", source)); + return nullptr; + } + if (!filesystem::exists(source)) { + ACSDK_ERROR(LX("create").m("Source file does not exists").d("artifact", source)); + return nullptr; + } + + auto resourceHome = parentDirectory + "/" + id; + auto filename = filesystem::basenameOf(source); + + if (!filesystem::makeDirectory(resourceHome)) { + ACSDK_ERROR(LX("create").m("Failed to create resource directory").d("directory", resourceHome)); + return nullptr; + } + + if (!filesystem::move(source, resourceHome + "/" + filename)) { + ACSDK_ERROR(LX("create").m("Failed to move file").d("file", source).d("directory", resourceHome)); + return nullptr; + } + + auto size = filesystem::sizeOf(resourceHome); + auto resource = shared_ptr(new Resource(resourceHome, filename, id, size)); + if (!resource->saveMetadata()) { + ACSDK_ERROR( + LX("create").m("Could not save metadata information, will try to generate this dynamically on next " + "restart")); + } + + ACSDK_INFO(LX("create").d("id", resource->getId().c_str()).d("path", resource->getPath().c_str())); + return resource; +} + +Resource::Resource(const string& resourceDirectory, const string& resourceName, const string& id, size_t sizeBytes) : + m_resourceDirectory(resourceDirectory), + m_resourceName(resourceName), + m_id(id), + m_sizeBytes(sizeBytes), + m_fullResourcePath(this->m_resourceDirectory + "/" + this->m_resourceName), + m_refCount(0) { +} + +bool Resource::saveMetadata() { + const auto outputPath = m_resourceDirectory + "/" + METADATA_FILE_NAME; + Document document(kObjectType); + auto& allocator = document.GetAllocator(); + + document.AddMember(StringRef(RESOURCE_ID), StringRef(m_id), allocator); + document.AddMember(StringRef(RESOURCE_SIZE), static_cast(m_sizeBytes), allocator); + document.AddMember(StringRef(RESOURCE_NAME), StringRef(m_resourceName), allocator); + + StringBuffer buffer; + Writer writer(buffer); + document.Accept(writer); + + ofstream fileHandle; + fileHandle.open(outputPath, ios::out); + if (!fileHandle.is_open()) { + ACSDK_ERROR(LX("saveMetadata").m("Failed to open metadata file")); + return false; + } + fileHandle << buffer.GetString(); + fileHandle.close(); + if (!fileHandle.good()) { + ACSDK_ERROR(LX("saveMetadata").m("Failed to close the file").d("file", outputPath.c_str())); + return false; + } + return true; +} + +void Resource::erase() { + filesystem::removeAll(m_resourceDirectory); + m_fullResourcePath.clear(); + m_sizeBytes = 0; +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManager/src/Resource.h b/capabilities/AssetManager/acsdkAssetManager/src/Resource.h new file mode 100644 index 0000000000..0e816aa08d --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/Resource.h @@ -0,0 +1,151 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_SRC_RESOURCE_H_ +#define ACSDKASSETMANAGER_SRC_RESOURCE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +class StorageManager; + +/** + * This class will represent a resource as it is stored on the system. It will maintain an internal reference counter + * which represents how many requests are referencing it. + * + * This class will only be created and managed by StorageManager to ensure that it does not get leaked or mishandled. + */ +class Resource { +public: + /** + * @return the id of this resource, most commonly the sha2 checksum. + */ + inline const std::string& getId() const { + return m_id; + } + + /** + * @return the full path of the resource itself, containing its content. ie /path/to/resource/file.txt + */ + inline const std::string& getPath() const { + return m_fullResourcePath; + } + + /** + * @return the size of the resource in bytes, if the resource has been deleted, then the size will be 0. + */ + inline size_t getSizeBytes() const { + return m_sizeBytes; + } + + /** + * @return weather the resource exists on the system or not using its size. + */ + inline bool exists() const { + return m_sizeBytes > 0; + } + +private: + /** + * Creates a resource given a source file or directory that will be represented by this resource and moves it to the + * designated parent directory. If the operation succeeds, then this will also attempt to cache this information in + * a metadata file. + * + * @param parentDirectory REQUIRED, directory that is used to store this resource. + * @param id REQUIRED, id that will be used to keep track of this resource. + * @param source REQUIRED, actual content (file or directory) that will be represented by this resource. + * @return NULLABLE, a smart pointer to the newly created resource upon success, null otherwise. + */ + static std::shared_ptr create( + const std::string& parentDirectory, + const std::string& id, + const std::string& source); + + /** + * Creates a resource, given a directory, by analyzing the content of the directory. ie if /path/to/resource_id + * Then this will parse out: + * resource_id as the ID of this resource. + * the single file inside it, ie file.txt as the name of the resource (fails if there are multiple files or none) + * size of this directory as the size of this resource. + * + * If the operation succeeds, then this will also attempt to cache this information in a metadata file. + * @note this is used in case the metadata file is missing. + * + * @param resourceDirectory REQUIRED, path to a valid directory housing a single resource. + * @return NULLABLE, a smart pointer to the newly created resource upon success, null otherwise. + */ + static std::shared_ptr createFromStorage(const std::string& resourceDirectory); + + /** + * Creates a resource using a configuration file found inside the specified directory. If no metadata file is found + * then call createFromStorage to create the file by analyzing this directory. + * + * @param resourceDirectory REQUIRED, path to a valid directory housing a single resource and its metadata file. + * @return NULLABLE, a smart pointer to the newly created resource upon success, null otherwise. + */ + static std::shared_ptr createFromConfigFile(const std::string& resourceDirectory); + + Resource( + const std::string& resourceDirectory, + const std::string& resourceName, + const std::string& id, + size_t sizeBytes); + + /** + * Cache the resource information to a metadata file inside resourceDirectory. + * + * @return true if successful, false otherwise. + */ + bool saveMetadata(); + + /** + * Erases the entire resourceDirectory and resets the resource content. + */ + void erase(); + + inline int incrementRefCount() { + return ++m_refCount; + } + inline int decrementRefCount() { + return --m_refCount; + } + +private: + // The parent directory where the resource is stored, like "/some/path/abc" + const std::string m_resourceDirectory; + // The name of the file or directory that is stored inside the resource directory, like "resource.txt" + const std::string m_resourceName; + // Id of the resource, usually the checksum, like "abc" + const std::string m_id; + // Size of the entire resource directory + size_t m_sizeBytes; + // The complete path of the resource including the name, like "/some/path/abc/resource.txt" + std::string m_fullResourcePath; + // Count of how many requesters reference this resource. + int m_refCount; + + // it's important that the creation, ref counting, and deletion of resource happens only by the Storage Manager. + friend StorageManager; +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_SRC_RESOURCE_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/src/StorageManager.cpp b/capabilities/AssetManager/acsdkAssetManager/src/StorageManager.cpp new file mode 100644 index 0000000000..ad5bf989c1 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/StorageManager.cpp @@ -0,0 +1,286 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "StorageManager.h" + +#include + +#include +#include + +#include "acsdkAssetManager/AssetManager.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; +using namespace alexaClientSDK::avsCommon::utils; + +#if UNIT_TEST == 1 +// For tests, because we don't want to create megabytes worth of files... +static constexpr auto BYTES_IN_MB = 1024 * 4; +#else +static constexpr auto BYTES_IN_MB = 1024 * 1024; +#endif +static const string BUDGET_FILE_SUFFIX = "/budget.config"; + +// when looking at how much space is available on the system, be sure to leave out a few MBs +static constexpr size_t SYSTEM_STORAGE_BUFFER = 5 * BYTES_IN_MB; + +/// String to identify log entries originating from this file. +static const std::string TAG{"StorageManager"}; + +/** + * 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 subtractSize(size_t original, size_t subAmount) { + if (original <= subAmount) { + return 0; + } + return original - subAmount; +} + +shared_ptr StorageManager::create( + const string& workingDirectory, + const shared_ptr& assetManager) { + if (assetManager == nullptr) { + ACSDK_ERROR(LX("create").m("Null AssetManager")); + return nullptr; + } + auto budget = MAX_BUDGET_MB; + ifstream configFile(workingDirectory + BUDGET_FILE_SUFFIX); + if (configFile.good()) { + configFile >> budget; + } + + auto sm = shared_ptr(new StorageManager(workingDirectory, assetManager, budget)); + + if (!sm->init()) { + ACSDK_ERROR(LX("create").m("Failed to initialize Storage Manager")); + return nullptr; + } + + return sm; +} + +StorageManager::StorageManager( + const string& workingDirectory, + const shared_ptr& assetManager, + size_t budget) : + m_workingDirectory(workingDirectory), + m_assetManager(assetManager), + m_budget(budget), + m_allocatedSize(0) { +} + +bool StorageManager::init() { + if (!filesystem::makeDirectory(m_workingDirectory)) { + ACSDK_ERROR(LX("init") + .m("This should never happen, failed to create working directory") + .d("directory", m_workingDirectory)); + return false; + } + + auto directories = filesystem::list(m_workingDirectory, filesystem::FileType::DIRECTORY); + for (const auto& dirName : directories) { + auto resourceDirectory = m_workingDirectory + "/" + dirName; + auto resource = Resource::createFromStorage(resourceDirectory); + if (resource != nullptr) { + ACSDK_INFO(LX("init").m("Loaded stored resource").d("resource", resource->getPath())); + m_allocatedSize += resource->getSizeBytes(); + m_bank[resource->getId()] = resource; + } else { + ACSDK_ERROR(LX("init").m("Failed to load stored resource, cleaning it up!")); + filesystem::removeAll(resourceDirectory); + } + } + return true; +} + +void StorageManager::purgeUnreferenced() { + unique_lock lock(m_allocationMutex); + for (auto resource = m_bank.begin(); resource != m_bank.end();) { + if (resource->second == nullptr) { + resource = m_bank.erase(resource); + } else if (resource->second->m_refCount <= 0) { + m_allocatedSize -= resource->second->getSizeBytes(); + resource->second->erase(); + resource = m_bank.erase(resource); + } else { + ++resource; + } + } +} + +bool StorageManager::requestSpace(const size_t requestedAmount) { + auto assetManagerPtr = m_assetManager.lock(); + if (assetManagerPtr == nullptr) { + ACSDK_ERROR( + LX("requestSpace") + .m("This should never happen, assetManager is not available, failed to free requested space")); + return false; + } + if (!assetManagerPtr->freeUpSpace(requestedAmount)) { + ACSDK_ERROR(LX("requestSpace").m("Failed to clear requested space").d("requestedSpaceBytes", requestedAmount)); + return false; + } + + ACSDK_DEBUG( + LX("requestSpace").m("Was able to clear up requested space").d("RequestedNumberOfBytes", requestedAmount)); + return true; +} + +size_t StorageManager::availableBudget() { + lock_guard lock(m_allocationMutex); + auto availableStorage = subtractSize(filesystem::availableSpace(m_workingDirectory), SYSTEM_STORAGE_BUFFER); + auto budgetSize = m_budget * BYTES_IN_MB; + + // if we are over our budget, then return 0 + if (budgetSize < m_allocatedSize) { + return 0; + } + + // return the lesser of: + // a. how much space is available on the device, and + // b. how many bytes we have left on our budget + return min(budgetSize - m_allocatedSize, availableStorage); +} + +void StorageManager::requestGarbageCollection(const size_t requestedAmount) { + auto assetManagerPtr = m_assetManager.lock(); + if (assetManagerPtr == nullptr) { + ACSDK_ERROR(LX("requestGarbageCollection") + .m("This should never happen, assetManager is not available, failed to request garbage " + "collection")); + return; + } + + assetManagerPtr->queueFreeUpSpace(requestedAmount); +} + +shared_ptr StorageManager::registerAndAcquireResource( + unique_ptr reservationToken, + const string& id, + const string& sourcePath) { + if (reservationToken == nullptr) { + ACSDK_ERROR(LX("registerAndAcquireResource").m("Cannot register a new resource without first reserving space")); + return nullptr; + } + // destroy the token to free up reservation space, this token is required to force users to account for how much + // space they'll need before starting the download in order to make sure we have enough to store it. + // when we destroy the token, it's destructor will callback to free up the reserved space (by design) + reservationToken.reset(); + + unique_lock lock(m_allocationMutex); + auto& resource = m_bank[id]; + if (resource != nullptr) { + ACSDK_WARN(LX("registerAndAcquireResource") + .m("Attempting to register path, which already exists, ignoring...") + .d("path", sourcePath)); + filesystem::removeAll(sourcePath); + resource->incrementRefCount(); + return resource; + } + + resource = Resource::create(m_workingDirectory, id, sourcePath); + if (resource == nullptr) { + ACSDK_ERROR(LX("registerAndAcquireResource").m("Failed to register resource").d("resource", id)); + return nullptr; + } + + resource->incrementRefCount(); + m_allocatedSize += resource->getSizeBytes(); + auto budgetSize = m_budget * BYTES_IN_MB; + if (m_allocatedSize > budgetSize) { + requestGarbageCollection(m_allocatedSize - budgetSize); + } + + return resource; +} + +shared_ptr StorageManager::acquireResource(const string& id) { + unique_lock lock(m_allocationMutex); + auto& resource = m_bank[id]; + if (resource != nullptr) { + resource->incrementRefCount(); + } + return resource; +} + +size_t StorageManager::releaseResource(const shared_ptr& resource) { + if (resource == nullptr) { + ACSDK_INFO(LX("releaseResource").m("Null resource provided, nothing to release")); + return 0; + } + + unique_lock lock(m_allocationMutex); + if (resource->decrementRefCount() > 0) { + return 0; + } + + ACSDK_INFO(LX("releaseResource").m("There is no usage for resource, deleting").d("resource", resource->m_id)); + auto size = resource->getSizeBytes(); + m_bank.erase(resource->m_id); + resource->erase(); + + m_allocatedSize = subtractSize(m_allocatedSize, size); + return size; +} + +unique_ptr StorageManager::reserveSpace(size_t requestedAmount) { + auto available = availableBudget(); + if (requestedAmount > available && !requestSpace(requestedAmount - available)) { + ACSDK_ERROR(LX("reserveSpace") + .m("Could not reserve the requested amount of space") + .d("requestedSpaceBytes", requestedAmount)); + return nullptr; + } + unique_lock lock(m_allocationMutex); + m_allocatedSize += requestedAmount; + return std::unique_ptr(new ReservationToken(shared_from_this(), requestedAmount)); +} + +void StorageManager::freeReservedSpace(size_t size) { + unique_lock lock(m_allocationMutex); + m_allocatedSize = subtractSize(m_allocatedSize, size); +} + +size_t StorageManager::getBudget() { + lock_guard lock(m_allocationMutex); + return m_budget; +} + +void StorageManager::setBudget(size_t value) { + ofstream configFile(m_workingDirectory + BUDGET_FILE_SUFFIX); + if (configFile.good()) { + configFile << value; + } + + auto newSize = static_cast(value) * BYTES_IN_MB; + lock_guard lock(m_allocationMutex); + if (m_allocatedSize > newSize) { + requestGarbageCollection(m_allocatedSize - newSize); + } + m_budget = value; +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/AssetManager/acsdkAssetManager/src/StorageManager.h b/capabilities/AssetManager/acsdkAssetManager/src/StorageManager.h new file mode 100644 index 0000000000..624adaa33a --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/StorageManager.h @@ -0,0 +1,209 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_SRC_STORAGEMANAGER_H_ +#define ACSDKASSETMANAGER_SRC_STORAGEMANAGER_H_ + +#include +#include +#include +#include +#include +#include "Resource.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +class AssetManager; + +/** + * This class manages a certain budget that Asset Manager should not go over. If at any point the amount of space used + * by Asset Manager goes over the budget, this class will trigger a call to the Asset Manager to free up space by + * deleting unused artifacts. + */ +class StorageManager : public std::enable_shared_from_this { +public: + static constexpr auto MAX_BUDGET_MB = 500; + + /** + * A reservation token class will be used to reserve space before downloading any artifacts, this token will then + * be used to utilize the reserved space when acquiring a resource. If this object is destroyed, the reserved space + * allocated by it will automatically be freed. + */ + struct ReservationToken { + public: + /** + * Destructor to free up any reserved space. + */ + ~ReservationToken() { + if (m_reservedSize == 0) { + return; + } + + if (auto sm = m_storageManager.lock()) { + sm->freeReservedSpace(m_reservedSize); + } + } + + private: + /** + * Create a reservation token (privately, only Storage Manager can create this) + * @param storageManager the parent storage manager object used to call back in case we destruct + * @param reservedSize amount of space reserved by this token + */ + ReservationToken(std::weak_ptr storageManager, size_t reservedSize) : + m_storageManager(std::move(storageManager)), + m_reservedSize(reservedSize) { + } + + // parent storage manager class used to free up any reserved space + std::weak_ptr m_storageManager; + // amount of space reserved + size_t m_reservedSize; + + // it's important that the space reservation and freeing happens only by the Storage Manager. + friend StorageManager; + }; + + /** + * Create a Storage Manager that is responsible for maintaining a budget for the artifacts to remain under. + * + * @param workingDirectory REQUIRED, place to store budget configuration. + * @param assetManager REQUIRED, used to request low priority artifacts cleanup to meet the budget. + * @return NULLABLE, a smart pointer to Storage Manager if successful, null otherwise. + */ + static std::shared_ptr create( + const std::string& workingDirectory, + const std::shared_ptr& assetManager); + + ~StorageManager() = default; + + /** + * Post initialization step that goes through the map of resources and erases any that are unreferenced. + */ + void purgeUnreferenced(); + + /** + * Registers a resource given a path to its content. If the operation succeeds, then it will be acquired as well. + * If another resource is found with the same id, then this will delete the provided path and use the existing + * resource. If there is no other resource with this id, then this will move the source path to the resources + * directory. + * + * @param reservationToken REQUIRED, a unique token that was used to reserve space ahead of download + * @param id REQUIRED, a unique identifier for this resource, preferably the sha2 checksum. + * @param sourcePath REQUIRED, the path on disk where this resource is found. + * @return NULLABLE, a smart pointer to the newly created and acquired resource, null if the operation failed. + */ + std::shared_ptr registerAndAcquireResource( + std::unique_ptr reservationToken, + const std::string& id, + const std::string& sourcePath); + + /** + * Given an id, attempt to acquire a resource which will increment its reference count and returns the resource + * accordingly. If no resource is found with this id, then return a nullptr. + * + * @param id REQUIRED, a unique identifier for the requested resource. + * @return NULLABLE, a smart pointer to an existing resource, null if not found. + */ + std::shared_ptr acquireResource(const std::string& id); + + /** + * Given a resource, attempt to find it in the list and decrement its reference count. If the reference count is 0, + * then erase the resource from the system and return the size of how much memory was freed. If there are others + * referencing this resource, then return 0 and keep the resource on disk. + * + * @param resource REQUIRED, a valid resource that is registered by storage manager. + * @return the size of the space that was cleared with this release, 0 if the resource was not deleted. + */ + size_t releaseResource(const std::shared_ptr& resource); + + /** + * Reserve the requested amount of space and return a token that will be used to track the reserved space and is + * needed for registering a new resource. If the token is destroyed, the space is automatically freed. + * + * @param requestedAmount space to reserve + * @return a new unique token if space is successfully reserved, nullptr otherwise + */ + std::unique_ptr reserveSpace(size_t requestedAmount); + + /** + * @return the amount of bytes remaining that can be used for a new artifact. + * This is calculated as the lowest of: + * 1. Asset Manager Budget - reserved space - downloaded resource space + * 2. Amount of space left on the device - 5MB buffer + */ + size_t availableBudget(); + + /** + * @return Get the current budget in MB + */ + size_t getBudget(); + + /** + * Set a new budget value. + * @param value the new budget in MB. + */ + void setBudget(size_t valueMB); + +protected: + explicit StorageManager( + const std::string& workingDirectory, + const std::shared_ptr& assetManager, + size_t budget); + + /** + * Goes through the working directory and initializes all the available resources. + * + * @return true if the initialization succeeded, false otherwise. + */ + bool init(); + + /** + * Asks the Asset Manager to free up a certain amount of space in a background task. + * + * @param requestedAmount amount in bytes to be cleared. + */ + void requestGarbageCollection(size_t requestedAmount); + + /** + * Forwards request to Asset Manager to free up space according to the given amount. + * + * @param requestedAmount amount in bytes to clear. + * @return true if the requested space was cleared, false otherwise. + */ + bool requestSpace(size_t requestedAmount); + + /** + * Free up reserved space, to be used by reservation tokens + */ + void freeReservedSpace(size_t size); + +private: + const std::string m_workingDirectory; + const std::weak_ptr m_assetManager; + size_t m_budget; + std::mutex m_allocationMutex; + std::unordered_map> m_bank; + + size_t m_allocatedSize; +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_SRC_STORAGEMANAGER_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/src/UrlAllowListWrapper.cpp b/capabilities/AssetManager/acsdkAssetManager/src/UrlAllowListWrapper.cpp new file mode 100644 index 0000000000..2630bf6c7f --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/UrlAllowListWrapper.cpp @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetManager/UrlAllowListWrapper.h" + +#include + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; + +#define TAG "UrlAllowListWrapper" + +/// String to identify log entries originating from this file. +static const std::string LOGGER_TAG{"UrlAllowListWrapper"}; + +/** + * 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(LOGGER_TAG, event) + +shared_ptr UrlAllowListWrapper::create(vector allowList, bool allowAllUrls) { + if (allowList.empty()) { + ACSDK_WARN(LX("Empty Allow List").m("No urls will be allowed")); + } + return shared_ptr(new UrlAllowListWrapper(allowList, allowAllUrls)); +} + +bool UrlAllowListWrapper::isUrlAllowed(const std::string& url) { + lock_guard lock(m_allowListMutex); + if (m_allowAllUrls) { + return true; + } + for (auto const& prefix : m_allowList) { + if (url.compare(0, prefix.length(), prefix) == 0) { + return true; + } + } + return false; +} + +void UrlAllowListWrapper::setUrlAllowList(std::vector newAllowList) { + lock_guard lock(m_allowListMutex); + m_allowList = move(newAllowList); +} + +void UrlAllowListWrapper::addUrlToAllowList(std::string url) { + lock_guard lock(m_allowListMutex); + m_allowList.emplace_back(move(url)); +} + +bool UrlAllowListWrapper::allowAllUrls(bool allow) { +#ifdef DEBUG + lock_guard lock(m_allowListMutex); + m_allowAllUrls = allow; + return true; +#endif + return false; +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManager/src/UrlRequester.cpp b/capabilities/AssetManager/acsdkAssetManager/src/UrlRequester.cpp new file mode 100644 index 0000000000..9a1aab1031 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/UrlRequester.cpp @@ -0,0 +1,225 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "UrlRequester.h" + +#include +#include +#include +#include + +#include + +#include "acsdkAssetsCommon/AmdMetricWrapper.h" +#include "acsdkAssetsCommon/CurlWrapper.h" +#include "acsdkAssetsCommon/JitterUtil.h" +#include "acsdkAssetsInterfaces/UrlRequest.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +using namespace std; +using namespace chrono; +using namespace client; +using namespace common; +using namespace commonInterfaces; +using namespace alexaClientSDK::avsCommon::utils; +using namespace alexaClientSDK::avsCommon::utils::error; +using namespace alexaClientSDK::avsCommon::utils::power; + +#if UNIT_TEST == 1 +// For tests, because we don't want to wait hours for it to finish... +static constexpr auto BASE_BACKOFF_VALUE = milliseconds(10); +static constexpr auto MAX_DOWNLOAD_RETRY = 2; +#else +static constexpr auto BASE_BACKOFF_VALUE = milliseconds(200); +static constexpr auto MAX_DOWNLOAD_RETRY = 10; +#endif + +static const auto s_metrics = AmdMetricsWrapper::creator("urlRequester"); + +/// String to identify log entries originating from this file. +static const std::string TAG{"UrlRequester"}; + +/** + * 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) + +static constexpr size_t DEFAULT_EXPECTED_URL_SIZE = 1 * 1024 * 1024; + +static string getValueFromHeaders(const string& headers, const string& key) { + regex rgx(key + " ?: ?(.*)"); + smatch matches; + if (regex_search(headers, matches, rgx) && matches.size() == 2) { + return matches[1].str(); + } + + return ""; +} + +UrlRequester::UrlRequester( + std::shared_ptr storageManager, + std::shared_ptr communicationHandler, + std::shared_ptr metadata, + std::string metadataFilePath, + std::string workingDirectory, + std::shared_ptr authDelegate, + std::shared_ptr powerResource, + std::shared_ptr allowUrlList) : + Requester( + std::move(storageManager), + std::move(communicationHandler), + std::move(metadata), + std::move(metadataFilePath)), + m_workingDirectory(move(workingDirectory)), + m_downloadProgressTrigger(std::make_shared()), + m_authDelegate(move(authDelegate)), + m_powerResource(move(powerResource)), + m_allowUrlList(move(allowUrlList)) { +} + +UrlRequester::~UrlRequester() { + m_downloadProgressTrigger->cancel(); + if (m_downloadFuture.valid()) { + m_downloadFuture.wait(); + } +} + +size_t UrlRequester::deleteAndCleanupLocked(unique_lock& lock) { + m_downloadProgressTrigger->cancel(); + return Requester::deleteAndCleanupLocked(lock); +} + +bool UrlRequester::download() { + ACSDK_INFO(LX("download").m("Requesting download").d("request", name())); + unique_lock lock(m_eventMutex); + auto state = getState(); + if (State::INVALID != state && State::INIT != state) { + ACSDK_INFO(LX("download").m("Download is unnecessary").d("request", name()).d("state", state)); + return true; + } + + if (!registerCommunicationHandlerPropsLocked()) { + ACSDK_ERROR(LX("download").m("Could not register Communication Handler properties").d("request", name())); + handleDownloadFailureLocked(lock); + return false; + } + + if (!m_allowUrlList->isUrlAllowed(static_pointer_cast(m_metadata->getRequest())->getUrl())) { + ACSDK_ERROR(LX("download").m("Requested URL is NOT approved").d("request", name())); + handleDownloadFailureLocked(lock); + return false; + } + + setStateLocked(State::DOWNLOADING); + m_storageReservationToken.reset(); + m_downloadFuture = async(launch::async, &UrlRequester::downloadWorker, this); + + ACSDK_INFO(LX("download").m("Creating a request").d("request", name())); + return true; +} + +void UrlRequester::downloadWorker() { + // Will create and acquire PowerResource. Will be released when the variable goes out of scope + WakeGuard guard(m_powerResource); + auto request = static_pointer_cast(m_metadata->getRequest()); + auto unpack = request->needsUnpacking(); + // each URL download should have its own unique tmp dir name + auto path = m_workingDirectory + "/" + (unpack ? request->getSummary() : request->getFilename()); + FinallyGuard deleteTmpPath([&] { filesystem::removeAll(path); }); + auto waitTime = milliseconds(0); + + auto curl = CurlWrapper::create(false, m_authDelegate, request->getCertPath()); + if (nullptr == curl) { + ACSDK_ERROR(LX("downloadWorker").m("Could not create curl wrapper")); + unique_lock lock(m_eventMutex); + handleDownloadFailureLocked(lock); + return; + } + + auto headerResult = curl->getHeaders(request->getUrl()); + string contentLength = getValueFromHeaders(headerResult.value(), "Content-Length"); + + size_t expectedSize = atoi(contentLength.c_str()); + + if (0 == expectedSize) { + ACSDK_INFO(LX("downloadWorker") + .m("ContentLength was invalid or missing") + .d("Defaulting to size", DEFAULT_EXPECTED_URL_SIZE)); + expectedSize = DEFAULT_EXPECTED_URL_SIZE; + } + + auto reservation = m_storageManager->reserveSpace(expectedSize); + if (reservation == nullptr) { + ACSDK_ERROR(LX("downloadWorker").m("Could not free up enough space").d("request", name())); + unique_lock lock(m_eventMutex); + handleDownloadFailureLocked(lock); + return; + } + + unique_lock lock(m_eventMutex); + m_storageReservationToken = move(reservation); + for (auto i = 0; i < MAX_DOWNLOAD_RETRY; ++i) { + m_stateTrigger.wait_for(lock, waitTime, [this] { return State::DOWNLOADING != getState(); }); + if (State::DOWNLOADING != getState()) { + ACSDK_INFO(LX("downloadWorker").m("Cancelling download").d("request", name())); + return; + } + waitTime = jitterUtil::expJitter(max(waitTime, BASE_BACKOFF_VALUE)); + + m_downloadProgressTrigger->enable(expectedSize); + lock.unlock(); + if (request->needsUnpacking()) { + // Creating the unpacked subdirectory explicitly with default 750 permission + filesystem::makeDirectory(path); + } + auto result = curl->download(request->getUrl(), path, m_downloadProgressTrigger, request->needsUnpacking()); + lock.lock(); + + if (State::DOWNLOADING != getState()) { + ACSDK_ERROR(LX("downloadWorker").m("Cancelling download").d("request", name())); + handleDownloadFailureLocked(lock); + return; + } + + if (ResultCode::SUCCESS == result) { + auto newResource = m_storageManager->registerAndAcquireResource( + move(m_storageReservationToken), request->getSummary(), path); + if (nullptr == newResource) { + ACSDK_ERROR(LX("downloadWorker").m("Failed to register and acquire the resource").d("request", name())); + handleDownloadFailureLocked(lock); + return; + } + + handleAcquiredResourceLocked(lock, newResource); + return; + } + + if (i < MAX_DOWNLOAD_RETRY - 1) { + ACSDK_INFO(LX("downloadWorker").m("Download attempt failed. Retrying...").d("attempt", i)); + } + } + + ACSDK_ERROR(LX("downloadWorker").m("Failed to download").d("After attempt", MAX_DOWNLOAD_RETRY)); + handleDownloadFailureLocked(lock); +} + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/AssetManager/acsdkAssetManager/src/UrlRequester.h b/capabilities/AssetManager/acsdkAssetManager/src/UrlRequester.h new file mode 100644 index 0000000000..c6e4ea646b --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/src/UrlRequester.h @@ -0,0 +1,108 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGER_SRC_URLREQUESTER_H_ +#define ACSDKASSETMANAGER_SRC_URLREQUESTER_H_ + +#include +#include + +#include +#include + +#include "Requester.h" +#include "acsdkAssetManager/UrlAllowListWrapper.h" +#include "acsdkAssetsCommon/CurlProgressCallbackInterface.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace manager { + +/** + * This class implements the Requester class and extends it to allow for the handling of artifacts downloaded directly + * from urls + */ +class UrlRequester : public Requester { +public: + class CurlProgressCallback : public common::CurlProgressCallbackInterface { + public: + ~CurlProgressCallback() override = default; + + void enable(size_t budget) { + availableBudget = budget; + } + + void cancel() { + availableBudget = 0; + } + + bool onProgressUpdate(long dlTotal, long dlNow, long, long) override { + return availableBudget >= static_cast(dlNow); + } + + private: + std::atomic_size_t availableBudget{0}; + }; + + ~UrlRequester() override; + + /// @name Requester Functions + /// @{ + bool download() override; + /// @} + +private: + UrlRequester( + std::shared_ptr storageManager, + std::shared_ptr communicationHandler, + std::shared_ptr metadata, + std::string metadataFilePath, + std::string workingDirectory, + std::shared_ptr authDelegate, + std::shared_ptr powerResource, + std::shared_ptr allowUrlList); + + size_t deleteAndCleanupLocked(std::unique_lock& lock) override; + + /** + * This function checks to see if a download request is valid and if there is space for the downloaded asset, then + * downloads the asset if appropriate. + */ + void downloadWorker(); + +private: + /// Directory where this class stores downloaded assets + std::string m_workingDirectory; + /// Condition variable used to block until no downloads are occurring + std::condition_variable m_stateTrigger; + /// Allows the class to monitor downloads (which are performed asynchronously) + std::future m_downloadFuture; + /// Callback which curl calls repeatedly during a download which shares download progress + std::shared_ptr m_downloadProgressTrigger; + /// AuthDelegate that curlWrapper will use to get the Authentication Token + std::shared_ptr m_authDelegate; + /// PowerResource used to acquire/release the wakelock + std::shared_ptr m_powerResource; + /// The list of urls that we can download an artifact from. + std::shared_ptr m_allowUrlList; + + friend RequesterFactory; +}; + +} // namespace manager +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGER_SRC_URLREQUESTER_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/test/ArtifactTest.cpp b/capabilities/AssetManager/acsdkAssetManager/test/ArtifactTest.cpp new file mode 100644 index 0000000000..278d29bd96 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/test/ArtifactTest.cpp @@ -0,0 +1,139 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "AuthDelegateMock.h" +#include "InternetConnectionMonitorMock.h" +#include "RequestFactory.h" +#include "Requester.h" +#include "RequesterFactory.h" +#include "TestUtil.h" +#include "acsdkAssetManager/AssetManager.h" +#include "acsdkAssetsInterfaces/Communication/InMemoryAmdCommunicationHandler.h" +#include "acsdkDavsClient/DavsClient.h" +#include "acsdkDavsClient/DavsEndpointHandlerV3.h" + +using namespace std; +using namespace testing; +using namespace alexaClientSDK::acsdkAssets::common; +using namespace alexaClientSDK::acsdkAssets::commonInterfaces; +using namespace alexaClientSDK::acsdkAssets::davsInterfaces; +using namespace alexaClientSDK::acsdkAssets::davs; +using namespace alexaClientSDK::acsdkAssets::client; +using namespace alexaClientSDK::acsdkAssets::manager; +using namespace alexaClientSDK::avsCommon::utils; +using namespace alexaClientSDK::avsCommon::sdkInterfaces; + +class ArtifactTest : public testing::Test { +public: + void SetUp() override { + TMP_DIR = createTmpDir("Artifact"); + DAVS_DIR = TMP_DIR + "/davs"; + DAVS_TMP = TMP_DIR + "/davstmp"; + + authDelegateMock = AuthDelegateMock::create(); + wifiMonitorMock = InternetConnectionMonitorMock::create(); + davsEndpointHandler = DavsEndpointHandlerV3::create("123"); + allowUrlList = UrlAllowListWrapper::create({"ALL"}); + commsHandler = InMemoryAmdCommunicationHandler::create(); + + davsClient = DavsClient::create(DAVS_TMP, authDelegateMock, wifiMonitorMock, davsEndpointHandler); + + auto assetManager = AssetManager::create(commsHandler, davsClient, DAVS_DIR, authDelegateMock, allowUrlList); + assetManager->onIdleChanged(1); + storageManager = StorageManager::create(DAVS_DIR, assetManager); + } + + void TearDown() override { + filesystem::removeAll(TMP_DIR); + } + + string TMP_DIR; + string DAVS_DIR; + string DAVS_TMP; + + shared_ptr request = + DavsRequest::create("test", "tar", {{"filter1", {"value1"}}, {"filter2", {"value2"}}}); + shared_ptr urlRequest = UrlRequest::create("urlLocation", "fileName", true, "certPath"); + shared_ptr davsClient; + shared_ptr storageManager; + shared_ptr authDelegateMock; + shared_ptr wifiMonitorMock; + shared_ptr davsEndpointHandler; + shared_ptr allowUrlList; + shared_ptr commsHandler; +}; + +TEST_F(ArtifactTest, CreateFromDavs) { // NOLINT + // clang-format off + ASSERT_TRUE(nullptr == RequesterFactory::create(nullptr, commsHandler, davsClient, DAVS_TMP, authDelegateMock,allowUrlList)); + ASSERT_TRUE(nullptr == RequesterFactory::create(storageManager, nullptr, davsClient, DAVS_TMP, authDelegateMock,allowUrlList)); + ASSERT_TRUE(nullptr == RequesterFactory::create(storageManager, commsHandler, nullptr, DAVS_TMP, authDelegateMock,allowUrlList)); + ASSERT_TRUE(nullptr == RequesterFactory::create(storageManager, commsHandler, davsClient, "", authDelegateMock,allowUrlList)); + ASSERT_TRUE(nullptr == RequesterFactory::create(storageManager, commsHandler, davsClient, DAVS_TMP, nullptr,allowUrlList)); + // clang-format on + + auto factory = RequesterFactory::create( + storageManager, commsHandler, davsClient, DAVS_TMP, authDelegateMock, allowUrlList); + ASSERT_TRUE(nullptr == factory->createFromMetadata(nullptr, DAVS_DIR)); + ASSERT_TRUE(nullptr == factory->createFromMetadata(RequesterMetadata::create(nullptr), DAVS_DIR)); + ASSERT_TRUE(nullptr == factory->createFromMetadata(RequesterMetadata::create(request), "")); +} + +TEST_F(ArtifactTest, CreateWithInvalidJsonFails) { // NOLINT + auto json = request->toJsonString(); + auto withoutType = string(json).replace(json.find("artifactType"), strlen("artifactType"), "artifactHype"); + auto withoutKey = string(json).replace(json.find("artifactKey"), strlen("artifactKey"), "artifactBey"); + auto withoutFilters = string(json).replace(json.find("filters"), strlen("filters"), "jitters"); + auto withoutEndpoint = string(json).replace(json.find("endpoint"), strlen("endpoint"), "endjoint"); + auto withoutUnpack = string(json).replace(json.find("unpack"), strlen("unpack"), "tupack"); + + ASSERT_TRUE(nullptr == RequestFactory::create("{}")); + ASSERT_TRUE(nullptr == RequestFactory::create(withoutType)); + ASSERT_TRUE(nullptr == RequestFactory::create(withoutKey)); + ASSERT_TRUE(nullptr == RequestFactory::create(withoutFilters)); + ASSERT_FALSE(nullptr == RequestFactory::create(withoutEndpoint)); // optional field + ASSERT_FALSE(nullptr == RequestFactory::create(withoutUnpack)); // optional field +} + +TEST_F(ArtifactTest, CreateUrlReqWithInvalidJsonFails) { // NOLINT + auto json = urlRequest->toJsonString(); + auto withoutUrl = string(json).replace(json.find("url"), strlen("url"), "urn"); + auto withoutFilename = string(json).replace(json.find("filename"), strlen("filename"), "tilebane"); + auto withoutUnpack = string(json).replace(json.find("unpack"), strlen("unpack"), "tupack"); + auto withoutCertPath = string(json).replace(json.find("certPath"), strlen("certPath"), "bertBath"); + + ASSERT_TRUE(nullptr == RequestFactory::create(withoutUrl)); + ASSERT_TRUE(nullptr == RequestFactory::create(withoutFilename)); + ASSERT_FALSE(nullptr == RequestFactory::create(withoutUnpack)); // optional field + ASSERT_FALSE(nullptr == RequestFactory::create(withoutCertPath)); // optional field +} + +TEST_F(ArtifactTest, CreateWithEmptyFilter) { // NOLINT + ASSERT_TRUE(nullptr == DavsRequest::create("test", "tar", {{}})); + + auto emptyFiltersRequest = DavsRequest::create("test", "tar", {}); + ASSERT_FALSE(nullptr == emptyFiltersRequest); + + auto json = emptyFiltersRequest->toJsonString(); + ASSERT_TRUE(json.find("filters") != string::npos); + + auto recreatedRequest = RequestFactory::create(json); + ASSERT_FALSE(nullptr == recreatedRequest); + ASSERT_TRUE(recreatedRequest->toJsonString() == emptyFiltersRequest->toJsonString()); +} diff --git a/capabilities/AssetManager/acsdkAssetManager/test/ArtifactUnderTest.h b/capabilities/AssetManager/acsdkAssetManager/test/ArtifactUnderTest.h new file mode 100644 index 0000000000..ae1682aa3f --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/test/ArtifactUnderTest.h @@ -0,0 +1,135 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 AVS_CAPABILITIES_ASSETMANAGER_ACSDKASSETMANAGER_TEST_ARTIFACTUNDERTEST_H_ +#define AVS_CAPABILITIES_ASSETMANAGER_ACSDKASSETMANAGER_TEST_ARTIFACTUNDERTEST_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "RequestFactory.h" +#include "acsdkAssetManager/AssetManager.h" +#include "acsdkAssetManagerClient/AMD.h" +#include "acsdkAssetsInterfaces/Communication/AmdCommunicationInterface.h" +#include "acsdkAssetsInterfaces/Priority.h" +#include "acsdkAssetsInterfaces/State.h" +#include "acsdkDavsClient/DavsEndpointHandlerV3.h" + +using namespace std; +using namespace chrono; +using namespace ::testing; +using namespace alexaClientSDK::acsdkAssets::common; +using namespace alexaClientSDK::acsdkAssets::commonInterfaces; +using namespace alexaClientSDK::acsdkAssets::davsInterfaces; +using namespace alexaClientSDK::acsdkAssets::davs; +using namespace alexaClientSDK::acsdkAssets::client; +using namespace alexaClientSDK::acsdkCommunicationInterfaces; + +class ArtifactUnderTest + : public CommunicationPropertyChangeSubscriber + , public CommunicationPropertyChangeSubscriber + , public enable_shared_from_this { +public: + bool hasAllProps() { + return hasStateProp() && hasPriorityProp() && hasPathProp(); + } + + bool hasStateProp() { + int value; + return commsHandler->readProperty(request->getSummary() + AMD::STATE_SUFFIX, value); + } + + bool hasPriorityProp() { + int value; + return commsHandler->readProperty(request->getSummary() + AMD::PRIORITY_SUFFIX, value); + } + + bool hasPathProp() { + return commsHandler->invoke(request->getSummary() + AMD::PATH_SUFFIX).isSucceeded(); + } + + State getStateProp() { + int value; + commsHandler->readProperty(request->getSummary() + AMD::STATE_SUFFIX, value); + return static_cast(value); + } + + Priority getPriorityProp() { + int temp; + commsHandler->readProperty(request->getSummary() + AMD::PRIORITY_SUFFIX, temp); + return static_cast(temp); + } + + string getPathProp() { + return commsHandler->invoke(request->getSummary() + AMD::PATH_SUFFIX).value(); + } + + Priority setPriorityProp(Priority p) { + commsHandler->writeProperty(request->getSummary() + AMD::PRIORITY_SUFFIX, static_cast(p)); + return p; + } + + bool waitUntilStateEquals(State expectedState, milliseconds timeout = milliseconds(500)) { + return waitUntil([this, expectedState] { return getStateProp() == expectedState; }, timeout); + } + + void onCommunicationPropertyChange(const std::string& PropertyName, int newValue) override { + if (PropertyName == request->getSummary() + AMD::STATE_SUFFIX) { + stateMap[static_cast(newValue)] += 1; + } + } + void onCommunicationPropertyChange(const std::string& PropertyName, string) override { + if (PropertyName == request->getSummary() + AMD::UPDATE_SUFFIX) { + updateEventCount++; + } + } + void resetCounts() { + updateEventCount = 0; + stateMap.clear(); + commsHandler->unsubscribeToPropertyChangeEvent( + request->getSummary() + AMD::STATE_SUFFIX, + static_pointer_cast>(shared_from_this())); + commsHandler->unsubscribeToPropertyChangeEvent( + request->getSummary() + AMD::UPDATE_SUFFIX, + static_pointer_cast>(shared_from_this())); + } + void subscribeToChangeEvents() { + commsHandler->subscribeToPropertyChangeEvent( + request->getSummary() + AMD::STATE_SUFFIX, + static_pointer_cast>(shared_from_this())); + commsHandler->subscribeToPropertyChangeEvent( + request->getSummary() + AMD::UPDATE_SUFFIX, + static_pointer_cast>(shared_from_this())); + } + ArtifactUnderTest(std::shared_ptr comm, shared_ptr request) : + commsHandler(comm), + request(request) { + } + std::shared_ptr commsHandler; + shared_ptr request; + unordered_map stateMap; + int updateEventCount = 0; +}; + +#endif // AVS_CAPABILITIES_ASSETMANAGER_ACSDKASSETMANAGER_TEST_ARTIFACTUNDERTEST_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerEvictionTest.cpp b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerEvictionTest.cpp new file mode 100644 index 0000000000..1c793546f1 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerEvictionTest.cpp @@ -0,0 +1,169 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "AssetManagerTest.h" + +using P = Priority; +static constexpr bool ERASED = true; +static constexpr bool KEPT = false; +const auto MB = 1024 * 4; // not really, but for our test we'll treat KB as MB +const auto ARTIFACT_SIZE = 10 * MB; + +struct EvictionData { + vector priorities; + vector orderUsageIndices; + size_t spaceNeeded; + bool spaceFreed; + vector deletedIndices; + string description; +}; + +class EvictionTest + : public AssetManagerTest + , public WithParamInterface { +public: + void SetUp() override { + AssetManagerTest::SetUp(); + auto& p = GetParam(); + + for (size_t i = 0; i < p.priorities.size(); i++) { + ArtifactUnderTest artifact{ + commsHandler, + DavsRequest::create(to_string(i), "k", {{"k", {"v"}}}, Region::NA, ArtifactRequest::UNPACK)}; + uploadArtifactFromRequest(artifact.request, static_cast(ARTIFACT_SIZE)); + ASSERT_TRUE(assetManager->downloadArtifact(artifact.request)) << "Failed setup: " << p.description; + ASSERT_TRUE(artifact.waitUntilStateEquals(State::LOADED)) << "Failed setup: " << p.description; + ASSERT_TRUE(artifact.hasAllProps()); + artifact.setPriorityProp(p.priorities[i]); + artifacts.emplace_back(move(artifact)); + } + } + + void waitForDeletion(const vector& paths) { + auto& p = GetParam(); + // wait until all the artifacts that were meant to be deleted to get erased + auto waited = false; + for (size_t i = 0; i < p.deletedIndices.size(); i++) { + if (p.deletedIndices[i]) { + auto path = paths[i]; + ASSERT_TRUE(waitUntil([path] { return !filesystem::exists(path); })); + waited = true; + } + } + if (!waited) { + this_thread::sleep_for(milliseconds(100)); + } + } + + vector artifacts; +}; + +TEST_P(EvictionTest, LastUsedScenario) { // NOLINT + auto& p = GetParam(); + + vector paths(artifacts.size()); + for (auto index : p.orderUsageIndices) { + paths[index] = artifacts[index].getPathProp(); + ASSERT_TRUE(filesystem::exists(paths[index])) << "Expected path does not exists: " << p.description; + this_thread::sleep_for(milliseconds(1)); + } + + ASSERT_EQ(assetManager->freeUpSpace(p.spaceNeeded), p.spaceFreed) + << "Failed freeUpSpace result check: " << p.description; + + for (size_t i = 0; i < p.deletedIndices.size(); i++) { + ASSERT_TRUE(waitUntil([&] { return !artifacts[i].hasStateProp() == p.deletedIndices[i]; })) + << "Failed artifact eviction state check: " << p.description; + ASSERT_TRUE(waitUntil([&] { return !artifacts[i].hasPriorityProp() == p.deletedIndices[i]; })) + << "Failed artifact eviction priority check: " << p.description; + ASSERT_TRUE(waitUntil([&] { return !artifacts[i].hasPathProp() == p.deletedIndices[i]; })) + << "Failed artifact eviction path check: " << p.description; + ASSERT_TRUE(waitUntil([&] { return !filesystem::exists(paths[i]) == p.deletedIndices[i]; })) + << "Failed artifact eviction file deletion check: " << p.description; + } +} + +TEST_P(EvictionTest, RestartingAssetManagerPreservesLastUsed) { // NOLINT + auto& p = GetParam(); + + vector paths(artifacts.size()); + for (auto index : p.orderUsageIndices) { + paths[index] = artifacts[index].getPathProp(); + ASSERT_TRUE(filesystem::exists(artifacts[index].getPathProp())) + << "Expected path does not exists: " << p.description; + this_thread::sleep_for(milliseconds(1)); + } + + shutdownAssetManager(); + startAssetManager(); + for (size_t i = 0; i < p.priorities.size(); i++) { + artifacts[i].setPriorityProp(p.priorities[i]); + } + + ASSERT_TRUE(waitUntil([&] { return assetManager->freeUpSpace(p.spaceNeeded) == p.spaceFreed; })) + << "Failed freeUpSpace result check: " << p.description; + + for (size_t i = 0; i < p.deletedIndices.size(); i++) { + ASSERT_TRUE(waitUntil([&] { return !artifacts[i].hasStateProp() == p.deletedIndices[i]; })) + << "Failed artifact eviction state check: " << p.description; + ASSERT_TRUE(waitUntil([&] { return !artifacts[i].hasPriorityProp() == p.deletedIndices[i]; })) + << "Failed artifact eviction priority check: " << p.description; + ASSERT_TRUE(waitUntil([&] { return !artifacts[i].hasPathProp() == p.deletedIndices[i]; })) + << "Failed artifact eviction path check: " << p.description; + ASSERT_TRUE(waitUntil([&] { return !filesystem::exists(paths[i]) == p.deletedIndices[i]; })) + << "Failed artifact eviction file deletion check: " << p.description; + } +} + +TEST_P(EvictionTest, LoweringBudgetScenario) { // NOLINT + auto& p = GetParam(); + + vector paths(artifacts.size()); + for (auto index : p.orderUsageIndices) { + paths[index] = artifacts[index].getPathProp(); + ASSERT_TRUE(filesystem::exists(artifacts[index].getPathProp())) + << "Expected path does not exists: " << p.description; + this_thread::sleep_for(milliseconds(1)); + } + + // budget is expected in MB + int newBudget = max(0, static_cast(artifacts.size()) * ARTIFACT_SIZE - static_cast(p.spaceNeeded)) / MB; + assetManager->setBudget(newBudget); + waitForDeletion(paths); + + for (size_t i = 0; i < p.deletedIndices.size(); i++) { + ASSERT_TRUE(waitUntil([&] { return !artifacts[i].hasStateProp() == p.deletedIndices[i]; })) + << "Failed artifact eviction state check: " << p.description; + ASSERT_TRUE(waitUntil([&] { return !artifacts[i].hasPriorityProp() == p.deletedIndices[i]; })) + << "Failed artifact eviction priority check: " << p.description; + ASSERT_TRUE(waitUntil([&] { return !artifacts[i].hasPathProp() == p.deletedIndices[i]; })) + << "Failed artifact eviction path check: " << p.description; + ASSERT_TRUE(waitUntil([&] { return !filesystem::exists(paths[i]) == p.deletedIndices[i]; })) + << "Failed artifact eviction file deletion check: " << p.description; + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(EvictionTestCases, EvictionTest, ValuesIn>( + // List of artifacts w/ Priorities | Usage Order | Space Needed || Space Freed? | What got erased | Description + {{{P::UNUSED, P::UNUSED, P::UNUSED} , {0, 1, 2} , 0 * MB , true , {KEPT, KEPT, KEPT} , "Requesting 0 bytes preserves all the artifacts"}, + {{P::UNUSED, P::UNUSED, P::UNUSED} , {0, 1, 2} , 9 * MB , true , {ERASED, KEPT, KEPT} , "Remove only as many artifacts that are needed to free up the requested space"}, + {{P::UNUSED, P::UNUSED, P::UNUSED} , {0, 1, 2} , 25 * MB , true , {ERASED, ERASED, ERASED} , "Remove all unused artifacts if necessary to clear up space"}, + {{P::UNUSED, P::UNUSED, P::UNUSED} , {0, 1, 2} , 31 * MB , false , {ERASED, ERASED, ERASED} , "Inform caller that we failed to clear sufficient space even after clearing all unused artifacts"}, + {{P::ACTIVE, P::UNUSED, P::PENDING_ACTIVATION} , {0, 1, 2} , 15 * MB , false , {KEPT, ERASED, KEPT} , "Never clear active or pending activation priorities even if more space is requested"}, + {{P::LIKELY_TO_BE_ACTIVE, P::UNUSED, P::UNUSED} , {2, 1, 0} , 20 * MB , true , {KEPT, ERASED, ERASED} , "Start erasing artifacts with lowest priority even if they were more recently used"}, + {{P::UNUSED, P::UNUSED, P::UNUSED} , {1, 2, 0} , 10 * MB , true , {KEPT, ERASED, KEPT} , "If priority is the same then erase the oldest used artifact"}, + }), PrintDescription()); +// clang-format on \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerInitTest.cpp b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerInitTest.cpp new file mode 100644 index 0000000000..b5cf94afde --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerInitTest.cpp @@ -0,0 +1,174 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "AssetManagerTest.h" + +using namespace rapidjson; + +static constexpr bool WILL_LOAD = true; +static constexpr bool WILL_BE_ERASED = false; + +// clang-format off +static auto REQUESTER_VALID = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_EMPTY_TYPE = R"({"artifactType":"", "artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_MISSING_TYPE = R"({ "artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_EMPTY_KEY = R"({"artifactType":"T","artifactKey":"", "filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_MISSING_KEY = R"({"artifactType":"T", "filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_EMPTY_FILTER_KEY = R"({"artifactType":"T","artifactKey":"K","filters":{"" :["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_INVALID_FILTER_VALUE = R"({"artifactType":"T","artifactKey":"K","filters":{"F" :[]}, "endpoint":0, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_MISSING_FILTER_VALUE = R"({"artifactType":"T","artifactKey":"K", "endpoint":0, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_INVALID_ENDPOINT = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":9, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_EMPTY_ENDPOINT = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":"","unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_MISSING_ENDPOINT = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]}, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_INVALID_UNPACK = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":"huh","resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_MISSING_UNPACK = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "resourceId":"R","priority":3, "usedTimestamp":10})"; +static auto REQUESTER_EMPTY_RESOURCE_ID = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"", "priority":3, "usedTimestamp":10})"; +static auto REQUESTER_MISSING_RESOURCE_ID = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false, "priority":3, "usedTimestamp":10})"; +static auto REQUESTER_INVALID_PRIORITY = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":7, "usedTimestamp":10})"; +static auto REQUESTER_EMPTY_PRIORITY = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":"","usedTimestamp":10})"; +static auto REQUESTER_MISSING_PRIORITY = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R", "usedTimestamp":10})"; +static auto REQUESTER_EMPTY_TIMESTAMP = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":3, "usedTimestamp":""})"; +static auto REQUESTER_MISSING_TIMESTAMP = R"({"artifactType":"T","artifactKey":"K","filters":{"F":["A"]},"endpoint":0, "unpack":false,"resourceId":"R","priority":3 })"; + +static auto RESOURCE_VALID = R"({"id":"R","size":1 ,"name":"file"})"; +static auto RESOURCE_EMPTY_ID = R"({"id":"" ,"size":1 ,"name":"file"})"; +static auto RESOURCE_MISSING_ID = R"({ "size":1 ,"name":"file"})"; +static auto RESOURCE_EMPTY_SIZE = R"({"id":"R","size":"","name":"file"})"; +static auto RESOURCE_MISSING_SIZE = R"({"id":"R", "name":"file"})"; +static auto RESOURCE_EMPTY_NAME = R"({"id":"R","size":1 ,"name":"" })"; +static auto RESOURCE_MISSING_NAME = R"({"id":"R","size":1 })"; +// clang-format on + +static size_t RESOURCE_SIZE = 1; +static const string RESOURCE_NAME = "file"; +static const string RESOURCE_ID = "R"; +static const string RESOURCE_METADATA_JSON = "metadata.json"; + +struct MetadataFileState { + string requester; + string resource; + bool loadsSuccessfully; + string description; +}; + +class InitTest + : public AssetManagerTest + , public WithParamInterface { +public: + void SetUp() override { + AssetManagerTest::SetUp(); + artifact.commsHandler = commsHandler; + uploadArtifact(); + ASSERT_TRUE(assetManager->downloadArtifact(artifact.request)); + ASSERT_TRUE(artifact.waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(artifact.hasAllProps()); + } + + void uploadArtifact() { + filesystem::makeDirectory(TESTING_DIRECTORY); + auto file = TESTING_DIRECTORY + "/" + RESOURCE_NAME; + ofstream(file, ios::trunc) << string(RESOURCE_SIZE, 'a'); + auto davsRequest = static_pointer_cast(artifact.request); + if (davsRequest != nullptr) { + service.uploadBinaryArtifact( + davsRequest->getType(), davsRequest->getKey(), davsRequest->getFilters(), file, seconds(10)); + } + } + + ArtifactUnderTest artifact{nullptr, RequestFactory::create(REQUESTER_VALID)}; +}; + +TEST_P(InitTest, AssetManagerRestarts) { // NOLINT + auto& p = GetParam(); + shutdownAssetManager(); + + auto requesterFile = DAVS_REQUESTS_DIR + "/" + filesystem::list(DAVS_REQUESTS_DIR)[0]; + ofstream(requesterFile, ios::trunc) << p.requester; + + // do this because resources will derive the ID from the directory name if metadata.json fails or is not found, and + // we're using id R + auto forcedResourceIdPath = DAVS_RESOURCES_DIR + "/" + RESOURCE_ID; + filesystem::move( + DAVS_RESOURCES_DIR + "/" + filesystem::list(DAVS_RESOURCES_DIR, filesystem::FileType::DIRECTORY)[0], + forcedResourceIdPath); + ofstream(forcedResourceIdPath + "/" + RESOURCE_METADATA_JSON, ios::trunc) << p.resource; + + // do the same for the file inside the resource + auto fileList = filesystem::list(forcedResourceIdPath); + fileList.erase(remove(fileList.begin(), fileList.end(), RESOURCE_METADATA_JSON), fileList.end()); + filesystem::move(forcedResourceIdPath + "/" + fileList[0], forcedResourceIdPath + "/" + RESOURCE_NAME); + + startAssetManager(); + ASSERT_TRUE(p.loadsSuccessfully == waitUntil([this] { return artifact.hasPathProp(); }, milliseconds(10))); + ASSERT_TRUE(p.loadsSuccessfully == artifact.hasPriorityProp()); + ASSERT_TRUE(p.loadsSuccessfully == artifact.hasStateProp()); + if (p.loadsSuccessfully) { + ASSERT_TRUE(filesystem::exists(forcedResourceIdPath)); + ASSERT_TRUE(filesystem::exists(forcedResourceIdPath + "/" + RESOURCE_METADATA_JSON)); + ASSERT_EQ(forcedResourceIdPath + "/" + RESOURCE_NAME, artifact.getPathProp()); + ASSERT_EQ(RESOURCE_SIZE, filesystem::sizeOf(artifact.getPathProp())); + + Document document; + ifstream ifs(forcedResourceIdPath + "/" + RESOURCE_METADATA_JSON); + IStreamWrapper is(ifs); + ASSERT_FALSE(document.ParseStream(is).HasParseError()); + + ASSERT_EQ(document["id"].GetString(), RESOURCE_ID); + ASSERT_EQ(document["name"].GetString(), RESOURCE_NAME); + ASSERT_EQ(document["size"].GetUint64(), RESOURCE_SIZE); + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(RequestersTestCases, InitTest, ValuesIn>( + // Requester file to be loaded | Resource file || Will succeed? | Description + {{REQUESTER_VALID , RESOURCE_VALID , WILL_LOAD , "Loading a valid requester will succeed"}, + {REQUESTER_EMPTY_TYPE , RESOURCE_VALID , WILL_BE_ERASED , "Loading requester with empty type will fail"}, + {REQUESTER_MISSING_TYPE , RESOURCE_VALID , WILL_BE_ERASED , "Loading requester with missing type will fail"}, + {REQUESTER_EMPTY_KEY , RESOURCE_VALID , WILL_BE_ERASED , "Loading requester with empty key will fail"}, + {REQUESTER_MISSING_KEY , RESOURCE_VALID , WILL_BE_ERASED , "Loading requester with missing key will fail"}, + {REQUESTER_EMPTY_FILTER_KEY , RESOURCE_VALID , WILL_BE_ERASED , "Loading requester with empty filter_key will fail"}, + {REQUESTER_INVALID_FILTER_VALUE , RESOURCE_VALID , WILL_BE_ERASED , "Loading requester with invalid filter_value will fail"}, + {REQUESTER_MISSING_FILTER_VALUE , RESOURCE_VALID , WILL_BE_ERASED , "Loading requester with missing filter_value will fail"}, + {REQUESTER_INVALID_ENDPOINT , RESOURCE_VALID , WILL_LOAD , "Loading requester with invalid endpoint will succeed"}, + {REQUESTER_EMPTY_ENDPOINT , RESOURCE_VALID , WILL_LOAD , "Loading requester with empty endpoint will succeed"}, + {REQUESTER_MISSING_ENDPOINT , RESOURCE_VALID , WILL_LOAD , "Loading requester with missing endpoint will succeed"}, + {REQUESTER_INVALID_UNPACK , RESOURCE_VALID , WILL_LOAD , "Loading requester with invalid unpack will succeed"}, + {REQUESTER_MISSING_UNPACK , RESOURCE_VALID , WILL_LOAD , "Loading requester with missing unpack will succeed"}, + {REQUESTER_EMPTY_RESOURCE_ID , RESOURCE_VALID , WILL_BE_ERASED , "Loading requester with empty resource id will fail"}, + {REQUESTER_MISSING_RESOURCE_ID , RESOURCE_VALID , WILL_BE_ERASED , "Loading requester with missing resource id will fail"}, + {REQUESTER_INVALID_PRIORITY , RESOURCE_VALID , WILL_LOAD , "Loading requester with invalid priority will succeed"}, + {REQUESTER_EMPTY_PRIORITY , RESOURCE_VALID , WILL_LOAD , "Loading requester with empty priority will succeed"}, + {REQUESTER_MISSING_PRIORITY , RESOURCE_VALID , WILL_LOAD , "Loading requester with missing priority will succeed"}, + {REQUESTER_EMPTY_TIMESTAMP , RESOURCE_VALID , WILL_LOAD , "Loading requester with empty timestamp will succeed"}, + {REQUESTER_MISSING_TIMESTAMP , RESOURCE_VALID , WILL_LOAD , "Loading requester with missing timestamp will succeed"} + }), PrintDescription()); + +INSTANTIATE_TEST_CASE_P(ResourceTestCases, InitTest, ValuesIn>( + // Requester file | Resource file to be loaded || Will succeed? | Description + {{REQUESTER_VALID , RESOURCE_VALID , WILL_LOAD , "Loading a valid resource will succeed"}, + {REQUESTER_VALID , RESOURCE_EMPTY_ID , WILL_LOAD , "Loading a resource with empty id will succeed"}, + {REQUESTER_VALID , RESOURCE_MISSING_ID , WILL_LOAD , "Loading a resource with missing id will succeed"}, + {REQUESTER_VALID , RESOURCE_EMPTY_SIZE , WILL_LOAD , "Loading a resource with empty size will succeed"}, + {REQUESTER_VALID , RESOURCE_MISSING_SIZE , WILL_LOAD , "Loading a resource with missing size will succeed"}, + {REQUESTER_VALID , RESOURCE_EMPTY_NAME , WILL_LOAD , "Loading a resource with empty name will succeed"}, + {REQUESTER_VALID , RESOURCE_MISSING_NAME , WILL_LOAD , "Loading a resource with missing name will succeed"} + }), PrintDescription()); +// clang-format on \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerSharedResourceTest.cpp b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerSharedResourceTest.cpp new file mode 100644 index 0000000000..7f901af813 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerSharedResourceTest.cpp @@ -0,0 +1,122 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "AssetManagerTest.h" + +class SharedResourceTest : public AssetManagerTest { +public: + void SetUp() override { + AssetManagerTest::SetUp(); + + sameId_1.commsHandler = commsHandler; + sameId_2.commsHandler = commsHandler; + differentArtifact.commsHandler = commsHandler; + + uploadArtifactFromRequest(sameId_1.request, artifactSize, tarId); + uploadArtifactFromRequest(sameId_2.request, artifactSize, tarId); + uploadArtifactFromRequest(differentArtifact.request, artifactSize); + } + + // clang-format off + size_t artifactSize = 10; + string tarId = "tarid"; + ArtifactUnderTest sameId_1{nullptr, DavsRequest::create("test", "tar", {{"filter", {"value1"}}}, Region::NA, ArtifactRequest::UNPACK)}; + ArtifactUnderTest sameId_2{nullptr, DavsRequest::create("test", "tar", {{"filter", {"value2"}}}, Region::NA, ArtifactRequest::UNPACK)}; + ArtifactUnderTest differentArtifact{nullptr, DavsRequest::create("different", "tar", {{"filterX", {"valueY"}}}, Region::NA, ArtifactRequest::UNPACK)}; + // clang-format on +}; + +TEST_F(SharedResourceTest, RequestingTheSameArtifactWithDifferentRequestDedups) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(sameId_1.request)); + ASSERT_TRUE(sameId_1.waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(sameId_1.hasAllProps()); + + auto path = sameId_1.getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + ASSERT_TRUE(assetManager->downloadArtifact(sameId_2.request)); + ASSERT_TRUE(sameId_2.waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(sameId_2.hasAllProps()); + + ASSERT_EQ(path, sameId_2.getPathProp()); + ASSERT_TRUE(filesystem::exists(path)); +} + +TEST_F(SharedResourceTest, + DeletingRequestWithSharedResourceDoesNotDeleteResourceUntilAllRequestsAreDeleted) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(sameId_1.request)); + ASSERT_TRUE(sameId_1.waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(assetManager->downloadArtifact(sameId_2.request)); + ASSERT_TRUE(sameId_2.waitUntilStateEquals(State::LOADED)); + + auto path = sameId_1.getPathProp(); + ASSERT_EQ(path, sameId_2.getPathProp()); + ASSERT_TRUE(filesystem::exists(path)); + + assetManager->deleteArtifact(sameId_2.request->getSummary()); + ASSERT_TRUE(waitUntil([this] { return !sameId_2.hasStateProp(); })); + ASSERT_FALSE(sameId_2.hasPathProp()); + ASSERT_TRUE(filesystem::exists(path)); + + assetManager->deleteArtifact(sameId_1.request->getSummary()); + ASSERT_TRUE(waitUntil([this] { return !sameId_1.hasStateProp(); })); + ASSERT_FALSE(sameId_1.hasPathProp()); + ASSERT_FALSE(filesystem::exists(path)); +} + +TEST_F(SharedResourceTest, ReloadingExistingArtifacts) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(sameId_1.request)); + ASSERT_TRUE(sameId_1.waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(assetManager->downloadArtifact(sameId_2.request)); + ASSERT_TRUE(sameId_2.waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(assetManager->downloadArtifact(differentArtifact.request)); + ASSERT_TRUE(differentArtifact.waitUntilStateEquals(State::LOADED)); + + auto samePath = sameId_1.getPathProp(); + ASSERT_EQ(samePath, sameId_2.getPathProp()); + ASSERT_TRUE(filesystem::exists(samePath)); + auto differentPath = differentArtifact.getPathProp(); + ASSERT_TRUE(filesystem::exists(differentPath)); + + shutdownAssetManager(); + startAssetManager(); + + ASSERT_EQ(samePath, sameId_1.getPathProp()); + ASSERT_EQ(sameId_1.getPathProp(), sameId_1.getPathProp()); + ASSERT_EQ(differentPath, differentArtifact.getPathProp()); + ASSERT_EQ(sameId_1.getStateProp(), State::LOADED); + ASSERT_EQ(sameId_2.getStateProp(), State::LOADED); + ASSERT_EQ(differentArtifact.getStateProp(), State::LOADED); +} + +TEST_F(SharedResourceTest, ClearingSpaceAccountsForSharedResource) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(sameId_1.request)); + ASSERT_TRUE(sameId_1.waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(assetManager->downloadArtifact(sameId_2.request)); + ASSERT_TRUE(sameId_2.waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(assetManager->downloadArtifact(differentArtifact.request)); + ASSERT_TRUE(differentArtifact.waitUntilStateEquals(State::LOADED)); + + // have the order from oldest to newest: sameId_2, different, sameId_1 + auto same2Path = sameId_2.getPathProp(); + auto differentPath = differentArtifact.getPathProp(); + auto same1Path = sameId_1.getPathProp(); + + ASSERT_TRUE(assetManager->freeUpSpace(artifactSize)); + + ASSERT_TRUE(filesystem::exists(same1Path)); + ASSERT_TRUE(filesystem::exists(same2Path)); + ASSERT_FALSE(filesystem::exists(differentPath)); +} \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerTest.cpp b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerTest.cpp new file mode 100644 index 0000000000..d4cdb3ff21 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerTest.cpp @@ -0,0 +1,297 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "AssetManagerTest.h" + +static const auto TIMEOUT = milliseconds(100); + +TEST_F(AssetManagerTest, InvalidParameters) { // NOLINT + // clang-format off + ASSERT_TRUE(nullptr == AssetManager::create(nullptr, davsClient, BASE_DIR, authDelegateMock,allowUrlList)); + ASSERT_TRUE(nullptr == AssetManager::create(commsHandler, nullptr, BASE_DIR, authDelegateMock,allowUrlList)); + ASSERT_TRUE(nullptr == AssetManager::create(commsHandler, davsClient, "", authDelegateMock,allowUrlList)); + ASSERT_TRUE(nullptr == AssetManager::create(commsHandler, davsClient, "/", authDelegateMock,allowUrlList)); + ASSERT_TRUE(nullptr == AssetManager::create(commsHandler,davsClient,"/non/existing/directory",authDelegateMock,allowUrlList)); + ASSERT_TRUE(nullptr ==AssetManager::create(commsHandler, davsClient, BASE_DIR, nullptr,allowUrlList)); + ASSERT_TRUE(nullptr ==AssetManager::create(commsHandler, davsClient, BASE_DIR, authDelegateMock,nullptr)); + + // clang-format on +} + +TEST_F(AssetManagerTest, DavsInvalidMetadataJsonFileOnLoad) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarArtifact->hasAllProps()); + + auto path = tarArtifact->getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + shutdownAssetManager(); + // Deleting all metadata files ensures invalid metadata on load + filesystem::removeAll(DAVS_REQUESTS_DIR + "/" + filesystem::list(DAVS_REQUESTS_DIR)[0]); + startAssetManager(); + + ASSERT_FALSE(tarArtifact->hasPathProp()); + ASSERT_FALSE(filesystem::exists(path)); +} + +TEST_F(AssetManagerTest, DavsRequestingValidDownloadUpdatesLipcProprtyAndDownloadsArtifactsToDisk) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarArtifact->hasAllProps()); + + ASSERT_EQ(tarArtifact->getPriorityProp(), Priority::UNUSED); + ASSERT_TRUE(filesystem::exists(tarArtifact->getPathProp() + "/target")); +} + +TEST_F(AssetManagerTest, + DavsRequestingValidButUnavailableArtifactSucceedsDownloadCallButLipcUpdatesAsInvalid) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(unavailableArtifact->request)); + ASSERT_TRUE(unavailableArtifact->hasAllProps()); + ASSERT_FALSE(unavailableArtifact->waitUntilStateEquals(State::LOADED, TIMEOUT)); + + ASSERT_FALSE(unavailableArtifact->hasStateProp()); + ASSERT_FALSE(unavailableArtifact->hasPriorityProp()); +} + +TEST_F(AssetManagerTest, DavsDownloadingTheSameArtifactDedups) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarArtifact->hasAllProps()); + + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_EQ(tarArtifact->getStateProp(), State::LOADED); +} + +TEST_F(AssetManagerTest, DavsRestartingAssetManagerAfterDownloadingDavsArtifactReloadsItFromDisk) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarArtifact->hasAllProps()); + ASSERT_TRUE(filesystem::exists(tarArtifact->getPathProp() + "/target")); + + shutdownAssetManager(); + ASSERT_FALSE(tarArtifact->hasStateProp()); + ASSERT_FALSE(tarArtifact->hasPriorityProp()); + ASSERT_FALSE(tarArtifact->hasPathProp()); + + startAssetManager(); + ASSERT_EQ(tarArtifact->getStateProp(), State::LOADED); + ASSERT_EQ(tarArtifact->getPriorityProp(), Priority::UNUSED); + ASSERT_TRUE(filesystem::exists(tarArtifact->getPathProp() + "/target")); +} +TEST_F(AssetManagerTest, DavsDeletingAnExistingArtifactRemovesItsPropertiesAndSendsAnInvalidStateEvent) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarArtifact->hasAllProps()); + auto path = tarArtifact->getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + assetManager->deleteArtifact(tarArtifact->request->getSummary()); + + ASSERT_TRUE(waitUntil([this] { return !tarArtifact->hasStateProp(); })); + ASSERT_FALSE(tarArtifact->hasStateProp()); + ASSERT_FALSE(tarArtifact->hasPriorityProp()); + ASSERT_FALSE(tarArtifact->hasPathProp()); + ASSERT_FALSE(filesystem::exists(path)); +} + +TEST_F(AssetManagerTest, DavsDeletingAnInvalidArtifactDoesNotImpactExistingArtifacts) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarArtifact->hasAllProps()); + + auto path = tarArtifact->getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + // deleting invalid request should have no impact on the results of this test and should be handled gracefully + assetManager->deleteArtifact(""); + assetManager->deleteArtifact("{validRequest:false}"); + + ASSERT_FALSE(waitUntil([this] { return !tarArtifact->hasStateProp(); }, TIMEOUT)); + ASSERT_TRUE(tarArtifact->hasStateProp()); + ASSERT_TRUE(tarArtifact->hasPriorityProp()); + ASSERT_TRUE(tarArtifact->hasPathProp()); + ASSERT_TRUE(filesystem::exists(path)); +} + +TEST_F(AssetManagerTest, DavsRequestingDownloadOfDeletedArtifactSucceeds) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarArtifact->hasAllProps()); + auto path = tarArtifact->getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + assetManager->deleteArtifact(tarArtifact->request->getSummary()); + ASSERT_TRUE(waitUntil([this] { return !tarArtifact->hasStateProp(); })); + ASSERT_FALSE(filesystem::exists(path)); + + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(filesystem::exists(path)); +} + +TEST_F(AssetManagerTest, PropogatesIdleState) { // NOLINT + assetManager->onIdleChanged(1); + ASSERT_TRUE(waitUntil([&] { return davsClient->getIdleState(); })); + assetManager->onIdleChanged(0); + ASSERT_TRUE(waitUntil([&] { return !davsClient->getIdleState(); })); +} + +TEST_F(AssetManagerTest, DavsDownloadWhileDeviceActive) { // NOLINT + assetManager->onIdleChanged(0); + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarArtifact->hasAllProps()); + + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_EQ(tarArtifact->getStateProp(), State::LOADED); +} + +TEST_F(AssetManagerTest, UrlInvalidMetadataJsonFileOnLoad) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarUrlArtifact->request)); + ASSERT_TRUE(tarUrlArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarUrlArtifact->hasAllProps()); + + auto path = tarUrlArtifact->getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + shutdownAssetManager(); + // Deleting all metadata files ensures invalid metadata on load + filesystem::removeAll(DAVS_REQUESTS_DIR + "/" + filesystem::list(DAVS_REQUESTS_DIR)[0]); + startAssetManager(); + + ASSERT_FALSE(tarUrlArtifact->hasPathProp()); + ASSERT_FALSE(filesystem::exists(path)); +} + +TEST_F(AssetManagerTest, UrlRequestingValidDownloadUpdatesLipcProprtyAndDownloadsArtifactsToDisk) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarUrlArtifact->request)); + ASSERT_TRUE(tarUrlArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarUrlArtifact->hasAllProps()); + + ASSERT_EQ(tarUrlArtifact->getPriorityProp(), Priority::UNUSED); + ASSERT_TRUE(filesystem::exists(tarUrlArtifact->getPathProp())); +} +TEST_F(AssetManagerTest, UrlRequestingUnavailableArtifactSucceedsDownloadCallButLipcUpdatesAsInvalid) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(unavailableUrlArtifact->request)); + ASSERT_FALSE(unavailableUrlArtifact->waitUntilStateEquals(State::LOADED, TIMEOUT)); + + ASSERT_FALSE(unavailableUrlArtifact->hasStateProp()); + ASSERT_FALSE(unavailableUrlArtifact->hasPriorityProp()); + + unavailableUrlArtifact->resetCounts(); +} +TEST_F(AssetManagerTest, UrlRequestingDownloadOfHttpArtifactSucceedsDownloadCallButLipcUpdatesAsInvalid) { // NOLINT + ASSERT_FALSE(assetManager->downloadArtifact(httpUrlArtifact->request)); + ASSERT_FALSE(httpUrlArtifact->waitUntilStateEquals(State::LOADED, TIMEOUT)); + + ASSERT_FALSE(httpUrlArtifact->hasStateProp()); + ASSERT_FALSE(httpUrlArtifact->hasPriorityProp()); +} +TEST_F(AssetManagerTest, UrlRequestingDownloadOfNonApprovedArtifactFailsDownloadCall) { // NOLINT + ASSERT_FALSE(assetManager->downloadArtifact(nonApprovedUrlArtifact->request)); + ASSERT_TRUE(waitUntil([this] { return !nonApprovedUrlArtifact->hasStateProp(); }, TIMEOUT)); + + ASSERT_FALSE(nonApprovedUrlArtifact->waitUntilStateEquals(State::LOADED, TIMEOUT)); + + ASSERT_FALSE(nonApprovedUrlArtifact->hasStateProp()); + ASSERT_FALSE(nonApprovedUrlArtifact->hasPriorityProp()); +} + +TEST_F(AssetManagerTest, UrlDownloadingTheSameArtifactDedups) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_TRUE(tarArtifact->waitUntilStateEquals(State::LOADED)); + + ASSERT_TRUE(assetManager->downloadArtifact(tarArtifact->request)); + ASSERT_EQ(tarArtifact->getStateProp(), State::LOADED); +} + +TEST_F(AssetManagerTest, UrlRestartingAssetManagerAfterDownloadingUrlArtifactReloadsItFromDisk) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarUrlArtifact->request)); + ASSERT_TRUE(tarUrlArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarUrlArtifact->hasAllProps()); + ASSERT_TRUE(filesystem::exists(tarUrlArtifact->getPathProp())); + + shutdownAssetManager(); + ASSERT_FALSE(tarUrlArtifact->hasStateProp()); + ASSERT_FALSE(tarUrlArtifact->hasPriorityProp()); + ASSERT_FALSE(tarUrlArtifact->hasPathProp()); + + startAssetManager(); + ASSERT_EQ(tarUrlArtifact->getStateProp(), State::LOADED); + ASSERT_EQ(tarUrlArtifact->getPriorityProp(), Priority::UNUSED); + ASSERT_TRUE(filesystem::exists(tarUrlArtifact->getPathProp())); +} + +TEST_F(AssetManagerTest, UrlDeletingAnExistingArtifactRemovesItsPropertiesAndSendsAnInvalidStateEvent) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarUrlArtifact->request)); + ASSERT_TRUE(tarUrlArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarUrlArtifact->hasAllProps()); + auto path = tarUrlArtifact->getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + tarUrlArtifact->subscribeToChangeEvents(); + assetManager->deleteArtifact(tarUrlArtifact->request->getSummary()); + + ASSERT_TRUE(waitUntil([this] { return !tarUrlArtifact->hasStateProp(); })); + ASSERT_FALSE(tarUrlArtifact->hasPriorityProp()); + ASSERT_FALSE(tarUrlArtifact->hasPathProp()); + ASSERT_EQ(tarUrlArtifact->stateMap[State::INVALID], 1); + ASSERT_FALSE(filesystem::exists(path)); + tarUrlArtifact->resetCounts(); +} + +TEST_F(AssetManagerTest, UrlDeletingAnInvalidArtifactDoesNotImpactExistingArtifacts) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarUrlArtifact->request)); + ASSERT_TRUE(tarUrlArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarUrlArtifact->hasAllProps()); + auto path = tarUrlArtifact->getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + // deleting invalid request should have no impact on the results of this test and should be handled gracefully + assetManager->deleteArtifact(""); + assetManager->deleteArtifact("{validRequest:false}"); + + ASSERT_FALSE(waitUntil([this] { return !tarUrlArtifact->hasStateProp(); }, TIMEOUT)); + ASSERT_TRUE(tarUrlArtifact->hasPriorityProp()); + ASSERT_TRUE(tarUrlArtifact->hasPathProp()); + ASSERT_TRUE(filesystem::exists(path)); +} + +TEST_F(AssetManagerTest, UrlRequestingDownloadOfDeletedArtifactSucceeds) { // NOLINT + ASSERT_TRUE(assetManager->downloadArtifact(tarUrlArtifact->request)); + ASSERT_TRUE(tarUrlArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarUrlArtifact->hasAllProps()); + auto path = tarUrlArtifact->getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + assetManager->deleteArtifact(tarUrlArtifact->request->getSummary()); + ASSERT_TRUE(waitUntil([this] { return !tarUrlArtifact->hasStateProp(); })); + ASSERT_FALSE(filesystem::exists(path)); + + ASSERT_TRUE(assetManager->downloadArtifact(tarUrlArtifact->request)); + ASSERT_TRUE(tarUrlArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(filesystem::exists(path)); +} + +TEST_F(AssetManagerTest, UrlDownloadWhileDeviceActive) { // NOLINT + assetManager->onIdleChanged(0); + ASSERT_TRUE(assetManager->downloadArtifact(tarUrlArtifact->request)); + ASSERT_TRUE(tarUrlArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(tarUrlArtifact->hasAllProps()); + + ASSERT_TRUE(assetManager->downloadArtifact(tarUrlArtifact->request)); + ASSERT_EQ(tarUrlArtifact->getStateProp(), State::LOADED); +} \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerTest.h b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerTest.h new file mode 100644 index 0000000000..a86f71c717 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerTest.h @@ -0,0 +1,179 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 AVS_CAPABILITIES_ASSETMANAGER_ACSDKASSETMANAGER_TEST_ASSETMANAGERTEST_H_ +#define AVS_CAPABILITIES_ASSETMANAGER_ACSDKASSETMANAGER_TEST_ASSETMANAGERTEST_H_ + +#include "ArtifactUnderTest.h" +#include "AuthDelegateMock.h" +#include "InternetConnectionMonitorMock.h" +#include "RequesterMetadata.h" +#include "TestUtil.h" +#include "acsdkAssetsInterfaces/Communication/InMemoryAmdCommunicationHandler.h" +#include "archive.h" +#include "archive_entry.h" + +using namespace alexaClientSDK::acsdkAssets::common; +using namespace alexaClientSDK::acsdkAssets::commonInterfaces; +using namespace alexaClientSDK::acsdkAssets::davsInterfaces; +using namespace alexaClientSDK::acsdkAssets::davs; +using namespace alexaClientSDK::acsdkAssets::client; +using namespace alexaClientSDK::acsdkAssets::manager; +using namespace alexaClientSDK::avsCommon::utils; + +class AssetManagerTest : public Test { +public: + void SetUp() override { + TMP_DIR = createTmpDir("AssetManager"); + TESTING_DIRECTORY = TMP_DIR + "/davs_testing"; + BASE_DIR = TMP_DIR + "/davs"; + DAVS_TMP = TMP_DIR + "/davstmp"; + DAVS_RESOURCES_DIR = BASE_DIR + "/resources"; + DAVS_REQUESTS_DIR = BASE_DIR + "/requests"; + URL_RESOURCES_DIR = "/tmp/urlResources"; + filesystem::makeDirectory(URL_RESOURCES_DIR); + + commsHandler = InMemoryAmdCommunicationHandler::create(); + + tarArtifact->commsHandler = commsHandler; + unavailableArtifact->commsHandler = commsHandler; + tarUrlArtifact->commsHandler = commsHandler; + unavailableUrlArtifact->commsHandler = commsHandler; + httpUrlArtifact->commsHandler = commsHandler; + nonApprovedUrlArtifact->commsHandler = commsHandler; + + authDelegateMock = AuthDelegateMock::create(); + // clang-format off + allowUrlList = UrlAllowListWrapper::create( + {"https://s3.amazonaws.com/alexareminderservice.prod.usamazon.reminder.earcons/echo_system_alerts_reminder_start_v", + "https://tinytts.amazon.com/", + "https://tinytts-eu-west-1.amazon.com/", + "https://tinytts-us-west-2.amazon.com/", + "test://"}); + // clang-format on + + CurlWrapperMock::useDavsService = true; + CurlWrapperMock::downloadShallFail = false; + + uploadArtifactFromRequest(tarArtifact->request); + uploadArtifactFromRequest(tarUrlArtifact->request, 100); + + wifiMonitorMock = InternetConnectionMonitorMock::create(); + davsEndpointHandler = DavsEndpointHandlerV3::create("123"); + startAssetManager(); + } + + void TearDown() override { + shutdownAssetManager(); + filesystem::removeAll(URL_RESOURCES_DIR); + filesystem::removeAll(TMP_DIR); + } + + void startAssetManager() { + if (assetManager != nullptr) { + return; + } + davsClient = DavsClient::create(DAVS_TMP, authDelegateMock, wifiMonitorMock, davsEndpointHandler); + assetManager = AssetManager::create(commsHandler, davsClient, BASE_DIR, authDelegateMock, allowUrlList); + ASSERT_NE(assetManager, nullptr); + assetManager->onIdleChanged(1); + } + + void shutdownAssetManager() { + if (assetManager == nullptr) { + return; + } + assetManager.reset(); + } + + void uploadArtifactFromRequest( + const shared_ptr& request, + size_t size = 1, + const string& id = "", + milliseconds ttlDelta = minutes(60)) { + filesystem::makeDirectory(TESTING_DIRECTORY); + auto metadata = RequesterMetadata::create(request); + auto type = metadata->getRequest()->getRequestType(); + if (type == Type::DAVS) { + auto davsRequest = static_pointer_cast(request); + if (davsRequest != nullptr) { + service.uploadBinaryArtifact( + davsRequest->getType(), + davsRequest->getKey(), + davsRequest->getFilters(), + createTarFile(TESTING_DIRECTORY, "target", size), + ttlDelta, + id); + } + } else if (type == Type::URL) { + auto urlRequest = static_pointer_cast(request); + if (urlRequest != nullptr) { + createTarFile(URL_RESOURCES_DIR, "urlTarget", 1); + } + } + } + + static string createTarFile(const string& dir, const string& filename, size_t size = 1) { + auto tarPath = dir + "/" + filename + ".tar.gz"; + auto data = string(size, 'a'); + + auto a = archive_write_new(); + archive_write_add_filter_gzip(a); + archive_write_set_format_pax_restricted(a); + archive_write_open_filename(a, tarPath.c_str()); + + auto entry = archive_entry_new(); + archive_entry_set_pathname(entry, filename.c_str()); + archive_entry_set_size(entry, static_cast(data.size())); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + archive_write_header(a, entry); + archive_write_data(a, data.c_str(), data.size()); + archive_entry_free(entry); + + archive_write_close(a); + archive_write_free(a); + return tarPath; + } + + string TMP_DIR; + string TESTING_DIRECTORY; + string BASE_DIR; + string DAVS_TMP; + string DAVS_RESOURCES_DIR; + string DAVS_REQUESTS_DIR; + string URL_RESOURCES_DIR; + string URL_WORKING_DIR; + + DavsServiceMock service; + shared_ptr davsClient; + shared_ptr assetManager; + shared_ptr authDelegateMock; + shared_ptr wifiMonitorMock; + shared_ptr davsEndpointHandler; + shared_ptr allowUrlList; + shared_ptr commsHandler; + + // clang-format off + shared_ptr tarArtifact = make_shared( nullptr, DavsRequest::create("test", "tar", {{"filter1", {"value1"}}, {"filter2", {"value2"}}}, Region::NA, ArtifactRequest::UNPACK)); + shared_ptr unavailableArtifact = make_shared( nullptr, DavsRequest::create("test", "not_found", {{"filter1", {"value1"}}}, Region::NA, ArtifactRequest::UNPACK)); + shared_ptr tarUrlArtifact = make_shared(nullptr, UrlRequest::create("test:///tmp/urlResources/urlTarget.tar.gz", "urlArtifact", ArtifactRequest::UNPACK)); + shared_ptr unavailableUrlArtifact = make_shared( nullptr, UrlRequest::create("test:///unavailableUrlArtifact", "unavailableUrlArtifact")); + shared_ptr httpUrlArtifact = make_shared(nullptr, UrlRequest::create("http://tinytts.amazon.com/", "httpUrlArtifact")); + shared_ptr nonApprovedUrlArtifact = make_shared(nullptr, UrlRequest::create("https://evil.com/", "nonApprovedUrlArtifact")); + // clang-format on +}; + +#endif // AVS_CAPABILITIES_ASSETMANAGER_ACSDKASSETMANAGER_TEST_ASSETMANAGERTEST_H_ diff --git a/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerUpdateTest.cpp b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerUpdateTest.cpp new file mode 100644 index 0000000000..d67175125a --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/test/AssetManagerUpdateTest.cpp @@ -0,0 +1,224 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "AssetManagerTest.h" + +// Two valid artifact json will be tested by Integ Test +constexpr auto VALID_ONE_ARTIFACT_JSON = R"delim({"artifactList":[{"type":"test","key":"tar"}]})delim"; +constexpr auto INVALID_ONE_ARTIFACT_JSON = + R"delim({"artifactList":[{"type":"test-invalid","key":"tar-invalid"}]})delim"; + +class UpdateTest + : public AssetManagerTest + , public WithParamInterface { +public: + void SetUp() override { + AssetManagerTest::SetUp(); + + artifact->commsHandler = commsHandler; + + uploadArtifactFromRequest(artifact->request, artifactSize, origId, ttl); + ASSERT_TRUE(assetManager->downloadArtifact(artifact->request)); + ASSERT_TRUE(artifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(artifact->hasAllProps()); + auto path = artifact->getPathProp(); + ASSERT_TRUE(filesystem::exists(path)); + + updateAccepted = GetParam(); + updateRejected = !updateAccepted; + oldPath = artifact->getPathProp(); + newPath = replaceAll(oldPath, origId, updatedId); + } + + void uploadArtifactAndSubscribeToChange( + const shared_ptr& artifactUnderTest, + const Priority priority, + string& updatedIdProp) { + uploadArtifactFromRequest(artifactUnderTest->request, artifactSize, updatedIdProp); + artifactUnderTest->setPriorityProp(priority); + // only after we've set the priority accordingly and have gone through the update request + artifactUnderTest->subscribeToChangeEvents(); + } + + void checkArtifactUpdatedOnce( + const shared_ptr& artifactUnderTest, + bool& updateAcceptedFlag, + bool& updateRejectedFlag, + string& oldPathProp, + string& newPathProp) { + ASSERT_TRUE(waitUntil([&] { return filesystem::exists(newPathProp); }, milliseconds(ttl * 10))); + assetManager->handleUpdate(artifactUnderTest->request->getSummary(), updateAcceptedFlag); + ASSERT_EQ(updateAccepted ? newPathProp : oldPathProp, artifactUnderTest->getPathProp()); + ASSERT_EQ(filesystem::exists(oldPathProp), updateRejectedFlag); + ASSERT_EQ(filesystem::exists(newPathProp), updateAcceptedFlag); + ASSERT_EQ(artifactUnderTest->updateEventCount, 1); + } + + string replaceAll(string str, const string& from, const string& to) { + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + return str; + } + + milliseconds ttl = milliseconds(500); + size_t artifactSize = 10; + string origId = "original"; + string updatedId = "updated_id"; + + bool updateAccepted{}; + bool updateRejected{}; + string oldPath; + string newPath; + shared_ptr artifact = + make_shared(nullptr, DavsRequest::create("test", "tar", {{"filter", {"first"}}})); +}; + +TEST_P(UpdateTest, UpdatingArtifactsDeletesTheOldResourceAndAcquiresTheNew) { // NOLINT + uploadArtifactFromRequest(artifact->request, artifactSize, updatedId); + + // nothing should happen when requesting update for invalid states or when artifact isn't ready + assetManager->handleUpdate("", updateAccepted); + assetManager->handleUpdate("{validRequest:false}", updateAccepted); + assetManager->handleUpdate(artifact->request->getSummary(), updateAccepted); + + artifact->subscribeToChangeEvents(); + // only after we've set the priority accordingly and have gone through the update request + artifact->setPriorityProp(Priority::ACTIVE); + + ASSERT_TRUE(waitUntil([&] { return filesystem::exists(newPath); }, ttl * 10)); + ASSERT_TRUE(filesystem::exists(oldPath)); + ASSERT_EQ(oldPath, artifact->getPathProp()); + + assetManager->handleUpdate(artifact->request->getSummary(), updateAccepted); + ASSERT_EQ(updateAccepted ? newPath : oldPath, artifact->getPathProp()); + ASSERT_EQ(filesystem::exists(oldPath), updateRejected); + ASSERT_EQ(filesystem::exists(newPath), updateAccepted); + ASSERT_EQ(artifact->updateEventCount, 1); + artifact->resetCounts(); +} + +TEST_P(UpdateTest, UpdatingArtifactsWillKeepRetryingUntilItTimesOutAndDeletesTheNew) { // NOLINT + uploadArtifactFromRequest(artifact->request, artifactSize, updatedId); + + // nothing should happen when requesting update for invalid states or when artifact isn't ready + assetManager->handleUpdate("", updateAccepted); + assetManager->handleUpdate("{validRequest:false}", updateAccepted); + assetManager->handleUpdate(artifact->request->getSummary(), updateAccepted); + + artifact->subscribeToChangeEvents(); + + // only after we've set the priority accordingly and have gone through the update request + artifact->setPriorityProp(Priority::ACTIVE); + + // expect to send 2 update events when we are not getting a handle update response + ASSERT_TRUE(waitUntil([&] { return filesystem::exists(newPath); }, ttl * 10)); + ASSERT_TRUE(filesystem::exists(oldPath)); + ASSERT_EQ(oldPath, artifact->getPathProp()); + + // after some time, we will delete the new artifact and keep the old + ASSERT_TRUE(waitUntil([&] { return !filesystem::exists(newPath); }, ttl * 10)); + ASSERT_TRUE(filesystem::exists(oldPath)); + ASSERT_EQ(oldPath, artifact->getPathProp()); + ASSERT_EQ(artifact->updateEventCount, 2); + artifact->resetCounts(); +} + +TEST_P(UpdateTest, HandlingSharedArtifactsWhereOneGetsUpdatedDoesNotDeleteOldResource) { // NOLINT + shared_ptr otherArtifact = + make_shared(commsHandler, DavsRequest::create("test", "tar", {{"filter", {"second"}}})); + uploadArtifactFromRequest(otherArtifact->request, artifactSize, origId); + + ASSERT_TRUE(assetManager->downloadArtifact(otherArtifact->request)); + ASSERT_TRUE(otherArtifact->waitUntilStateEquals(State::LOADED)); + ASSERT_TRUE(otherArtifact->hasAllProps()); + + uploadArtifactFromRequest(artifact->request, artifactSize, updatedId); + artifact->subscribeToChangeEvents(); + artifact->setPriorityProp(Priority::ACTIVE); + + ASSERT_TRUE(waitUntil([&] { return filesystem::exists(newPath); }, ttl * 10)); + ASSERT_TRUE(filesystem::exists(oldPath)); + ASSERT_EQ(oldPath, artifact->getPathProp()); + + assetManager->handleUpdate(artifact->request->getSummary(), updateAccepted); + assetManager->handleUpdate(otherArtifact->request->getSummary(), updateAccepted); // nothing should happen here + ASSERT_EQ(updateAccepted ? newPath : oldPath, artifact->getPathProp()); + ASSERT_EQ(filesystem::exists(oldPath), true); // never get rid of the old path since it's being shared + ASSERT_EQ(filesystem::exists(newPath), updateAccepted); + ASSERT_EQ(otherArtifact->getPathProp(), oldPath); + ASSERT_EQ(artifact->updateEventCount, 1); + artifact->resetCounts(); +} + +TEST_P(UpdateTest, CheckingForUpdateAtStartupAfterArtifactBecomesActive) { // NOLINT + int updateCount = 0; + uploadArtifactFromRequest(artifact->request, artifactSize, updatedId); + artifact->setPriorityProp(Priority::ACTIVE); + artifact->subscribeToChangeEvents(); + // artifact->expectUpdateEvent(newPath); + ASSERT_TRUE(waitUntil([&] { return filesystem::exists(newPath); }, ttl * 10)); + + assetManager->handleUpdate(artifact->request->getSummary(), updateAccepted); + ASSERT_EQ(updateAccepted ? newPath : oldPath, artifact->getPathProp()); + ASSERT_EQ(filesystem::exists(oldPath), updateRejected); + ASSERT_EQ(filesystem::exists(newPath), updateAccepted); + + // make sure things are still reflected after reboot + if (updateRejected) { + // note that if we failed to update, DavsClient will recheck with DAVS at bootup + updateCount = 1; + } + ASSERT_EQ(artifact->updateEventCount, 1); + artifact->resetCounts(); + shutdownAssetManager(); + startAssetManager(); + artifact->subscribeToChangeEvents(); + // the new artifact will always be checked and downloaded when changing to active + artifact->setPriorityProp(Priority::ACTIVE); + ASSERT_TRUE(waitUntil([&] { return filesystem::exists(newPath); }, ttl * 10)); + ASSERT_TRUE(waitUntil([&] { return (updateAccepted ? newPath : oldPath) == artifact->getPathProp(); })); + ASSERT_EQ(filesystem::exists(oldPath), updateRejected); + ASSERT_EQ(filesystem::exists(newPath), true); + ASSERT_EQ(artifact->updateEventCount, updateCount); + artifact->resetCounts(); +} +TEST_P(UpdateTest, UpdatingOneActiveArtifactViaPuffinDeviceArtifactNotification) { + uploadArtifactAndSubscribeToChange(artifact, Priority::ACTIVE, updatedId); + + // Trigger update from JSON + davsClient->checkAndUpdateArtifactGroupFromJson(VALID_ONE_ARTIFACT_JSON); + + checkArtifactUpdatedOnce(artifact, updateAccepted, updateRejected, oldPath, newPath); +} +TEST_P(UpdateTest, UpdatingOneInactiveArtifactViaPuffinDeviceArtifactNotification) { + uploadArtifactAndSubscribeToChange(artifact, Priority::UNUSED, updatedId); + + // Trigger update from JSON + davsClient->checkAndUpdateArtifactGroupFromJson(VALID_ONE_ARTIFACT_JSON); + + ASSERT_FALSE(waitUntil([&] { return filesystem::exists(newPath); }, milliseconds(300))); +} +TEST_P(UpdateTest, UpdatingUnregisteredArtifactViaPuffinDeviceArtifactNotification) { + uploadArtifactAndSubscribeToChange(artifact, Priority::UNUSED, updatedId); + + // Trigger update from JSON + davsClient->checkAndUpdateArtifactGroupFromJson(INVALID_ONE_ARTIFACT_JSON); + + ASSERT_FALSE(waitUntil([&] { return filesystem::exists(newPath); }, milliseconds(300))); +} +INSTANTIATE_TEST_CASE_P(UpdatesAcceptedAndRejected, UpdateTest, Values(true, false), PrintToStringParamName()); \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManager/test/CMakeLists.txt b/capabilities/AssetManager/acsdkAssetManager/test/CMakeLists.txt new file mode 100644 index 0000000000..07230a5bb1 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManager/test/CMakeLists.txt @@ -0,0 +1,12 @@ +set(INCLUDE_PATH + "." + "${acsdkAssetManager_SOURCE_DIR}/src" + ) + +set(LIBS + "AVSCommon" + "acsdkAssetManagerForTesting" + "acsdkAssetsMocks" + ) + +discover_unit_tests("${INCLUDE_PATH}" "${LIBS}") \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManagerClient/CMakeLists.txt b/capabilities/AssetManager/acsdkAssetManagerClient/CMakeLists.txt new file mode 100644 index 0000000000..3476bcee80 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClient/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkAssetManagerClient LANGUAGES CXX) + +if (DEFINED AVS_CMAKE_BUILD) + include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +endif () +add_subdirectory("src") diff --git a/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/AMD.h b/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/AMD.h new file mode 100644 index 0000000000..fd35439386 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/AMD.h @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGERCLIENT_AMD_H_ +#define ACSDKASSETMANAGERCLIENT_AMD_H_ + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace client { + +class AMD { +public: + static constexpr const char* PUBLISHER = "com.amazon.assetmgrd"; + static constexpr const char* REGISTER_PROP = "RegisterArtifact"; + static constexpr const char* REMOVE_PROP = "RemoveArtifact"; + static constexpr const char* INITIALIZATION_PROP = "Initialization"; + + static constexpr const char* ACCEPT_UPDATE_PROP = "AcceptUpdate"; + static constexpr const char* REJECT_UPDATE_PROP = "RejectUpdate"; + + static constexpr const char* STATE_SUFFIX = "_State"; + static constexpr const char* PRIORITY_SUFFIX = "_Priority"; + static constexpr const char* PATH_SUFFIX = "_Path"; + static constexpr const char* UPDATE_SUFFIX = "_Update"; +}; + +} // namespace client +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGERCLIENT_AMD_H_ diff --git a/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/ArtifactWrapper.h b/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/ArtifactWrapper.h new file mode 100644 index 0000000000..ba3c3cc2ca --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/ArtifactWrapper.h @@ -0,0 +1,178 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGERCLIENT_ARTIFACTWRAPPER_H_ +#define ACSDKASSETMANAGERCLIENT_ARTIFACTWRAPPER_H_ + +#include + +#include +#include +#include +#include + +#include "acsdkAssetManagerClientInterfaces/ArtifactUpdateValidator.h" +#include "acsdkAssetManagerClientInterfaces/ArtifactWrapperInterface.h" +#include "acsdkAssetsInterfaces/ArtifactRequest.h" +#include "acsdkAssetsInterfaces/Communication/AmdCommunicationInterface.h" +#include "acsdkAssetsInterfaces/Priority.h" +#include "acsdkAssetsInterfaces/State.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace client { + +/** + * This class provides a mechanism for controlling artifacts in asset manager through aipc. + */ +class ArtifactWrapper + : public clientInterfaces::ArtifactWrapperInterface + , public acsdkNotifier::Notifier + , public acsdkCommunicationInterfaces::CommunicationPropertyChangeSubscriber + , public acsdkCommunicationInterfaces::CommunicationPropertyChangeSubscriber { +public: + /** + * Creates an artifact wrapper and request a download and creation on asset manager side. If the creation was + * successful or if the artifact already existed on asset manager, then this will return a valid artifact wrapper to + * manage that artifact. + * + * @param aipcWrapper REQUIRED, for aipc communication with asset manager + * @param request REQUIRED, to uniquely identify the artifact to download or use. + * @param updateValidator OPTIONAL, if none is provided, then the update will always be applied. Otherwise, the + * validator will be used to confirm that the new artifact is valid and should be applied. + * @return NULLABLE, a smart pointer to an artifact wrapper if successfully registered, null otherwise. + */ + static std::shared_ptr create( + const std::shared_ptr& amdComm, + const std::shared_ptr& request, + const std::shared_ptr& updateValidator = nullptr); + + ~ArtifactWrapper() override = default; + + inline bool operator==(const ArtifactWrapper& rhs) const { + return m_request->getSummary() == rhs.m_request->getSummary(); + } + + inline bool operator!=(const ArtifactWrapper& rhs) const { + return !(rhs == *this); + } + + inline std::string name() const override { + return m_request->getSummary(); + } + + /** + * Requests the download of the artifact referenced by this wrapper if not already downloaded or downloading. + * + * @return true if the request was submitted successfully, false otherwise. + */ + bool download() const override; + + /** + * @return true if the artifact is already downloaded and ready. + */ + inline bool isAvailable() const override { + std::lock_guard lock(m_stateMutex); + return m_state == commonInterfaces::State::LOADED; + } + + /** + * @return if the artifact is being created, requested, or downloading. + */ + inline bool isPending() const override { + std::lock_guard lock(m_stateMutex); + return m_state == commonInterfaces::State::INIT || m_state == commonInterfaces::State::REQUESTING || + m_state == commonInterfaces::State::DOWNLOADING; + } + + inline std::shared_ptr getRequest() const override { + return m_request; + } + + /** + * @return the path where to find the artifact on disk using aipc. + */ + std::string getPath() const override; + + /** + * @return gets the current artifact priority using aipc. + */ + commonInterfaces::Priority getPriority() const override; + + /** + * Sets the priority accordingly. + * + * @return true if successful, false otherwise. + */ + bool setPriority(commonInterfaces::Priority priority) override; + + /** + * Requests the removal and cleanup of the given artifact. + */ + void erase() override; + + void addWeakPtrObserver(const std::weak_ptr& observer) override { + return acsdkNotifier::Notifier::addWeakPtrObserver(observer); + } + + void removeWeakPtrObserver(const std::weak_ptr& observer) override { + return acsdkNotifier::Notifier::removeWeakPtrObserver(observer); + } + +private: + /// Constructor + ArtifactWrapper( + std::shared_ptr amdComm, + std::shared_ptr request, + const std::shared_ptr& updateValidator = nullptr); + + /// @name CommunicationPropertyChangeSubscriber Functions + /// @{ + void onCommunicationPropertyChange(const std::string& PropertyName, int newValue) override; + void onCommunicationPropertyChange(const std::string& PropertyName, std::string newValue) override; + /// @} + + /** + * The event when asset manager restarts or is brought up for the first time. + * We need to sync with the daemon to ensure that we have the right state and that it has the right priority. + */ + void onAmdInit(); + + /** + * The event when the artifact that we are managing changes state. + */ + void onStateChange(commonInterfaces::State newState); + + /** + * The event when the artifact that we are managing has updated with a new path. + */ + void onUpdateAvailable(const std::string& newPath); + +private: + const std::shared_ptr m_amdComm; + const std::shared_ptr m_request; + const std::weak_ptr m_updateValidator; + + commonInterfaces::State m_state; + commonInterfaces::Priority m_desiredPriority; + mutable std::mutex m_stateMutex; + mutable std::condition_variable m_stateTrigger; +}; + +} // namespace client +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGERCLIENT_ARTIFACTWRAPPER_H_ diff --git a/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/ArtifactWrapperFactory.h b/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/ArtifactWrapperFactory.h new file mode 100644 index 0000000000..0aa24f2229 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/ArtifactWrapperFactory.h @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGERCLIENT_ARTIFACTWRAPPERFACTORY_H_ +#define ACSDKASSETMANAGERCLIENT_ARTIFACTWRAPPERFACTORY_H_ + +#include +#include +#include + +#include +#include + +#include "acsdkAssetManagerClientInterfaces/ArtifactWrapperFactoryInterface.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace client { + +/** + * This interface provides a mechanism for controlling artifacts in asset manager through the Communication Interface. + * This corresponds with a one to one mapping of ArtifactWrapperInterface to either a davs or url request. + */ +class ArtifactWrapperFactory : public clientInterfaces::ArtifactWrapperFactoryInterface { +public: + ~ArtifactWrapperFactory() override = default; + + static std::shared_ptr create( + std::shared_ptr amdComm); + + std::shared_ptr createArtifactWrapper( + const std::shared_ptr& request, + const std::shared_ptr& updateValidator = nullptr) override; + +private: + explicit ArtifactWrapperFactory(std::shared_ptr amdComm); + +private: + std::shared_ptr m_amdComm; +}; + +} // namespace client +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGERCLIENT_ARTIFACTWRAPPERFACTORY_H_ diff --git a/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/GenericInventory.h b/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/GenericInventory.h new file mode 100644 index 0000000000..9b7beff21c --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClient/include/acsdkAssetManagerClient/GenericInventory.h @@ -0,0 +1,142 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGERCLIENT_GENERICINVENTORY_H_ +#define ACSDKASSETMANAGERCLIENT_GENERICINVENTORY_H_ + +#include +#include +#include + +#include "acsdkAssetManagerClient/ArtifactWrapper.h" +#include "acsdkAssetManagerClientInterfaces/ArtifactWrapperFactoryInterface.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace client { + +using Setting = std::set; +using SettingsMap = std::unordered_map; + +/** + * A general inventory manager class that is responsible for managing a list of artifacts and maintaining an active + * artifact. This manager will respond to setting changes by downloading an artifact if one does not exist for the + * required settings or by preparing one that already exists on the system. This manager will determine the artifact + * identity based on the request created. + */ +class GenericInventory + : public std::enable_shared_from_this + , public clientInterfaces::ArtifactUpdateValidator { +public: + ~GenericInventory() override; + + inline const char* getName() const { + return m_name.data(); + } + + /** + * Informs the manager class before applying the settings of the new settings that are going to be active. + * @note This should be called before commit to allow the manager to download the appropriate artifact if needed. + * The caller needs to check the returned artifact to ensure that it is available before committing. + * + * @param newSettings that will be used to identify the request for the new artifact. + * @return NULLABLE, a pointer to the artifact that is represented by the new settings which can be used to query + * its state. + */ + std::shared_ptr prepareForSettingChange(const SettingsMap& newSettings); + + /** + * Will apply the changes for the new settings after preparations have been completed. + * @warning This must only be called after prepareForSettingChange has been called and the returned artifact has + * been confirmed. Failing to do so will prevent the settings from being applied. + * + * @return weather the commit was successful. + */ + bool commitChange(); + + /** + * Will cancel the changes for the new settings that were requested. This cancels any pending download if requested. + */ + void cancelChange(); + + /** + * @return the path of the current active artifact on disk. + */ + std::string getArtifactPath(); + + /** + * Overrides the current active artifact priority if one exists. + */ + void setCurrentActivePriority(commonInterfaces::Priority priority); + + /** + * Checks to see if an artifact for the provided setting is already available. + */ + bool isSettingReady(const SettingsMap& setting); + +protected: + /** + * Constructor + * + * @param name - name for the generic inventory + * @param artifactWrapperFactory - factory for creating artifact + */ + GenericInventory( + std::string name, + std::shared_ptr artifactWrapperFactory); + + /** + * A method that will be responsible for creating an Artifact Request based on the provided settings. + * + * @param settings used to create the request. + * @return NULLABLE, new request based on the provided settings. + */ + virtual std::shared_ptr createRequest(const SettingsMap& settings) = 0; + + /** + * A method that attempts to make use of the new artifact to ensure that it is usable. + * + * @param path to the new artifact that just got downloaded or updated. + * @return true if the artifact is valid and should be used, false otherwise. + */ + virtual bool applyChangesLocked(const std::string& path) = 0; + + /** + * Internal call for cancelChange. + */ + void cancelChangeLocked(); + + /// @name ArtifactUpdateValidator method + inline bool validateUpdate( + const std::shared_ptr& request, + const std::string& newPath) override { + return m_activeArtifact != nullptr && m_activeArtifact->getRequest() == request && applyChangesLocked(newPath); + } + +private: + const std::string m_name; + const std::shared_ptr m_artifactWrapperFactory; + + std::shared_ptr m_activeArtifact; + std::shared_ptr m_pendingArtifact; + + std::mutex m_eventMutex; +}; + +} // namespace client +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGERCLIENT_GENERICINVENTORY_H_ diff --git a/capabilities/AssetManager/acsdkAssetManagerClient/src/ArtifactWrapper.cpp b/capabilities/AssetManager/acsdkAssetManagerClient/src/ArtifactWrapper.cpp new file mode 100644 index 0000000000..0d45a6ec51 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClient/src/ArtifactWrapper.cpp @@ -0,0 +1,222 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetManagerClient/ArtifactWrapper.h" +#include +#include + +#include "acsdkAssetManagerClient/AMD.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace client { + +using namespace std; +using namespace chrono; +using namespace commonInterfaces; +using namespace clientInterfaces; + +static constexpr auto DOWNLOAD_ACK_TIMEOUT = seconds(5); + +/// String to identify log entries originating from this file. +static const std::string TAG{"ArtifactWrapper"}; + +/** + * 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 ArtifactWrapper::create( + const shared_ptr& amdComm, + const shared_ptr& request, + const shared_ptr& updateValidator) { + if (nullptr == amdComm) { + ACSDK_ERROR(LX("create").m("Null AmdCommunicationInterface")); + return nullptr; + } + + if (nullptr == request) { + ACSDK_ERROR(LX("create").m("Null ArtifactRequest")); + return nullptr; + } + + auto wrapper = shared_ptr(new ArtifactWrapper(amdComm, request, updateValidator)); + if (nullptr == wrapper) { + ACSDK_ERROR(LX("create").m("ArtifactWrapper create failed")); + return nullptr; + } + + // subscribe to assetmgrd initialization event + if (!amdComm->subscribeToPropertyChangeEvent( + AMD::INITIALIZATION_PROP, static_pointer_cast>(wrapper))) { + ACSDK_ERROR(LX("create").m("Failed to register for Asset Manager initialization event")); + return nullptr; + } + + // subscribe to state changes + if (!amdComm->subscribeToPropertyChangeEvent( + request->getSummary() + AMD::STATE_SUFFIX, + static_pointer_cast>(wrapper))) { + ACSDK_ERROR(LX("create").m("Failed to register for state changes")); + return nullptr; + } + + // subscribe to updates + if (!amdComm->subscribeToPropertyChangeEvent( + request->getSummary() + AMD::UPDATE_SUFFIX, + static_pointer_cast>(wrapper))) { + ACSDK_ERROR(LX("create").m("Failed to register for update changes")); + return nullptr; + } + + // initialize current state + auto currentState = static_cast(State::INIT); + amdComm->readProperty(request->getSummary() + AMD::STATE_SUFFIX, currentState); + wrapper->m_state = static_cast(currentState); + + // initialize current priority + auto currentPriority = static_cast(Priority::UNUSED); + amdComm->readProperty(request->getSummary() + AMD::PRIORITY_SUFFIX, currentPriority); + wrapper->m_desiredPriority = static_cast(currentPriority); + + ACSDK_INFO(LX("create").d("Artifact registration succeeded for", request->getSummary())); + return wrapper; +} + +ArtifactWrapper::ArtifactWrapper( + shared_ptr amdComm, + shared_ptr request, + const shared_ptr& updateValidator) : + m_amdComm(move(amdComm)), + m_request(move(request)), + m_updateValidator(updateValidator), + m_state(State::INIT), + m_desiredPriority(Priority::UNUSED) { +} + +bool ArtifactWrapper::download() const { + ACSDK_INFO(LX("download").m("Downloading").d("name", name())); + auto result = m_amdComm->invoke(AMD::REGISTER_PROP, m_request->toJsonString()); + if (!result.isSucceeded() || !result.value()) { + ACSDK_ERROR(LX("download").m("Failed to initiate download").d("name", name())); + return false; + } + + std::unique_lock lock(m_stateMutex); + return m_stateTrigger.wait_for(lock, DOWNLOAD_ACK_TIMEOUT, [this] { return m_state != State::INIT; }); +} + +string ArtifactWrapper::getPath() const { + auto result = m_amdComm->invoke(m_request->getSummary() + AMD::PATH_SUFFIX); + if (!result.isSucceeded()) { + ACSDK_ERROR(LX("getPath").m("Could not read path property").d("name", name())); + } + return result.value(); +} + +Priority ArtifactWrapper::getPriority() const { + int priority; + if (!m_amdComm->readProperty(m_request->getSummary() + AMD::PRIORITY_SUFFIX, priority)) { + ACSDK_ERROR(LX("getPriority").m("Could not read priority property").d("name", name())); + return Priority::UNUSED; + } + return static_cast(priority); +} + +bool ArtifactWrapper::setPriority(Priority priority) { + lock_guard lock(m_stateMutex); + m_desiredPriority = priority; + + if (!m_amdComm->writeProperty(m_request->getSummary() + AMD::PRIORITY_SUFFIX, static_cast(priority))) { + ACSDK_ERROR(LX("setPriority").m("Could not write priority property").d("priority", priority).d("name", name())); + return false; + } + return true; +} + +void ArtifactWrapper::erase() { + ACSDK_INFO(LX("erase").d("Erasing", name())); + + auto result = m_amdComm->invoke(AMD::REMOVE_PROP, m_request->getSummary()); + if (!result.isSucceeded() || !result.value()) { + ACSDK_ERROR(LX("erase").m("Could not write erase property")); + } + + unique_lock lock(m_stateMutex); + auto erased = m_stateTrigger.wait_for( + lock, seconds(1), [this] { return m_state == State::INIT || m_state == State::INVALID; }); + if (!erased) { + ACSDK_DEBUG9(LX("erase").m("Timed out waiting to be erased").d("name", name()).d("state", m_state)); + } +} + +void ArtifactWrapper::onAmdInit() { + ACSDK_DEBUG(LX("onAmdInit").m("Asset Manager has been restarted")); + + auto currentState = static_cast(State::INVALID); + if (m_amdComm->readProperty(m_request->getSummary() + AMD::STATE_SUFFIX, currentState)) { + onStateChange(static_cast(currentState)); + } else { + ACSDK_ERROR(LX("onAmdInit").m("Could not read state property upon initialization")); + } + + setPriority(m_desiredPriority); +} + +void ArtifactWrapper::onStateChange(commonInterfaces::State newState) { + std::unique_lock lock(m_stateMutex); + ACSDK_INFO(LX("onStateChange") + .m("State changed") + .d("name", name()) + .d("old state", toString(m_state)) + .d("new state", toString(newState))); + m_state = newState; + lock.unlock(); + m_stateTrigger.notify_all(); + notifyObservers( + [=](const shared_ptr& observer) { observer->stateChanged(m_request, newState); }); +} + +void ArtifactWrapper::onUpdateAvailable(const string& newPath) { + ACSDK_INFO(LX("onUpdateAvailable").m("New update is available").d("name", name()).sensitive("path", newPath)); + auto validator = m_updateValidator.lock(); + auto action = (validator == nullptr || validator->validateUpdate(m_request, newPath)) ? AMD::ACCEPT_UPDATE_PROP + : AMD::REJECT_UPDATE_PROP; + m_amdComm->writeProperty(action, m_request->getSummary()); +} +void ArtifactWrapper::onCommunicationPropertyChange(const string& propertyName, int newValue) { + if (propertyName == AMD::INITIALIZATION_PROP) { + if (newValue == 1) { + onAmdInit(); + } + return; + } + + if (propertyName == m_request->getSummary() + AMD::STATE_SUFFIX) { + onStateChange(static_cast(newValue)); + return; + } +} +void ArtifactWrapper::onCommunicationPropertyChange(const string& propertyName, string newValue) { + if (propertyName == m_request->getSummary() + AMD::UPDATE_SUFFIX) { + onUpdateAvailable(newValue); + } +} + +} // namespace client +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/AssetManager/acsdkAssetManagerClient/src/ArtifactWrapperFactory.cpp b/capabilities/AssetManager/acsdkAssetManagerClient/src/ArtifactWrapperFactory.cpp new file mode 100644 index 0000000000..623958ccbe --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClient/src/ArtifactWrapperFactory.cpp @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetManagerClient/ArtifactWrapperFactory.h" +#include "acsdkAssetManagerClient/ArtifactWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace client { + +using namespace std; +using namespace commonInterfaces; +using namespace clientInterfaces; + +/// String to identify log entries originating from this file. +static const std::string TAG{"ArtifactWrapperFactory"}; +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +shared_ptr ArtifactWrapperFactory::create(shared_ptr amdComm) { + if (amdComm == nullptr) { + ACSDK_ERROR(LX("create").m("Null AmdCommunicationInterface")); + return nullptr; + } + + return shared_ptr(new ArtifactWrapperFactory(move(amdComm))); +} + +ArtifactWrapperFactory::ArtifactWrapperFactory(shared_ptr amdComm) : + m_amdComm(move(amdComm)) { +} +shared_ptr ArtifactWrapperFactory::createArtifactWrapper( + const shared_ptr& request, + const shared_ptr& updateValidator) { + return ArtifactWrapper::create(m_amdComm, request, updateValidator); +} + +} // namespace client +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/AssetManager/acsdkAssetManagerClient/src/CMakeLists.txt b/capabilities/AssetManager/acsdkAssetManagerClient/src/CMakeLists.txt new file mode 100644 index 0000000000..6da3425443 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClient/src/CMakeLists.txt @@ -0,0 +1,22 @@ +add_definitions("-DACSDK_LOG_MODULE=AssetManager") + +add_library(acsdkAssetManagerClient + ArtifactWrapper.cpp + ArtifactWrapperFactory.cpp + GenericInventory.cpp + ) + +target_include_directories(acsdkAssetManagerClient PUBLIC + "${acsdkAssetManagerClient_SOURCE_DIR}/include" + ) + +target_link_libraries(acsdkAssetManagerClient + AVSCommon + acsdkNotifier + acsdkAssetsInterfaces + acsdkAssetManagerClientInterfaces + acsdkCommunicationInterfaces + ) + +# install target +asdk_install() diff --git a/capabilities/AssetManager/acsdkAssetManagerClient/src/GenericInventory.cpp b/capabilities/AssetManager/acsdkAssetManagerClient/src/GenericInventory.cpp new file mode 100644 index 0000000000..65f76403fc --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClient/src/GenericInventory.cpp @@ -0,0 +1,189 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetManagerClient/GenericInventory.h" + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace client { + +using namespace std; +using namespace commonInterfaces; +using namespace clientInterfaces; + +/// String to identify log entries originating from this file. +static const std::string TAG{"GenericInventory"}; + +/** + * 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) + +GenericInventory::GenericInventory(string name, shared_ptr artifactWrapperFactory) : + m_name(move(name)), + m_artifactWrapperFactory(move(artifactWrapperFactory)) { +} + +GenericInventory::~GenericInventory() { + ACSDK_DEBUG(LX("~GenericInventory").m("Shutting down manager").d("name", m_name)); + cancelChange(); +} + +shared_ptr GenericInventory::prepareForSettingChange(const SettingsMap& newSettings) { + auto request = createRequest(newSettings); + if (request == nullptr) { + ACSDK_ERROR(LX("prepareForSettingChange").m("Invalid request created").d("name", m_name)); + return nullptr; + } + + lock_guard lock(m_eventMutex); + // if our active artifact is the same one we'd like to prepare, then set our "pending" to our active and return it + if (m_activeArtifact != nullptr && *m_activeArtifact->getRequest() == *request) { + ACSDK_INFO(LX("prepareForSettingChange").m("Already using the new artifact").d("name", m_name)); + cancelChangeLocked(); + m_pendingArtifact = m_activeArtifact; + return m_pendingArtifact; + } + + // if our pending artifact is the same one we'd like to prepare, then return the pending artifact + if (m_pendingArtifact != nullptr && *m_pendingArtifact->getRequest() == *request) { + ACSDK_INFO(LX("prepareForSettingChange") + .m("Already in the process of fetching the new artifact") + .d("name", m_name)); + return m_pendingArtifact; + } + + auto artifactWrapper = m_artifactWrapperFactory->createArtifactWrapper(request, shared_from_this()); + + if (artifactWrapper == nullptr) { + ACSDK_ERROR(LX("prepareForSettingChange") + .m("Failed to create an artifact request based off of given settings") + .d("name", m_name)); + return nullptr; + } + artifactWrapper->download(); + + // if we were preparing a different artifact, then cancel it + if (m_pendingArtifact != nullptr) { + ACSDK_WARN(LX("prepareForSettingChange") + .m("There was already a different pending artifact, cancelling it") + .d("name", m_name)); + cancelChangeLocked(); + } + + m_pendingArtifact = artifactWrapper; + m_pendingArtifact->setPriority(Priority::PENDING_ACTIVATION); + return m_pendingArtifact; +} + +bool GenericInventory::commitChange() { + lock_guard lock(m_eventMutex); + if (m_pendingArtifact == nullptr) { + ACSDK_ERROR(LX("commitChange").m("Pending artifact is NULL").d("name", m_name)); + return false; + } + + if (!m_pendingArtifact->isAvailable()) { + ACSDK_ERROR(LX("commitChange").m("Pending artifact is NOT downloaded or available").d("name", m_name)); + return false; + } + + if (m_pendingArtifact == m_activeArtifact) { + ACSDK_INFO(LX("commitChange").m("Artifact is already active").d("name", m_name)); + m_pendingArtifact->setPriority(Priority::ACTIVE); + m_pendingArtifact.reset(); + return true; + } + + if (!applyChangesLocked(m_pendingArtifact->getPath())) { + ACSDK_ERROR( + LX("commitChange").m("Pending artifact is not valid or corrupt, could not apply").d("name", m_name)); + return false; + } + + ACSDK_INFO(LX("commitChange").m("Successfully committed changes").d("name", m_name)); + if (m_activeArtifact != nullptr) { + m_activeArtifact->setPriority(Priority::UNUSED); + } + m_pendingArtifact->setPriority(Priority::ACTIVE); + m_activeArtifact = move(m_pendingArtifact); + return true; +} + +void GenericInventory::cancelChange() { + ACSDK_INFO(LX("cancelChange").m("Cancelling changes").d("name", m_name)); + lock_guard lock(m_eventMutex); + cancelChangeLocked(); +} + +void GenericInventory::cancelChangeLocked() { + if (m_pendingArtifact == nullptr) { + ACSDK_DEBUG(LX("cancelChangeLocked").m("Nothing to cancel").d("name", m_name)); + return; + } + + if (m_pendingArtifact == m_activeArtifact) { + ACSDK_DEBUG(LX("cancelChangeLocked") + .m("Won't cancel pending artifact since it's already active") + .d("name", m_name)); + m_pendingArtifact.reset(); + return; + } + + if (m_pendingArtifact->isAvailable()) { + ACSDK_DEBUG(LX("cancelChangeLocked") + .m("Demoting priority") + .d("name", m_name) + .d("priority", toString(Priority::UNUSED))); + m_pendingArtifact->setPriority(Priority::UNUSED); + } else { + ACSDK_DEBUG(LX("cancelChangeLocked").m("Cancelling and cleaning up pending download").d("name", m_name)); + m_pendingArtifact->erase(); + } + m_pendingArtifact.reset(); +} + +string GenericInventory::getArtifactPath() { + lock_guard lock(m_eventMutex); + if (m_activeArtifact == nullptr) { + ACSDK_ERROR(LX("getArtifactPath").m("No active artifact found").d("name", m_name)); + return ""; + } + + return m_activeArtifact->getPath(); +} + +void GenericInventory::setCurrentActivePriority(Priority priority) { + lock_guard lock(m_eventMutex); + if (m_activeArtifact == nullptr) { + ACSDK_WARN(LX("setCurrentActivePriority").m("No active priority to set").d("name", m_name)); + return; + } + + m_activeArtifact->setPriority(priority); +} + +bool GenericInventory::isSettingReady(const SettingsMap& setting) { + auto artifact = m_artifactWrapperFactory->createArtifactWrapper(createRequest(setting)); + return artifact && artifact->isAvailable(); +} + +} // namespace client +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/CMakeLists.txt b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..476692fba6 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkAssetManagerClientInterfaces LANGUAGES CXX) + +if (DEFINED AVS_CMAKE_BUILD) + include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +endif () +add_library(acsdkAssetManagerClientInterfaces INTERFACE) + +target_include_directories(acsdkAssetManagerClientInterfaces INTERFACE + "${acsdkAssetManagerClientInterfaces_SOURCE_DIR}/include" +) + +# install interface +asdk_install_interface() diff --git a/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactChangeObserver.h b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactChangeObserver.h new file mode 100644 index 0000000000..e519d6f275 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactChangeObserver.h @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTCHANGEOBSERVER_H_ +#define ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTCHANGEOBSERVER_H_ + +#include "acsdkAssetsInterfaces/ArtifactRequest.h" +#include "acsdkAssetsInterfaces/State.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace clientInterfaces { + +/** + * Observer used for communicating artifact state changes. + */ +class ArtifactChangeObserver { +public: + virtual void stateChanged( + const std::shared_ptr& request, + commonInterfaces::State newState) = 0; + virtual ~ArtifactChangeObserver() = default; +}; + +} // namespace clientInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTCHANGEOBSERVER_H_ diff --git a/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactUpdateValidator.h b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactUpdateValidator.h new file mode 100644 index 0000000000..c0b852c3b5 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactUpdateValidator.h @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTUPDATEVALIDATOR_H_ +#define ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTUPDATEVALIDATOR_H_ + +#include "acsdkAssetsInterfaces/ArtifactRequest.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace clientInterfaces { + +/** + * Used to validate that the new path for the given artifact is good to keep. + */ +class ArtifactUpdateValidator { +public: + virtual ~ArtifactUpdateValidator() = default; + + /** + * Providing a new path, check to see if the content of the update is valid and usable. + * + * @param newPath of the potential updated artifact. + * @return true if valid, false otherwise. + */ + virtual bool validateUpdate( + const std::shared_ptr& request, + const std::string& newPath) = 0; +}; + +} // namespace clientInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTUPDATEVALIDATOR_H_ diff --git a/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactWrapperFactoryInterface.h b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactWrapperFactoryInterface.h new file mode 100644 index 0000000000..fba15d05d7 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactWrapperFactoryInterface.h @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTWRAPPERFACTORYINTERFACE_H_ +#define ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTWRAPPERFACTORYINTERFACE_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace clientInterfaces { + +/** + * This class is used to house the mechanism for creating the impl instances of ArtifactWrapperInterface. + */ +class ArtifactWrapperFactoryInterface { +public: + virtual ~ArtifactWrapperFactoryInterface() = default; + + /** + * Create a pointer to an instance that implements ArtifactWrapperInterface. + * @param request REQUIRED artifact request used for creating the artifact wrapper. + * @param updateValidator OPTIONAL update validator used to check on new artifacts once they're downloaded. + * @return a new artifact wrapper instance, null in case of error. + */ + virtual std::shared_ptr createArtifactWrapper( + const std::shared_ptr& request, + const std::shared_ptr& updateValidator = nullptr) = 0; +}; + +} // namespace clientInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTWRAPPERFACTORYINTERFACE_H_ diff --git a/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactWrapperInterface.h b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactWrapperInterface.h new file mode 100644 index 0000000000..5df31e3240 --- /dev/null +++ b/capabilities/AssetManager/acsdkAssetManagerClientInterfaces/include/acsdkAssetManagerClientInterfaces/ArtifactWrapperInterface.h @@ -0,0 +1,107 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTWRAPPERINTERFACE_H_ +#define ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTWRAPPERINTERFACE_H_ + +#include +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace clientInterfaces { + +/** + * This interface provides a mechanism for controlling artifacts in asset manager through the Communication Interface. + * This corresponds with a one to one mapping of ArtifactWrapperInterface to either a davs or url request. + */ +class ArtifactWrapperInterface { +public: + virtual ~ArtifactWrapperInterface() = default; + + /** + * @return unique name identifying this artifact wrapper. + */ + virtual std::string name() const = 0; + + /** + * Requests the download of the artifact referenced by this wrapper if not already downloaded or downloading. + * + * @return true if the request was submitted successfully, false otherwise. + */ + virtual bool download() const = 0; + + /** + * @return true if the artifact is already downloaded and ready. + */ + virtual bool isAvailable() const = 0; + + /** + * @return if the artifact is being created, requested, or downloading. + */ + virtual bool isPending() const = 0; + + /** + * @return the request used to identify this artifact. + */ + virtual std::shared_ptr getRequest() const = 0; + + /** + * @return the path where to find the artifact on disk using aipc. + */ + virtual std::string getPath() const = 0; + + /** + * @return gets the current artifact priority using aipc. + */ + virtual commonInterfaces::Priority getPriority() const = 0; + + /** + * Sets the priority accordingly. + * + * @return true if successful, false otherwise. + */ + virtual bool setPriority(commonInterfaces::Priority priority) = 0; + + /** + * Requests the removal and cleanup of the given artifact. + */ + virtual void erase() = 0; + + /** + * Add observer to the state changes of this artifact. This will hold a weak_ptr to the observer. + * + * @param observer to be added. + */ + virtual void addWeakPtrObserver(const std::weak_ptr& observer) = 0; + + /** + * Remove the observer from this artifact. + * + * @param observer to be removed. + */ + virtual void removeWeakPtrObserver(const std::weak_ptr& observer) = 0; +}; + +} // namespace clientInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETMANAGERCLIENTINTERFACES_ARTIFACTWRAPPERINTERFACE_H_ diff --git a/capabilities/AudioPlayer/acsdkAudioPlayer/include/acsdkAudioPlayer/AudioPlayer.h b/capabilities/AudioPlayer/acsdkAudioPlayer/include/acsdkAudioPlayer/AudioPlayer.h index 9035acbc5b..4b91948543 100644 --- a/capabilities/AudioPlayer/acsdkAudioPlayer/include/acsdkAudioPlayer/AudioPlayer.h +++ b/capabilities/AudioPlayer/acsdkAudioPlayer/include/acsdkAudioPlayer/AudioPlayer.h @@ -373,11 +373,15 @@ class AudioPlayer /// Cached metadata. std::shared_ptr cachedMetadata; + /// Analyzers data. + std::vector analyzersData; + /** * Constructor. * * @param messageId The message Id of the @c PLAY directive. * @param dialogRequestId The dialog request Id of the @c PLAY directive. + * @param correlationToken The message Id of the @c PLAY directive. */ PlayDirectiveInfo(const std::string& messageId, const std::string& dialogRequestId); }; @@ -808,6 +812,7 @@ class AudioPlayer * * @param eventName The name of the event to be include in the header. * @param dialogRequestIdString The value associated with the "dialogRequestId" key. + * @param correlationToken The correlation toekn of the event to be included in the header. * @param payload The payload value associated with the "payload" key. * @param context Optional @c context to be sent with the event message. */ @@ -823,6 +828,7 @@ class AudioPlayer * @param audioItem item associated with the metadata * @param vectorOfTags Pointer to vector of tags that should be sent to AVS. * @param state Metadata about the media player state + * @param correlationToken correlation token of the event */ void sendStreamMetadataExtractedEvent( AudioItem& audioItem, diff --git a/capabilities/AudioPlayer/acsdkAudioPlayer/include/acsdkAudioPlayer/ErrorType.h b/capabilities/AudioPlayer/acsdkAudioPlayer/include/acsdkAudioPlayer/ErrorType.h deleted file mode 100644 index 43c09d1be9..0000000000 --- a/capabilities/AudioPlayer/acsdkAudioPlayer/include/acsdkAudioPlayer/ErrorType.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKAUDIOPLAYER_ERRORTYPE_H_ -#define ACSDKAUDIOPLAYER_ERRORTYPE_H_ - -#include - -namespace alexaClientSDK { -namespace avsCommon { -namespace utils { -namespace mediaPlayer { - -/// Identifies the specific type of error in a @c PlaybackFailed event. -enum class ErrorType { - /// An unknown error occurred. - MEDIA_ERROR_UNKNOWN, - /// The server recognized the request as being malformed (bad request, unauthorized, forbidden, not found, etc). - MEDIA_ERROR_INVALID_REQUEST, - /// The client was unable to reach the service. - MEDIA_ERROR_SERVICE_UNAVAILABLE, - /// The server accepted the request, but was unable to process the request as expected. - MEDIA_ERROR_INTERNAL_SERVER_ERROR, - /// There was an internal error on the client. - MEDIA_ERROR_INTERNAL_DEVICE_ERROR -}; - -/** - * Convert an @c ErrorType to an AVS-compliant @c std::string. - * - * @param errorType The @c ErrorType to convert. - * @return The AVS-compliant string representation of @c errorType. - */ -inline std::string errorTypeToString(ErrorType errorType) { - switch (errorType) { - case ErrorType::MEDIA_ERROR_UNKNOWN: - return "MEDIA_ERROR_UNKNOWN"; - case ErrorType::MEDIA_ERROR_INVALID_REQUEST: - return "MEDIA_ERROR_INVALID_REQUEST"; - case ErrorType::MEDIA_ERROR_SERVICE_UNAVAILABLE: - return "MEDIA_ERROR_SERVICE_UNAVAILABLE"; - case ErrorType::MEDIA_ERROR_INTERNAL_SERVER_ERROR: - return "MEDIA_ERROR_INTERNAL_SERVER_ERROR"; - case ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR: - return "MEDIA_ERROR_INTERNAL_DEVICE_ERROR"; - } - return "unknown ErrorType"; -} - -/** - * Write an @c ErrorType value to an @c ostream. - * - * @param stream The stream to write the value to. - * @param errorType The @c ErrorType 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 ErrorType& errorType) { - return stream << errorTypeToString(errorType); -} - -} // namespace mediaPlayer -} // namespace utils -} // namespace avsCommon -} // namespace alexaClientSDK - -#endif // ACSDKAUDIOPLAYER_ERRORTYPE_H_ diff --git a/capabilities/AudioPlayer/acsdkAudioPlayer/src/AudioPlayer.cpp b/capabilities/AudioPlayer/acsdkAudioPlayer/src/AudioPlayer.cpp index 45a7e5ce6b..ca48d1106a 100644 --- a/capabilities/AudioPlayer/acsdkAudioPlayer/src/AudioPlayer.cpp +++ b/capabilities/AudioPlayer/acsdkAudioPlayer/src/AudioPlayer.cpp @@ -26,12 +26,15 @@ #include #include +#include #include #include #include + #include #include #include + namespace alexaClientSDK { namespace acsdkAudioPlayer { @@ -161,6 +164,15 @@ static const char CAPTION_TYPE_KEY[] = "type"; /// The key under "captionData" containing the caption content static const char CAPTION_CONTENT_KEY[] = "content"; +/// The "analyzers" key used to retrieve analyzer data from directive. +static const char KEY_ANALYZERS[] = "analyzers"; + +/// The key used to retrieve the audio analyzer name from directive. +static const char KEY_ANALYZERS_INTERFACE[] = "interface"; + +/// The key used to retrieve the audio analyzer enabled state from directive. +static const char KEY_ANALYZERS_ENABLED[] = "enabled"; + /// The stutter key used in @c AudioPlayer events. static const char STUTTER_DURATION_KEY[] = "stutterDurationInMilliseconds"; @@ -256,12 +268,19 @@ static const std::string METRIC_KEY_ENTRY_NAME = "entry_name"; static const std::string METRIC_KEY_ERROR_DESCRIPTION = "error_description"; static const std::string METRIC_KEY_ERROR_RECOVERABLE = "error_recoverable"; +static const std::string METRIC_KEY_ASSOCIATE_FROM = "association_from_id"; +static const std::string METRIC_KEY_ASSOCIATE_TO = "association_to_id"; + static const std::string METRIC_INSTANCE_RECEIVE_DIRECTIVE = "ReceiveDirective"; static const std::string METRIC_INSTANCE_STATE_CHANGED = "PlaybackStateChanged"; +static const std::string METRIC_INSTANCE_FIRST_BYTE_READ = "FirstAudioByteRead"; static const std::string METRIC_META_KEY_NAMESPACE = "namespace"; static const std::string METRIC_META_KEY_NAME = "name"; static const std::string METRIC_META_KEY_ISPREPARE_BOOL = "isPreparePhase"; +static const std::string METRIC_INSTANCE_ACQUIRE_MEDIA_PLAYER_START = "AcquireMediaPlayerStart"; +static const std::string METRIC_INSTANCE_ACQUIRE_MEDIA_PLAYER_FINISH = "AcquireMediaPlayerFinish"; + /** * Compare two URLs up to the 'query' portion, if one exists. * @@ -386,6 +405,30 @@ static void submitInstanceMetric( recordMetric(metricRecorder, metric); } +static void createAssociation( + const std::shared_ptr& metricRecorder, + const std::string& associateFrom, + const std::string& associateTo) { + if (associateFrom.empty() || associateTo.empty()) { + ACSDK_ERROR(LX(__func__) + .m("Cannot create association") + .d("associateFrom", associateFrom) + .d("associateTo", associateTo)); + return; + } + + auto metricBuilder = MetricEventBuilder{}.setActivityName(AUDIO_PLAYER_METRIC_PREFIX + "ASSOCIATION"); + metricBuilder.addDataPoint( + DataPointStringBuilder{}.setName(METRIC_KEY_ASSOCIATE_FROM).setValue(associateFrom).build()); + metricBuilder.addDataPoint(DataPointStringBuilder{}.setName(METRIC_KEY_ASSOCIATE_TO).setValue(associateTo).build()); + auto metric = metricBuilder.build(); + if (metric == nullptr) { + ACSDK_ERROR(LX(__FUNCTION__).m("Error creating metric.")); + return; + } + recordMetric(metricRecorder, metric); +} + inline bool messageSentSuccessfully(MessageRequestObserverInterface::Status status) { switch (status) { case MessageRequestObserverInterface::Status::SUCCESS: @@ -445,7 +488,7 @@ AudioPlayer::PlayDirectiveInfo::PlayDirectiveInfo(const std::string& messageId, sourceId{ERROR_SOURCE_ID}, isBuffered{false}, isPNFSent(false), - normalizationEnabled{false} { + normalizationEnabled(false) { } std::shared_ptr AudioPlayer::createAudioPlayerInterface( @@ -760,6 +803,11 @@ void AudioPlayer::onFocusChanged(FocusState newFocus, MixingBehavior behavior) { void AudioPlayer::onFirstByteRead(SourceId id, const MediaPlayerState& state) { ACSDK_DEBUG(LX(__func__).d("id", id).d("state", state)); + submitInstanceMetric( + m_metricRecorder, + m_currentlyPlaying->dialogRequestId, + METRIC_INSTANCE_FIRST_BYTE_READ, + std::map{{TOKEN_KEY, m_currentlyPlaying->audioItem.id}}); } void AudioPlayer::onPlaybackStarted(SourceId id, const MediaPlayerState& state) { @@ -1257,32 +1305,29 @@ void AudioPlayer::preHandlePlayDirective(std::shared_ptr info) { audioItem.stream.token); } - if (!m_captionManager || !m_captionManager->isEnabled()) { - ACSDK_DEBUG5(LX("captionsNotParsed").d("reason", "captions disabled")); + rapidjson::Value::ConstMemberIterator captionIterator; + if (!jsonUtils::findNode(stream->value, CAPTION_KEY, &captionIterator)) { + captionIterator = stream->value.MemberEnd(); + ACSDK_DEBUG3(LX("captionsNotParsed").d("reason", "keyNotFoundInPayload")); } else { - rapidjson::Value::ConstMemberIterator captionIterator; - if (!jsonUtils::findNode(stream->value, CAPTION_KEY, &captionIterator)) { - captionIterator = stream->value.MemberEnd(); - ACSDK_DEBUG3(LX("captionsNotParsed").d("reason", "keyNotFoundInPayload")); + auto captionFormat = captions::CaptionFormat::UNKNOWN; + std::string captionFormatString; + if (jsonUtils::retrieveValue(captionIterator->value, CAPTION_TYPE_KEY, &captionFormatString)) { + captionFormat = captions::avsStringToCaptionFormat(captionFormatString); } else { - auto captionFormat = captions::CaptionFormat::UNKNOWN; - std::string captionFormatString; - if (jsonUtils::retrieveValue(captionIterator->value, CAPTION_TYPE_KEY, &captionFormatString)) { - captionFormat = captions::avsStringToCaptionFormat(captionFormatString); - } else { - ACSDK_WARN(LX("captionParsingIncomplete").d("reason", "failedToParseField").d("field", "type")); - } - - std::string captionContentString; - if (!jsonUtils::retrieveValue(captionIterator->value, CAPTION_CONTENT_KEY, &captionContentString)) { - ACSDK_WARN(LX("captionParsingIncomplete").d("reason", "failedToParseField").d("field", "content")); - } + ACSDK_WARN(LX("captionParsingIncomplete").d("reason", "failedToParseField").d("field", "type")); + } - auto captionData = captions::CaptionData(captionFormat, captionContentString); - ACSDK_DEBUG5(LX("captionPayloadParsed").d("type", captionData.format)); - audioItem.captionData = captions::CaptionData(captionFormat, captionContentString); + std::string captionContentString; + if (!jsonUtils::retrieveValue(captionIterator->value, CAPTION_CONTENT_KEY, &captionContentString)) { + ACSDK_WARN(LX("captionParsingIncomplete").d("reason", "failedToParseField").d("field", "content")); } + + auto captionData = captions::CaptionData(captionFormat, captionContentString); + ACSDK_DEBUG5(LX("captionPayloadParsed").d("type", captionData.format)); + audioItem.captionData = captions::CaptionData(captionFormat, captionContentString); } + rapidjson::Value::ConstMemberIterator playRequestorJson; if (jsonUtils::findNode(payload, "playRequestor", &playRequestorJson)) { if (!jsonUtils::retrieveValue(playRequestorJson->value, "type", &playItem->playRequestor.type)) { @@ -1301,6 +1346,29 @@ void AudioPlayer::preHandlePlayDirective(std::shared_ptr info) { } } + // Populate audio analyzers state from directive to play info + auto analyzerIterator = payload.FindMember(KEY_ANALYZERS); + if (payload.MemberEnd() != analyzerIterator) { + const rapidjson::Value& analyzersPayload = payload[KEY_ANALYZERS]; + if (!analyzersPayload.IsArray()) { + ACSDK_WARN(LX("audioAnalyzerParsingIncomplete").d("reason", "NotAnArray").d("field", "analyzers")); + } else { + std::vector analyzersData; + for (auto itr = analyzersPayload.Begin(); itr != analyzersPayload.End(); ++itr) { + const rapidjson::Value& analyzerStatePayload = *itr; + auto nameIterator = analyzerStatePayload.FindMember(KEY_ANALYZERS_INTERFACE); + auto stateIterator = analyzerStatePayload.FindMember(KEY_ANALYZERS_ENABLED); + if (analyzerStatePayload.MemberEnd() != nameIterator && + analyzerStatePayload.MemberEnd() != stateIterator) { + audioAnalyzer::AudioAnalyzerState state( + nameIterator->value.GetString(), stateIterator->value.GetString()); + analyzersData.push_back(state); + } + } + playItem->analyzersData = analyzersData; + } + } + playItem->audioItem = audioItem; m_executor.submit([this, info, playItem] { if (isMessageInQueue(playItem->messageId)) { @@ -2341,8 +2409,13 @@ bool AudioPlayer::isMessageInQueue(const std::string& messageId) { bool AudioPlayer::configureMediaPlayer(std::shared_ptr& playbackItem) { if (!playbackItem->mediaPlayer) { + submitInstanceMetric( + m_metricRecorder, playbackItem->dialogRequestId, METRIC_INSTANCE_ACQUIRE_MEDIA_PLAYER_START); std::shared_ptr mediaPlayer = m_mediaResourceProvider->acquireMediaPlayer(); + submitInstanceMetric( + m_metricRecorder, playbackItem->dialogRequestId, METRIC_INSTANCE_ACQUIRE_MEDIA_PLAYER_FINISH); + AudioPlayer::SourceId sourceId = ERROR_SOURCE_ID; if (mediaPlayer == nullptr) { ACSDK_ERROR(LX("configureMediaPlayerFailed").d("reason", "nullMediaPlayer")); @@ -2367,6 +2440,19 @@ bool AudioPlayer::configureMediaPlayer(std::shared_ptr& playb SourceConfig cfg = emptySourceConfig(); cfg.endOffset = playbackItem->audioItem.stream.endOffset; cfg.audioNormalizationConfig.enabled = playbackItem->normalizationEnabled; + + auto& mediaDescription = cfg.mediaDescription; + + mediaDescription.mixingBehavior = playbackItem->mixingBehavior; + mediaDescription.focusChannel = "Content"; + mediaDescription.trackId = playbackItem->audioItem.id; + mediaDescription.caption.set(playbackItem->audioItem.captionData); + mediaDescription.analyzers.set(playbackItem->analyzersData); + auto playBehavior = playBehaviorToString(playbackItem->playBehavior); + // PLAY_BEHAVIOR is defined in avsCommon::utils::mediaPlayer + mediaDescription.additionalData.insert(std::pair(PLAY_BEHAVIOR, playBehavior)); + mediaDescription.enabled = true; + sourceId = mediaPlayer->setSource( playbackItem->audioItem.stream.url, playbackItem->audioItem.stream.offset, @@ -2387,6 +2473,7 @@ bool AudioPlayer::configureMediaPlayer(std::shared_ptr& playb ACSDK_DEBUG5(LX(__func__).d("sourceId", sourceId).d("audioItemId", playbackItem->audioItem.id)); playbackItem->mediaPlayer = mediaPlayer; playbackItem->sourceId = sourceId; + createAssociation(m_metricRecorder, std::to_string(playbackItem->sourceId), playbackItem->dialogRequestId); } return true; } @@ -2688,20 +2775,46 @@ void AudioPlayer::playNextItem() { m_currentlyPlaying->initialOffset); } -void AudioPlayer::executeStop(const std::string& messageId, bool playNextItem) { +void AudioPlayer::executeStop(const std::string& messageId, bool playNext) { ACSDK_DEBUG1(LX("executeStop") - .d("playNextItem", playNextItem) + .d("playNextItem", playNext) .d("m_currentState", playerStateToString(m_currentState)) .d("sourceId", m_currentlyPlaying->sourceId)); m_currentlyPlaying->stopMessageId = messageId; // if a 'stop' comes in before the 'start' event from the player, this can still be true + bool isStarting = m_isStartingPlayback; m_isStartingPlayback = false; switch (m_currentState) { case AudioPlayerState::IDLE: + case AudioPlayerState::FINISHED: + // nothing to do here + return; case AudioPlayerState::BUFFERING: case AudioPlayerState::STOPPED: - case AudioPlayerState::FINISHED: - // If we're already stopped, there's nothing more to do. + // This special handling is to handle a race condition where a 'clearQueue' is + // sent just before a play. The current track may be stopped, but the 'onPlaybackStopped' + // event not yet received when the play directive is handled. + // si, if 'executeStop' is called by executePlay() with 'playNextItem' set to 'true', we + // need to make sure here that the next track will play, even if the player state has not quite caught up + if (playNext && !isStarting && !m_audioPlayQueue.empty()) { + m_okToRequestNextTrack = false; + ACSDK_DEBUG1(LX("playNextTest").d("focusState", m_focus)); + if (avsCommon::avs::FocusState::FOREGROUND == m_focus || + (avsCommon::avs::FocusState::BACKGROUND == m_focus && + m_audioPlayQueue.front()->mixingBehavior == audio::MixingBehavior::BEHAVIOR_DUCK && + MixingBehavior::MAY_DUCK == m_mixingBehavior)) { + ACSDK_DEBUG1(LX(__FUNCTION__).d("action", "playNextItem")); + + m_playNextItemAfterStopped = false; + playNextItem(); + + // If the last track was non-mixable, then we paused instead of ducked, so we need to make + // sure to start up ducked. + if (avsCommon::avs::FocusState::BACKGROUND == m_focus) { + executeStartDucking(); + } + } + } return; case AudioPlayerState::PLAYING: case AudioPlayerState::PAUSED: @@ -2709,7 +2822,7 @@ void AudioPlayer::executeStop(const std::string& messageId, bool playNextItem) { // Make sure we have the offset cached before stopping. getOffset(); // Set a flag indicating what we want to do in the onPlaybackStopped() call. - m_playNextItemAfterStopped = playNextItem; + m_playNextItemAfterStopped = playNext; // Request to stop. if (m_currentlyPlaying->mediaPlayer && !m_currentlyPlaying->mediaPlayer->stop(m_currentlyPlaying->sourceId)) { @@ -2806,8 +2919,11 @@ void AudioPlayer::executeLocalOperation(PlaybackOperation op, std::promise m_currentlyPlaying->initialOffset = getOffset(); success.set_value(m_currentlyPlaying->mediaPlayer->play(m_currentlyPlaying->sourceId)); } + } else if (op == PlaybackOperation::TRANSIENT_PAUSE) { + // Cannot do a transient pause from STOPPED + success.set_value(false); } else { - // we are stopped, so ok... + // STOP or RESUMABLE STOP, so success success.set_value(true); } break; @@ -2822,11 +2938,38 @@ void AudioPlayer::executeLocalOperation(PlaybackOperation op, std::promise } break; - case AudioPlayerState::PLAYING: case AudioPlayerState::PAUSED: + if (op == PlaybackOperation::RESUME_PLAYBACK) { + if (m_isStopCalled) { + ACSDK_DEBUG1(LX("localOP-ResumeFailed").d("reason", "stoppingAlreadyCannotResume")); + success.set_value(false); + break; + } + if (!m_isStartingPlayback) { + ACSDK_DEBUG1(LX("localOp-Resume")); + if (!m_currentlyPlaying->mediaPlayer->resume(m_currentlyPlaying->sourceId)) { + sendPlaybackFailedEvent( + m_currentlyPlaying->audioItem.stream.token, + ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, + "failed to resume media player", + getMediaPlayerState()); + ACSDK_ERROR(LX("localOp-ResumeFailed").d("reason", "resumeCallFailed")); + m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); + success.set_value(false); + break; + } + m_isStartingPlayback = true; + } else { + // already starting... + } + success.set_value(true); + break; + } + // Fall through + case AudioPlayerState::PLAYING: case AudioPlayerState::BUFFER_UNDERRUN: case AudioPlayerState::BUFFERING: - if (op != PlaybackOperation::RESUME_PLAYBACK) { + if (op == PlaybackOperation::STOP_PLAYBACK || op == PlaybackOperation::RESUMABLE_STOP) { // Make sure we have the offset cached before stopping. getOffset(); // Request to stop. @@ -2841,7 +2984,10 @@ void AudioPlayer::executeLocalOperation(PlaybackOperation op, std::promise clearPlayQueue(false); } success.set_value(m_isStopCalled); - } else { + } else if (op == PlaybackOperation::TRANSIENT_PAUSE) { + m_isPausingPlayback = m_currentlyPlaying->mediaPlayer->pause(m_currentlyPlaying->sourceId); + success.set_value(m_isPausingPlayback); + } else { // Cannot reach here if in PAUSED state, and op is RESUME success.set_value(true); } break; @@ -2934,13 +3080,13 @@ void AudioPlayer::sendEventWithTokenAndOffset( const std::string& eventName, bool includePlaybackReports, std::chrono::milliseconds offset) { - auto token = m_currentlyPlaying->audioItem.stream.token; - if (token.empty() && m_currentState == AudioPlayerState::BUFFERING && !m_audioPlayQueue.empty()) { - token = m_audioPlayQueue.front()->audioItem.stream.token; + auto token = &m_currentlyPlaying->audioItem.stream.token; + if (token->empty() && m_currentState == AudioPlayerState::BUFFERING && !m_audioPlayQueue.empty()) { + token = &m_audioPlayQueue.front()->audioItem.stream.token; } rapidjson::Document payload(rapidjson::kObjectType); - payload.AddMember(TOKEN_KEY, token, payload.GetAllocator()); + payload.AddMember(TOKEN_KEY, *token, payload.GetAllocator()); // Note: offset is an optional parameter, which defaults to MEDIA_PLAYER_INVALID_OFFSET. Per documentation, // this function will use the current MediaPlayer offset is a valid offset was not provided. if (MEDIA_PLAYER_INVALID_OFFSET == offset) { @@ -2958,7 +3104,6 @@ void AudioPlayer::sendEventWithTokenAndOffset( if (includePlaybackReports) { attachPlaybackReportsIfAvailable(payload, payload.GetAllocator()); } - rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); if (!payload.Accept(writer)) { @@ -3259,8 +3404,18 @@ void AudioPlayer::sendEvent( const std::string& dialogRequestIdString, const std::string& payload, const std::string& context) { - auto event = buildJsonEventString(eventName, dialogRequestIdString, payload, context); - auto request = std::make_shared(m_metricRecorder, event.second, ""); + AVSMessageHeader header = AVSMessageHeader::createAVSEventHeader(m_namespace, eventName, dialogRequestIdString, ""); + + json::JsonGenerator jsonGenerator; + if (!context.empty()) { + jsonGenerator.addRawJsonMember("context", context); + } + jsonGenerator.startObject("event"); + jsonGenerator.addRawJsonMember(avsCommon::avs::constants::HEADER_KEY_STRING, header.toJson()); + jsonGenerator.addRawJsonMember(avsCommon::avs::constants::PAYLOAD_KEY_STRING, payload); + jsonGenerator.finishObject(); + + auto request = std::make_shared(m_metricRecorder, jsonGenerator.toString(), ""); m_messageSender->sendMessage(request); } @@ -3278,23 +3433,32 @@ std::unordered_set> Aud void AudioPlayer::attachPlaybackAttributesIfAvailable( rapidjson::Value& parent, rapidjson::Document::AllocatorType& allocator) { - if (!m_currentlyPlaying->mediaPlayer) { - return; - } - - Optional playbackAttributes = m_currentlyPlaying->mediaPlayer->getPlaybackAttributes(); - if (!playbackAttributes.hasValue()) { - return; + std::shared_ptr mediaPlayer = m_currentlyPlaying->mediaPlayer; + if (!mediaPlayer) { + // This is a corner case (failure while buffering, and not playing, for example) + if (m_currentState == AudioPlayerState::BUFFERING && !m_audioPlayQueue.empty()) { + mediaPlayer = m_audioPlayQueue.front()->mediaPlayer; + } + if (!mediaPlayer) { + ACSDK_ERROR(LX("attachPlaybackAttributesIfAvailableFailed").m("playerMissing")); + return; + } } rapidjson::Value jsonPlaybackAttributes(rapidjson::kObjectType); - jsonPlaybackAttributes.AddMember(NAME_KEY, playbackAttributes.value().name, allocator); - jsonPlaybackAttributes.AddMember(CODEC_KEY, playbackAttributes.value().codec, allocator); + Optional playbackAttributes = mediaPlayer->getPlaybackAttributes(); + bool hasAttr = playbackAttributes.hasValue(); + + // We need to always attach an attributes object, even if empty + jsonPlaybackAttributes.AddMember(NAME_KEY, (hasAttr ? playbackAttributes.value().name : ""), allocator); + jsonPlaybackAttributes.AddMember(CODEC_KEY, (hasAttr ? playbackAttributes.value().codec : ""), allocator); jsonPlaybackAttributes.AddMember( - SAMPLING_RATE_IN_HERTZ_KEY, static_cast(playbackAttributes.value().samplingRateInHertz), allocator); + SAMPLING_RATE_IN_HERTZ_KEY, + (hasAttr ? static_cast(playbackAttributes.value().samplingRateInHertz) : -1), + allocator); jsonPlaybackAttributes.AddMember( DATA_RATE_IN_BITS_PER_SECOND_KEY, - static_cast(playbackAttributes.value().dataRateInBitsPerSecond), + (hasAttr ? static_cast(playbackAttributes.value().dataRateInBitsPerSecond) : -1), allocator); parent.AddMember(PLAYBACK_ATTRIBUTES_KEY, jsonPlaybackAttributes, allocator); @@ -3303,33 +3467,42 @@ void AudioPlayer::attachPlaybackAttributesIfAvailable( void AudioPlayer::attachPlaybackReportsIfAvailable( rapidjson::Value& parent, rapidjson::Document::AllocatorType& allocator) { - if (!m_currentlyPlaying->mediaPlayer) { - return; - } - - std::vector playbackReports = m_currentlyPlaying->mediaPlayer->getPlaybackReports(); - if (playbackReports.empty()) { - return; + std::shared_ptr mediaPlayer = m_currentlyPlaying->mediaPlayer; + if (!mediaPlayer) { + // This is a corner case (failure while buffering, and not playing, for example) + if (m_currentState == AudioPlayerState::BUFFERING && !m_audioPlayQueue.empty()) { + mediaPlayer = m_audioPlayQueue.front()->mediaPlayer; + } + if (!mediaPlayer) { + ACSDK_ERROR(LX("attachPlaybackReportsIfAvailableFailed").m("playerMissing")); + return; + } } + // We need to attach a report, even if empty rapidjson::Value jsonPlaybackReports(rapidjson::kArrayType); - for (auto& report : playbackReports) { - rapidjson::Value jsonReport(rapidjson::kObjectType); - jsonReport.AddMember(START_OFFSET_KEY, static_cast(report.startOffset.count()), allocator); - jsonReport.AddMember(END_OFFSET_KEY, static_cast(report.endOffset.count()), allocator); - - rapidjson::Value jsonPlaybackAttributes(rapidjson::kObjectType); - jsonPlaybackAttributes.AddMember(NAME_KEY, report.playbackAttributes.name, allocator); - jsonPlaybackAttributes.AddMember(CODEC_KEY, report.playbackAttributes.codec, allocator); - jsonPlaybackAttributes.AddMember( - SAMPLING_RATE_IN_HERTZ_KEY, static_cast(report.playbackAttributes.samplingRateInHertz), allocator); - jsonPlaybackAttributes.AddMember( - DATA_RATE_IN_BITS_PER_SECOND_KEY, - static_cast(report.playbackAttributes.dataRateInBitsPerSecond), - allocator); - - jsonReport.AddMember(PLAYBACK_ATTRIBUTES_KEY, jsonPlaybackAttributes, allocator); - jsonPlaybackReports.PushBack(jsonReport, allocator); + std::vector playbackReports = mediaPlayer->getPlaybackReports(); + if (!playbackReports.empty()) { + for (auto& report : playbackReports) { + rapidjson::Value jsonReport(rapidjson::kObjectType); + jsonReport.AddMember(START_OFFSET_KEY, static_cast(report.startOffset.count()), allocator); + jsonReport.AddMember(END_OFFSET_KEY, static_cast(report.endOffset.count()), allocator); + + rapidjson::Value jsonPlaybackAttributes(rapidjson::kObjectType); + jsonPlaybackAttributes.AddMember(NAME_KEY, report.playbackAttributes.name, allocator); + jsonPlaybackAttributes.AddMember(CODEC_KEY, report.playbackAttributes.codec, allocator); + jsonPlaybackAttributes.AddMember( + SAMPLING_RATE_IN_HERTZ_KEY, + static_cast(report.playbackAttributes.samplingRateInHertz), + allocator); + jsonPlaybackAttributes.AddMember( + DATA_RATE_IN_BITS_PER_SECOND_KEY, + static_cast(report.playbackAttributes.dataRateInBitsPerSecond), + allocator); + + jsonReport.AddMember(PLAYBACK_ATTRIBUTES_KEY, jsonPlaybackAttributes, allocator); + jsonPlaybackReports.PushBack(jsonReport, allocator); + } } parent.AddMember(PLAYBACK_REPORTS_KEY, jsonPlaybackReports, allocator); } @@ -3353,13 +3526,6 @@ void AudioPlayer::parseHeadersFromPlayDirective(const rapidjson::Value& httpHead DataPointCounterBuilder{}.setName(INVALID_HEADER_RECEIVED).increment(1).build(), "", audioItem->stream.token); - } else { - submitMetric( - m_metricRecorder, - AUDIO_PLAYER_METRIC_PREFIX + INVALID_HEADER_RECEIVED, - DataPointCounterBuilder{}.setName(INVALID_HEADER_RECEIVED).increment(0).build(), - "", - audioItem->stream.token); } if (!isValid.second) { ACSDK_WARN(LX(__FUNCTION__).m("Malicious data found")); @@ -3369,13 +3535,6 @@ void AudioPlayer::parseHeadersFromPlayDirective(const rapidjson::Value& httpHead DataPointCounterBuilder{}.setName(MALICIOUS_HEADER_RECEIVED).increment(1).build(), "", audioItem->stream.token); - } else { - submitMetric( - m_metricRecorder, - AUDIO_PLAYER_METRIC_PREFIX + MALICIOUS_HEADER_RECEIVED, - DataPointCounterBuilder{}.setName(MALICIOUS_HEADER_RECEIVED).increment(0).build(), - "", - audioItem->stream.token); } } else { ACSDK_ERROR(LX(__FUNCTION__).m("audioItem is null")); diff --git a/capabilities/AudioPlayer/acsdkAudioPlayer/src/CMakeLists.txt b/capabilities/AudioPlayer/acsdkAudioPlayer/src/CMakeLists.txt index b6f09669e6..d9a871dc6a 100644 --- a/capabilities/AudioPlayer/acsdkAudioPlayer/src/CMakeLists.txt +++ b/capabilities/AudioPlayer/acsdkAudioPlayer/src/CMakeLists.txt @@ -2,11 +2,13 @@ if (ENABLE_DEVICE_CONTEXT_IN_EVENT) add_definitions("-DENABLE_DEVICE_CONTEXT_IN_EVENT") endif() add_definitions("-DACSDK_LOG_MODULE=audioplayer") -add_library(acsdkAudioPlayer SHARED + +add_library(acsdkAudioPlayer AudioPlayer.cpp AudioPlayerComponent.cpp ProgressTimer.cpp Util.cpp) + target_include_directories(acsdkAudioPlayer PUBLIC "${acsdkAudioPlayer_SOURCE_DIR}/include" "${CRYPTO_INCLUDE_DIRS}") @@ -19,5 +21,6 @@ target_link_libraries(acsdkAudioPlayer AVSCommon "${CRYPTO_LDFLAGS}") + # install target asdk_install() diff --git a/capabilities/AudioPlayer/acsdkAudioPlayer/test/AudioPlayerTest.cpp b/capabilities/AudioPlayer/acsdkAudioPlayer/test/AudioPlayerTest.cpp index a7fe4de102..6b42bfadca 100644 --- a/capabilities/AudioPlayer/acsdkAudioPlayer/test/AudioPlayerTest.cpp +++ b/capabilities/AudioPlayer/acsdkAudioPlayer/test/AudioPlayerTest.cpp @@ -111,6 +111,9 @@ static const std::string MESSAGE_ID_TEST_3("MessageId_Test3"); /// PlayRequestId for testing. static const std::string PLAY_REQUEST_ID_TEST("PlayRequestId_Test"); +/// CorrelationToken for testing. +static const std::string CORRELATION_TOKEN_TEST("CorrelationToken_Test"); + /// Context ID for testing static const std::string CONTEXT_ID_TEST("ContextId_Test"); @@ -526,6 +529,11 @@ static const std::string FINGERPRINT_BUILD_TYPE_KEY = "buildType"; /// JSON key for "versionNumber" in fingerprint configuration. static const std::string FINGERPRINT_VERSION_NUMBER_KEY = "versionNumber"; +/// List of event messages expected to have PlaybackReports field +static const std::vector EVENTS_WITH_REPORTS = {PLAYBACK_FINISHED_NAME, + PLAYBACK_STOPPED_NAME, + PLAYBACK_FAILED_NAME, + PROGRESS_REPORT_INTERVAL_ELAPSED_NAME}; /** * Create a LogEntry using this file's TAG and the specified event string. * @@ -749,6 +757,20 @@ class AudioPlayerTest : public ::testing::Test { */ void sendUpdateProgressReportIntervalDirective(); + /** + * Verify that the message name matches the expected name, + * and contains fields common to PlaybackEvents + * + * @param request The @c MessageRequest to verify + * @param expectedName The expected name to find in the json header + * @param verifyReport True to look for PlaybackReport + */ + + bool verifyPlaybackMessage( + std::shared_ptr request, + std::string expectedName, + bool verifyReport = false); + /** * Verify that the message name matches the expected name * @@ -965,8 +987,8 @@ void AudioPlayerTest::wakeOnSendMessage() { } void AudioPlayerTest::sendPlayDirective(long offsetInMilliseconds, long endOffsetInMilliseconds) { - auto avsMessageHeader = - std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST, PLAY_REQUEST_ID_TEST); + auto avsMessageHeader = std::make_shared( + NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST, PLAY_REQUEST_ID_TEST, CORRELATION_TOKEN_TEST); std::string payload; if (endOffsetInMilliseconds == 0) { payload = createEnqueuePayloadTest(offsetInMilliseconds); @@ -1045,6 +1067,46 @@ void AudioPlayerTest::sendUpdateProgressReportIntervalDirective() { m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); } +bool AudioPlayerTest::verifyPlaybackMessage( + std::shared_ptr request, + std::string expectedName, + bool verifyReport) { + rapidjson::Document document; + document.Parse(request->getJsonContent().c_str()); + EXPECT_FALSE(document.HasParseError()) + << "rapidjson detected a parsing error at offset:" + std::to_string(document.GetErrorOffset()) + + ", error message: " + GetParseError_En(document.GetParseError()); + + auto event = document.FindMember(MESSAGE_EVENT_KEY); + EXPECT_NE(event, document.MemberEnd()); + + auto header = event->value.FindMember(MESSAGE_HEADER_KEY); + EXPECT_NE(header, event->value.MemberEnd()); + + std::string requestName; + jsonUtils::retrieveValue(header->value, MESSAGE_NAME_KEY, &requestName); + + if (requestName == expectedName) { + auto payload = event->value.FindMember(MESSAGE_PAYLOAD_KEY); + EXPECT_NE(payload, event->value.MemberEnd()); + + auto key = payload->value.FindMember(MESSAGE_TOKEN_KEY); + EXPECT_NE(key, event->value.MemberEnd()); + + key = payload->value.FindMember(MESSAGE_PLAYBACK_ATTRIBUTES_KEY); + EXPECT_NE(key, event->value.MemberEnd()); + + if (verifyReport) { + key = payload->value.FindMember(MESSAGE_PLAYBACK_REPORTS_KEY); + EXPECT_NE(key, event->value.MemberEnd()); + } + + return true; + } + + return false; +} + bool AudioPlayerTest::verifyMessage(std::shared_ptr request, std::string expectedName) { rapidjson::Document document; document.Parse(request->getJsonContent().c_str()); @@ -1575,7 +1637,7 @@ TEST_F(AudioPlayerTest, test_localPause_withEnqueuedTracks_doesNotAutoProgress) m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST_2); // local pause - m_audioPlayer->localOperation(AudioPlayer::PlaybackOperation::PAUSE_PLAYBACK); + m_audioPlayer->localOperation(AudioPlayer::PlaybackOperation::RESUMABLE_STOP); ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); // Never plays next item after user initiated pause @@ -3261,10 +3323,14 @@ void AudioPlayerTest::verifyMessageOrder2Phase( Invoke([this, orderedMessageList, &nextIndex](std::shared_ptr request) { std::lock_guard lock(m_mutex); if (nextIndex < orderedMessageList.size()) { - if (verifyMessage(request, orderedMessageList.at(nextIndex))) { + auto msg = orderedMessageList.at(nextIndex); + if (verifyMessage(request, msg)) { if (nextIndex < orderedMessageList.size()) { nextIndex++; } + bool hasReport = std::find(EVENTS_WITH_REPORTS.begin(), EVENTS_WITH_REPORTS.end(), msg) != + EVENTS_WITH_REPORTS.end(); + EXPECT_TRUE(verifyPlaybackMessage(request, msg, hasReport)); } } m_messageSentTrigger.notify_one(); @@ -3329,13 +3395,58 @@ TEST_F(AudioPlayerTest, testTimer_playbackFinishedMessageOrder_2Players) { }); } -TEST_F(AudioPlayerTest, testTimer_playbackStoppedMessageOrder_1Player) { +TEST_F(AudioPlayerTest, testTimer_playbackValidateReportEvents_1Player) { + reSetUp(1); + + std::vector expectedMessages; + + expectedMessages.push_back(PLAYBACK_STARTED_NAME); + expectedMessages.push_back(PROGRESS_REPORT_DELAY_ELAPSED_NAME); + expectedMessages.push_back(PROGRESS_REPORT_INTERVAL_ELAPSED_NAME); + expectedMessages.push_back(PROGRESS_REPORT_INTERVAL_UPDATED_NAME); + expectedMessages.push_back(PLAYBACK_STOPPED_NAME); + + verifyMessageOrder2Phase( + expectedMessages, + 3, + [this] { sendPlayDirective(); }, + [this] { + m_audioPlayer->onProgressReportIntervalUpdated(); + m_audioPlayer->onPlaybackStopped(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + }); +} + +TEST_F(AudioPlayerTest, testTimer_playbackValidateFailed_1Player) { + reSetUp(1); + + std::vector expectedMessages; + + expectedMessages.push_back(PLAYBACK_STARTED_NAME); + expectedMessages.push_back(PROGRESS_REPORT_DELAY_ELAPSED_NAME); + expectedMessages.push_back(PLAYBACK_FAILED_NAME); + + verifyMessageOrder2Phase( + expectedMessages, + 2, + [this] { sendPlayDirective(); }, + [this] { + m_audioPlayer->onPlaybackError( + m_mockMediaPlayer->getCurrentSourceId(), + ErrorType::MEDIA_ERROR_UNKNOWN, + "TEST_ERROR", + DEFAULT_MEDIA_PLAYER_STATE); + }); +} + +TEST_F(AudioPlayerTest, testTimer_playbackValidateStutter_1Player) { reSetUp(1); std::vector expectedMessages; expectedMessages.push_back(PLAYBACK_STARTED_NAME); expectedMessages.push_back(PROGRESS_REPORT_DELAY_ELAPSED_NAME); + expectedMessages.push_back(PLAYBACK_STUTTER_STARTED_NAME); + expectedMessages.push_back(PLAYBACK_STUTTER_FINISHED_NAME); expectedMessages.push_back(PLAYBACK_STOPPED_NAME); verifyMessageOrder2Phase( @@ -3343,25 +3454,30 @@ TEST_F(AudioPlayerTest, testTimer_playbackStoppedMessageOrder_1Player) { 2, [this] { sendPlayDirective(); }, [this] { + m_audioPlayer->onBufferUnderrun(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + m_audioPlayer->onBufferRefilled(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); m_audioPlayer->onPlaybackStopped(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); }); } -TEST_F(AudioPlayerTest, testTimer_playbackStoppedMessageOrder_2Players) { - reSetUp(2); +TEST_F(AudioPlayerTest, testTimer_playbackPauseResume_1Player) { + reSetUp(1); std::vector expectedMessages; expectedMessages.push_back(PLAYBACK_STARTED_NAME); - expectedMessages.push_back(PLAYBACK_NEARLY_FINISHED_NAME); expectedMessages.push_back(PROGRESS_REPORT_DELAY_ELAPSED_NAME); + expectedMessages.push_back(PLAYBACK_PAUSED_NAME); + expectedMessages.push_back(PLAYBACK_RESUMED_NAME); expectedMessages.push_back(PLAYBACK_STOPPED_NAME); verifyMessageOrder2Phase( expectedMessages, - 3, + 2, [this] { sendPlayDirective(); }, [this] { + m_audioPlayer->onPlaybackPaused(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); + m_audioPlayer->onPlaybackResumed(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); m_audioPlayer->onPlaybackStopped(m_mockMediaPlayer->getCurrentSourceId(), DEFAULT_MEDIA_PLAYER_STATE); }); } @@ -3398,7 +3514,7 @@ TEST_F(AudioPlayerTest, test_localPause) { EXPECT_CALL(*(m_mockMediaPlayer.get()), stop(_, _)).Times(AtLeast(1)); - ASSERT_TRUE(m_audioPlayer->localOperation(LocalPlaybackHandlerInterface::PlaybackOperation::PAUSE_PLAYBACK)); + ASSERT_TRUE(m_audioPlayer->localOperation(LocalPlaybackHandlerInterface::PlaybackOperation::RESUMABLE_STOP)); ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); } @@ -3406,7 +3522,7 @@ TEST_F(AudioPlayerTest, test_localResumeAfterPaused) { sendPlayDirective(); // pause playback - ASSERT_TRUE(m_audioPlayer->localOperation(LocalPlaybackHandlerInterface::PlaybackOperation::PAUSE_PLAYBACK)); + ASSERT_TRUE(m_audioPlayer->localOperation(LocalPlaybackHandlerInterface::PlaybackOperation::RESUMABLE_STOP)); ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); // verify mediaplayer resumes @@ -3489,7 +3605,7 @@ TEST_F(AudioPlayerTest, test_localSeekToWhileLocalStopped) { })); // pause playback - ASSERT_TRUE(m_audioPlayer->localOperation(LocalPlaybackHandlerInterface::PlaybackOperation::PAUSE_PLAYBACK)); + ASSERT_TRUE(m_audioPlayer->localOperation(LocalPlaybackHandlerInterface::PlaybackOperation::RESUMABLE_STOP)); ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, MY_WAIT_TIMEOUT)); auto origin = std::chrono::milliseconds::zero(); diff --git a/capabilities/AudioPlayer/acsdkAudioPlayerInterfaces/include/acsdkAudioPlayerInterfaces/AudioPlayerInterface.h b/capabilities/AudioPlayer/acsdkAudioPlayerInterfaces/include/acsdkAudioPlayerInterfaces/AudioPlayerInterface.h index aadd507c85..32c1cc51c3 100644 --- a/capabilities/AudioPlayer/acsdkAudioPlayerInterfaces/include/acsdkAudioPlayerInterfaces/AudioPlayerInterface.h +++ b/capabilities/AudioPlayer/acsdkAudioPlayerInterfaces/include/acsdkAudioPlayerInterfaces/AudioPlayerInterface.h @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ACSDKAUDIOPLAYERINTERFACES_AUDIOPLAYERINTERFACE_H_ -#define ACSDKAUDIOPLAYERINTERFACES_AUDIOPLAYERINTERFACE_H_ +#ifndef ALEXA_CLIENT_SDK_ACSDKAUDIOPLAYERINTERFACES_INCLUDE_ACSDKAUDIOPLAYERINTERFACES_AUDIOPLAYERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_ACSDKAUDIOPLAYERINTERFACES_INCLUDE_ACSDKAUDIOPLAYERINTERFACES_AUDIOPLAYERINTERFACE_H_ #include #include @@ -59,4 +59,4 @@ class AudioPlayerInterface { } // namespace acsdkAudioPlayerInterfaces } // namespace alexaClientSDK -#endif // ACSDKAUDIOPLAYERINTERFACES_AUDIOPLAYERINTERFACE_H_ +#endif // ALEXA_CLIENT_SDK_ACSDKAUDIOPLAYERINTERFACES_INCLUDE_ACSDKAUDIOPLAYERINTERFACES_AUDIOPLAYERINTERFACE_H_ diff --git a/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/Bluetooth.h b/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/Bluetooth.h index eb342f818a..578bc8d36a 100644 --- a/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/Bluetooth.h +++ b/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/Bluetooth.h @@ -66,6 +66,7 @@ #include "acsdkBluetooth/BluetoothEventState.h" #include "acsdkBluetooth/BluetoothMediaInputTransformer.h" +#include "acsdkBluetoothInterfaces/BluetoothLocalInterface.h" namespace alexaClientSDK { namespace acsdkBluetooth { @@ -117,6 +118,7 @@ static const constexpr char* BLUETOOTH_MEDIA_PLAYER_NAME = "BluetoothMediaPlayer */ class Bluetooth : public std::enable_shared_from_this + , public acsdkBluetoothInterfaces::BluetoothLocalInterface , public avsCommon::avs::CapabilityAgent , public avsCommon::utils::bluetooth::BluetoothEventListenerInterface , public avsCommon::utils::bluetooth::FormattedAudioStreamAdapterListener @@ -256,6 +258,16 @@ class Bluetooth void onFocusChanged(avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior) override; /// @} + /// @name BluetoothLocalInterface Functions + /// @{ + void setDiscoverableMode(bool discoverable) override; + void setScanMode(bool scanning) override; + void pair(const std::string& addr) override; + void unpair(const std::string& addr) override; + void connect(const std::string& addr) override; + void disconnect(const std::string& addr) override; + /// @} + /// @name CapabilityConfigurationInterface Functions /// @{ std::unordered_set> getCapabilityConfigurations() override; @@ -406,6 +418,55 @@ class Bluetooth /// A state transition function for entering the none state. void executeEnterNone(); + /** + * Handles setting the device into discoverable mode. + */ + void executeHandleEnterDiscoverableMode(); + + /** + * Handles setting the device into undiscoverable mode. + */ + void executeHandleExitDiscoverableMode(); + + /** + * Handles setting the device into scan mode. + */ + void executeHandleScanDevices(); + + /** + * Handles pairing with the devices matching the given uuids. + * + * @param uuids The uuids associated with the devices. + * @return A bool indicating success. + */ + bool executeHandlePairDevices(const std::unordered_set& uuids); + + /** + * Handles unpairing with the devices matching the given uuids. + * + * @param uuids The uuids associated with the devices. + * @return A bool indicating success. + */ + bool executeHandleUnpairDevices(const std::unordered_set& uuids); + + /** + * Handles connecting with the devices matching the given uuids. + * This will connect all available services between the two devices. + * + * @param uuids The uuids associated with the devices. + * @return A bool indicating success. + */ + bool executeHandleConnectByDeviceIds(const std::unordered_set& uuids); + + /** + * Disconnect with the devices matching the given uuids. + * This will disconnect all available services between the two devices. + * + * @param uuids The uuids associated with the devices. + * @return A bool indicating success. + */ + bool executeHandleDisconnectDevices(const std::unordered_set& uuids); + /** * Puts the device into the desired discoverable mode. * diff --git a/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/BluetoothComponent.h b/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/BluetoothComponent.h index 53995dca79..ae091cda88 100644 --- a/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/BluetoothComponent.h +++ b/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/BluetoothComponent.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,7 @@ namespace acsdkBluetooth { * Manufactory Component definition for the @c BluetoothNotifierInterface. */ using BluetoothComponent = acsdkManufactory::Component< + std::shared_ptr, std::shared_ptr, acsdkManufactory::Import< std::shared_ptr>, diff --git a/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/BluetoothNotifier.h b/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/BluetoothNotifier.h index 9b2a0a2fc0..14fb00593c 100644 --- a/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/BluetoothNotifier.h +++ b/capabilities/Bluetooth/acsdkBluetooth/include/acsdkBluetooth/BluetoothNotifier.h @@ -20,7 +20,7 @@ #include #include -#include +#include namespace alexaClientSDK { namespace acsdkBluetooth { diff --git a/capabilities/Bluetooth/acsdkBluetooth/src/Bluetooth.cpp b/capabilities/Bluetooth/acsdkBluetooth/src/Bluetooth.cpp index de1555b6fb..e8bcf671ff 100644 --- a/capabilities/Bluetooth/acsdkBluetooth/src/Bluetooth.cpp +++ b/capabilities/Bluetooth/acsdkBluetooth/src/Bluetooth.cpp @@ -1363,6 +1363,72 @@ void Bluetooth::onFocusChanged(FocusState newFocus, MixingBehavior behavior) { }); } +void Bluetooth::setDiscoverableMode(bool discoverable) { + m_executor.submit([this, discoverable] { + ACSDK_DEBUG5(LX(__func__).d("discoverable", discoverable)); + if (discoverable) { + executeHandleEnterDiscoverableMode(); + } else { + executeHandleExitDiscoverableMode(); + } + }); +} + +void Bluetooth::setScanMode(bool scanning) { + m_executor.submit([this, scanning] { + ACSDK_DEBUG5(LX(__func__).d("scanning", scanning)); + if (scanning) { + executeHandleScanDevices(); + } else { + executeSetScanMode(false); + } + }); +} + +void Bluetooth::pair(const std::string& addr) { + m_executor.submit([this, addr] { + ACSDK_DEBUG5(LX(__func__)); + std::string uuid; + retrieveUuid(addr, &uuid); + std::unordered_set uuids({uuid}); + + executeHandlePairDevices(uuids); + }); +} + +void Bluetooth::unpair(const std::string& addr) { + m_executor.submit([this, addr] { + ACSDK_DEBUG5(LX(__func__)); + std::string uuid; + retrieveUuid(addr, &uuid); + std::unordered_set uuids({uuid}); + + executeHandleUnpairDevices(uuids); + }); +} + +void Bluetooth::connect(const std::string& addr) { + m_executor.submit([this, addr] { + ACSDK_DEBUG5(LX(__func__)); + std::string uuid; + retrieveUuid(addr, &uuid); + std::unordered_set uuids({uuid}); + + executeHandleConnectByDeviceIds(uuids); + }); +} + +void Bluetooth::disconnect(const std::string& addr) { + m_executor.submit([this, addr] { + ACSDK_DEBUG5(LX(__func__)); + std::string uuid; + retrieveUuid(addr, &uuid); + std::unordered_set uuids({uuid}); + + executeHandleDisconnectDevices(uuids); + }); +} + void Bluetooth::onContextAvailable(const std::string& jsonContext) { m_executor.submit([this, jsonContext] { ACSDK_DEBUG9(LX("onContextAvailableLambda")); @@ -1457,46 +1523,21 @@ void Bluetooth::handleDirective(std::shared_ptr } if (directiveName == SCAN_DEVICES.name) { - clearUnusedUuids(); - executeSetScanMode(true); + executeHandleScanDevices(); } else if (directiveName == ENTER_DISCOVERABLE_MODE.name) { - if (executeSetDiscoverableMode(true)) { - executeSendEnterDiscoverableModeSucceeded(); - } else { - executeSendEnterDiscoverableModeFailed(); - } + executeHandleEnterDiscoverableMode(); } else if (directiveName == EXIT_DISCOVERABLE_MODE.name) { - // There are no events to send in case this operation fails. The best we can do is log. - executeSetScanMode(false); - executeSetDiscoverableMode(false); + executeHandleExitDiscoverableMode(); } else if (directiveName == PAIR_DEVICES.name) { std::unordered_set uuids = retrieveUuidsFromConnectionPayload(payload); - if (!uuids.empty()) { - /* - * AVS expects this sequence of implicit behaviors. - * AVS should send individual directives, but we will handle this for now. - * - * Don't send ScanDeviceReport event to cloud in pairing mode. Otherwise, another - * SCAN_DEVICES directive would be sent down to start scan mode again. - * - * If the device fails to pair, start scan mode again. - */ - executeSetScanMode(false, false); - executeSetDiscoverableMode(false); - - if (!executePairDevices(uuids)) { - executeSetScanMode(true, false); - } - } else { + if (!executeHandlePairDevices(uuids)) { sendExceptionEncountered( info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); return; } } else if (directiveName == UNPAIR_DEVICES.name) { std::unordered_set uuids = retrieveUuidsFromConnectionPayload(payload); - if (!uuids.empty()) { - executeUnpairDevices(uuids); - } else { + if (!executeHandleUnpairDevices(uuids)) { sendExceptionEncountered( info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); return; @@ -1529,9 +1570,7 @@ void Bluetooth::handleDirective(std::shared_ptr } } else if (directiveName == CONNECT_BY_DEVICE_IDS.name) { std::unordered_set uuids = retrieveUuidsFromConnectionPayload(payload); - if (!uuids.empty()) { - executeConnectByDeviceIds(uuids); - } else { + if (!executeHandleConnectByDeviceIds(uuids)) { sendExceptionEncountered( info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); return; @@ -1560,9 +1599,7 @@ void Bluetooth::handleDirective(std::shared_ptr } } else if (directiveName == DISCONNECT_DEVICES.name) { std::unordered_set uuids = retrieveUuidsFromConnectionPayload(payload); - if (!uuids.empty()) { - executeDisconnectDevices(uuids); - } else { + if (!executeHandleDisconnectDevices(uuids)) { sendExceptionEncountered( info, "uniqueDeviceId not found.", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); return; @@ -2139,6 +2176,82 @@ void Bluetooth::sendExceptionEncountered( removeDirective(info); } +void Bluetooth::executeHandleEnterDiscoverableMode() { + ACSDK_INFO(LX(__func__)); + if (executeSetDiscoverableMode(true)) { + executeSendEnterDiscoverableModeSucceeded(); + } else { + executeSendEnterDiscoverableModeFailed(); + } +} + +void Bluetooth::executeHandleExitDiscoverableMode() { + ACSDK_INFO(LX(__func__)); + // There are no events to send in case this operation fails. The best we can do is log. + executeSetScanMode(false); + executeSetDiscoverableMode(false); +} + +void Bluetooth::executeHandleScanDevices() { + ACSDK_INFO(LX(__func__)); + clearUnusedUuids(); + executeSetScanMode(true); +} + +bool Bluetooth::executeHandlePairDevices(const std::unordered_set& uuids) { + ACSDK_INFO(LX(__func__)); + if (uuids.empty()) { + return false; + } + + /* + * AVS expects this sequence of implicit behaviors. + * AVS should send individual directives, but we will handle this for now. + * + * Don't send ScanDeviceReport event to cloud in pairing mode. Otherwise, another + * SCAN_DEVICES directive would be sent down to start scan mode again. + * + * If the device fails to pair, start scan mode again. + */ + executeSetScanMode(false, false); + executeSetDiscoverableMode(false); + + if (!executePairDevices(uuids)) { + executeSetScanMode(true, false); + } + return true; +} + +bool Bluetooth::executeHandleUnpairDevices(const std::unordered_set& uuids) { + ACSDK_INFO(LX(__func__)); + if (uuids.empty()) { + return false; + } + + executeUnpairDevices(uuids); + return true; +} + +bool Bluetooth::executeHandleConnectByDeviceIds(const std::unordered_set& uuids) { + ACSDK_INFO(LX(__func__)); + if (uuids.empty()) { + return false; + } + + executeConnectByDeviceIds(uuids); + return true; +} + +bool Bluetooth::executeHandleDisconnectDevices(const std::unordered_set& uuids) { + ACSDK_INFO(LX(__func__)); + if (uuids.empty()) { + return false; + } + + executeDisconnectDevices(uuids); + return true; +} + bool Bluetooth::executeSetScanMode(bool scanning, bool shouldReport) { ACSDK_DEBUG5(LX(__func__)); diff --git a/capabilities/Bluetooth/acsdkBluetooth/src/BluetoothComponent.cpp b/capabilities/Bluetooth/acsdkBluetooth/src/BluetoothComponent.cpp index ab8a714080..4faeba97f8 100644 --- a/capabilities/Bluetooth/acsdkBluetooth/src/BluetoothComponent.cpp +++ b/capabilities/Bluetooth/acsdkBluetooth/src/BluetoothComponent.cpp @@ -23,14 +23,52 @@ namespace alexaClientSDK { namespace acsdkBluetooth { +std::shared_ptr createBluetooth( + std::shared_ptr contextManager, + std::shared_ptr messageSender, + std::shared_ptr exceptionEncounteredSender, + std::shared_ptr bluetoothStorage, + std::shared_ptr deviceManager, + std::shared_ptr eventBus, + std::shared_ptr customerDataManager, + std::shared_ptr + audioPipelineFactory, + acsdkManufactory::Annotated< + avsCommon::sdkInterfaces::AudioFocusAnnotation, + avsCommon::sdkInterfaces::FocusManagerInterface> audioFocusManager, + std::shared_ptr shutdownNotifier, + acsdkManufactory::Annotated< + avsCommon::sdkInterfaces::endpoints::DefaultEndpointAnnotation, + avsCommon::sdkInterfaces::endpoints::EndpointCapabilitiesRegistrarInterface> endpointCapabilitiesRegistrar, + std::shared_ptr connectionRulesProvider, + std::shared_ptr mediaInputTransformer, + std::shared_ptr bluetoothNotifier) { + return std::shared_ptr(Bluetooth::createBluetoothCapabilityAgent( + contextManager, + messageSender, + exceptionEncounteredSender, + bluetoothStorage, + deviceManager, + eventBus, + customerDataManager, + audioPipelineFactory, + audioFocusManager, + shutdownNotifier, + endpointCapabilitiesRegistrar, + connectionRulesProvider, + mediaInputTransformer, + bluetoothNotifier)); +} + BluetoothComponent getComponent() { return acsdkManufactory::ComponentAccumulator<>() #ifdef BLUETOOTH_ENABLED .addRetainedFactory(BluetoothNotifier::createBluetoothNotifierInterface) .addRetainedFactory(BluetoothMediaInputTransformer::create) - .addRequiredFactory(Bluetooth::createBluetoothCapabilityAgent) + .addRequiredFactory(createBluetooth) #else .addInstance(std::shared_ptr(nullptr)) + .addInstance(std::shared_ptr(nullptr)) #endif ; } diff --git a/capabilities/Bluetooth/acsdkBluetooth/src/CMakeLists.txt b/capabilities/Bluetooth/acsdkBluetooth/src/CMakeLists.txt index f00e72ad14..ad2176aaa3 100644 --- a/capabilities/Bluetooth/acsdkBluetooth/src/CMakeLists.txt +++ b/capabilities/Bluetooth/acsdkBluetooth/src/CMakeLists.txt @@ -1,7 +1,7 @@ add_definitions("-DACSDK_LOG_MODULE=bluetooth") add_library( - acsdkBluetooth SHARED + acsdkBluetooth BasicDeviceConnectionRule.cpp BasicDeviceConnectionRulesProvider.cpp Bluetooth.cpp diff --git a/capabilities/Bluetooth/acsdkBluetooth/test/BluetoothTest.cpp b/capabilities/Bluetooth/acsdkBluetooth/test/BluetoothTest.cpp index 4c95c9115b..15bf9d9cd0 100644 --- a/capabilities/Bluetooth/acsdkBluetooth/test/BluetoothTest.cpp +++ b/capabilities/Bluetooth/acsdkBluetooth/test/BluetoothTest.cpp @@ -668,6 +668,10 @@ void BluetoothTest::TearDown() { if (fileExists(TEST_DATABASE)) { remove(TEST_DATABASE.c_str()); } + + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_3, deviceCategoryToString(DeviceCategory::UNKNOWN)); } void BluetoothTest::wakeOnSetCompleted() { @@ -1128,9 +1132,6 @@ TEST_F(BluetoothTest, test_handleConnectByDeviceIdsDirectiveWithOnePhoneOneGadge // Verify the connection result. ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); ASSERT_TRUE(m_mockBluetoothDevice2->isConnected()); - // Reset the device category info stored in the database. - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); } /** @@ -1228,6 +1229,40 @@ TEST_F(BluetoothTest, test_handleConnectByProfileWithMatchedProfileName) { ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); } +/** + * Test call to handle the local connect() method. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1. The device belongs to + * DeviceCategory::UNKNOWN. The device is in disconnected state. + * Use case: + * A local connect() call is made with the MAC address of m_mockBluetoothDevice1. + * Expected result: + * 1) m_mockBluetoothDevice1 connects. + * 2) The observer should be notified of device connection once. + * 3) One @c ConnectByDeviceIdsSucceed event. + */ +TEST_F(BluetoothTest, test_handleConnectLocal) { + EXPECT_CALL(*m_mockBluetoothDeviceObserver, onActiveDeviceConnected(_)).Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(BLUETOOTH_STATE, _, StateRefreshPolicy::NEVER, _)).Times(Exactly(1)); + std::vector events = {CONNECT_BY_DEVICE_IDS_SUCCEEDED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + // Call connect() + m_Bluetooth->connect(TEST_BLUETOOTH_DEVICE_MAC); + + /* + * Mimic the @c DeviceStateChangedEvent which should happen after device connection status changes. + * In order to guarantee that all @c DeviceStateChangedEvent happen after the corresponding device connection + * status changes, force the test to wait EVENT_PROCESS_DELAY_MS. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::CONNECTED)); + })); + + // Verify the connection result. + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); +} + /** * Test call to handle PairDevices directive with matched device UUIDs. * Assumption: @@ -1307,10 +1342,43 @@ TEST_F(BluetoothTest, DISABLED_test_handlePairDevices) { ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); ASSERT_TRUE(m_mockBluetoothDevice2->isPaired()); ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); +} - // Reset device categories. - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); +/** + * Test call to handle the local pair() method. + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1. + * m_mockBluetoothDevice1 belongs to DeviceCategory::PHONE, which follows @c BasicDeviceConnectionRule. + * Use case: + * A local pair() call is made with the MAC address of m_mockBluetoothDevice1. + * Expected result: + * 1) m_mockBluetoothDevice1 pairs and connects. + * 2) The observer should be notified of device connection once. + * 3) One @c PairDevicesSucceeded and one @c ConnectByDeviceIdsSucceed event. + */ +TEST_F(BluetoothTest, test_handlePairLocal) { + // Initially, all devices are stored as DeviceCategory::UNKNOWN. Need to manually update device category in order + // to test. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::PHONE)); + + std::vector events = {PAIR_DEVICES_SUCCEEDED, CONNECT_BY_DEVICE_IDS_SUCCEEDED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + // Call pair() + m_Bluetooth->pair(TEST_BLUETOOTH_DEVICE_MAC); + + /* + * Mimic the @c DeviceStateChangedEvent which should happen after device connection status changes. + * In order to guarantee that all @c DeviceStateChangedEvent happen after the corresponding device connection + * status changes, force the test to wait EVENT_PROCESS_DELAY_MS. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS * 5)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::PAIRED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::CONNECTED)); + })); + + // Verify the pair result. + ASSERT_TRUE(m_mockBluetoothDevice1->isPaired()); + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); } /** @@ -1384,10 +1452,48 @@ TEST_F(BluetoothTest, test_handleUnpairDevices) { ASSERT_FALSE(m_mockBluetoothDevice1->isConnected()); ASSERT_FALSE(m_mockBluetoothDevice2->isPaired()); ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); +} - // Reset device categories. - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); +/** + * Test call to handle the local unpair() method. + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1. + * m_mockBluetoothDevice1 belongs to DeviceCategory::PHONE, which follows @c BasicDeviceConnectionRule. + * Use case: + * A local unpair() call is made with the MAC address of m_mockBluetoothDevice1. + * Expected result: + * 1) m_mockBluetoothDevice1 disconnects and unpairs. + * 2) The observer should be notified of device disconnection once. + * 3) One @c DisconnectDevicesSucceeded and one @c UnpairDevicesSucceeded event. + */ +TEST_F(BluetoothTest, test_handleUnpairLocal) { + // Initially, all devices are stored as DeviceCategory::UNKNOWN. Need to manually update device category in order + // to test. + m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::PHONE)); + + // Assume all devices are paired and connected before. + m_mockBluetoothDevice1->pair(); + m_mockBluetoothDevice1->connect(); + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); + + std::vector events = {DISCONNECT_DEVICES_SUCCEEDED, UNPAIR_DEVICES_SUCCEEDED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + // Call unpair() + m_Bluetooth->unpair(TEST_BLUETOOTH_DEVICE_MAC); + + /* + * Mimic the @c DeviceStateChangedEvent which should happen after device connection status changes. + * In order to guarantee that all @c DeviceStateChangedEvent happen after the corresponding device connection + * status changes, force the test to wait EVENT_PROCESS_DELAY_MS. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::DISCONNECTED)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::UNPAIRED)); + })); + + // Verify the unpair result. + ASSERT_FALSE(m_mockBluetoothDevice1->isPaired()); + ASSERT_FALSE(m_mockBluetoothDevice1->isConnected()); } /** @@ -1440,6 +1546,45 @@ TEST_F(BluetoothTest, test_handleDisconnectDevices) { ASSERT_FALSE(m_mockBluetoothDevice2->isConnected()); } +/** + * Test call to handle the local disconnect() method. + * + * Assumption: + * A @c DeviceManager contains m_mockBluetoothDevice1. The device belongs to + * DeviceCategory::UNKNOWN. The device is in connected state. + * Use case: + * A local disconnect() call is made with the MAC address of m_mockBluetoothDevice1. + * Expected result: + * 1) m_mockBluetoothDevice1 disconnects. + * 2) The observer should be notified of device disconnection once. + * 3) One @c DisconnectDevicesSucceeded event. + */ +TEST_F(BluetoothTest, test_handleDisconnectLocal) { + // Assume all devices are paired and connected before. + m_mockBluetoothDevice1->pair(); + m_mockBluetoothDevice1->connect(); + ASSERT_TRUE(m_mockBluetoothDevice1->isConnected()); + + EXPECT_CALL(*m_mockBluetoothDeviceObserver, onActiveDeviceDisconnected(_)).Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(BLUETOOTH_STATE, _, StateRefreshPolicy::NEVER, _)).Times(Exactly(1)); + std::vector events = {DISCONNECT_DEVICES_SUCCEEDED}; + ASSERT_TRUE(verifyMessagesSentInOrder(events, [this]() { + // Call disconnect() + m_Bluetooth->disconnect(TEST_BLUETOOTH_DEVICE_MAC); + + /* + * Mimic the @c DeviceStateChangedEvent which should happen after device connection status changes. + * In order to guarantee that all @c DeviceStateChangedEvent happen after the corresponding device connection + * status changes, force the test to wait EVENT_PROCESS_DELAY_MS. + */ + std::this_thread::sleep_for(std::chrono::milliseconds(EVENT_PROCESS_DELAY_MS)); + m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice1, DeviceState::DISCONNECTED)); + })); + + // Verify the disconnection result. + ASSERT_FALSE(m_mockBluetoothDevice1->isConnected()); +} + /** * Test call to handle SetDeviceCategories directive with matched device UUID. * @@ -1480,10 +1625,6 @@ TEST_F(BluetoothTest, test_handleSetDeviceCategories) { m_bluetoothStorage->getCategory(TEST_BLUETOOTH_UUID_2, &category2); ASSERT_EQ(deviceCategoryToString(DeviceCategory::PHONE), category1); ASSERT_EQ(deviceCategoryToString(DeviceCategory::GADGET), category2); - - // Reset device categories. - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); } TEST_F(BluetoothTest, test_contentDucksUponReceivingBackgroundFocus) { @@ -1574,10 +1715,6 @@ TEST_F(BluetoothTest, test_streamingStateChange) { // m_mockBluetoothDevice2 initiates connection. m_mockBluetoothDevice2->connect(); m_eventBus->sendEvent(DeviceStateChangedEvent(m_mockBluetoothDevice2, DeviceState::CONNECTED)); - - // Reset device categories. - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID, deviceCategoryToString(DeviceCategory::UNKNOWN)); - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_2, deviceCategoryToString(DeviceCategory::UNKNOWN)); } /** @@ -1617,8 +1754,6 @@ TEST_F(BluetoothTest, test_focusStateChange) { m_eventBus->sendEvent(MediaStreamingStateChangedEvent( MediaStreamingState::IDLE, A2DPRole::SINK, m_mockBluetoothDevice3)); m_wakeSetCompletedFuture.wait_for(WAIT_TIMEOUT_MS); - - m_bluetoothStorage->updateByCategory(TEST_BLUETOOTH_UUID_3, deviceCategoryToString(DeviceCategory::UNKNOWN)); } } // namespace test } // namespace acsdkBluetooth diff --git a/capabilities/Bluetooth/acsdkBluetoothInterfaces/include/acsdkBluetoothInterfaces/BluetoothLocalInterface.h b/capabilities/Bluetooth/acsdkBluetoothInterfaces/include/acsdkBluetoothInterfaces/BluetoothLocalInterface.h new file mode 100644 index 0000000000..3fb86ea85f --- /dev/null +++ b/capabilities/Bluetooth/acsdkBluetoothInterfaces/include/acsdkBluetoothInterfaces/BluetoothLocalInterface.h @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKBLUETOOTHINTERFACES_BLUETOOTHLOCALINTERFACE_H_ +#define ACSDKBLUETOOTHINTERFACES_BLUETOOTHLOCALINTERFACE_H_ + +namespace alexaClientSDK { +namespace acsdkBluetoothInterfaces { + +/** + * Interface to be implemented by class containing business logic for Bluetooth operations. + */ +class BluetoothLocalInterface { +public: + /** + * Destructor + */ + virtual ~BluetoothLocalInterface() = default; + + /** + * Puts the device into the desired discoverable mode. + * + * @param discoverable A bool indicating whether it should be discoverable. + */ + virtual void setDiscoverableMode(bool discoverable) = 0; + + /** + * Puts the device into the desired scan mode. + * + * @param scanning A bool indicating whether it should be scanning. + */ + virtual void setScanMode(bool scanning) = 0; + + /** + * Pair with the device matching the given MAC address. + * + * @param mac The MAC address associated with the device. + */ + virtual void pair(const std::string& addr) = 0; + + /** + * Unpair with the device matching the given MAC address. + * + * @param mac The MAC address associated with the device. + */ + virtual void unpair(const std::string& addr) = 0; + + /** + * Connect with the device matching the given MAC address. + * + * @param mac The MAC address associated with the device. + */ + virtual void connect(const std::string& addr) = 0; + + /** + * Disconnect from the device matching the given MAC address. + * + * @param mac The MAC address associated with the device. + */ + virtual void disconnect(const std::string& addr) = 0; +}; + +} // namespace acsdkBluetoothInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKBLUETOOTHINTERFACES_BLUETOOTHLOCALINTERFACE_H_ diff --git a/capabilities/Bluetooth/acsdkBluetoothInterfaces/include/acsdkBluetoothInterfaces/BluetoothNotifierInterface.h b/capabilities/Bluetooth/acsdkBluetoothInterfaces/include/acsdkBluetoothInterfaces/BluetoothNotifierInterface.h index affc3eb37d..b5bbf87c51 100644 --- a/capabilities/Bluetooth/acsdkBluetoothInterfaces/include/acsdkBluetoothInterfaces/BluetoothNotifierInterface.h +++ b/capabilities/Bluetooth/acsdkBluetoothInterfaces/include/acsdkBluetoothInterfaces/BluetoothNotifierInterface.h @@ -18,7 +18,7 @@ #include -#include +#include #include "acsdkBluetoothInterfaces/BluetoothDeviceObserverInterface.h" diff --git a/capabilities/DavsClient/.clang-format b/capabilities/DavsClient/.clang-format new file mode 100644 index 0000000000..f6ea4f5839 --- /dev/null +++ b/capabilities/DavsClient/.clang-format @@ -0,0 +1,69 @@ +--- +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: false +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +FixNamespaceComments: true +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Single +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyReturnTypeOnItsOwnLine: 20000 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 4 +UseTab: Never +... + diff --git a/capabilities/DavsClient/CMakeLists.txt b/capabilities/DavsClient/CMakeLists.txt new file mode 100644 index 0000000000..80e835355a --- /dev/null +++ b/capabilities/DavsClient/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.1) + +add_subdirectory("acsdkDavsClientInterfaces") + +add_subdirectory("acsdkAssetsInterfaces") + +if (ASSET_MANAGER) + add_subdirectory("acsdkAssetsCommon") + add_subdirectory("acsdkDavsClient") +endif() \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/CMakeLists.txt b/capabilities/DavsClient/acsdkAssetsCommon/CMakeLists.txt new file mode 100644 index 0000000000..44eaa4f5a5 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkAssetsCommon LANGUAGES CXX) + +if (DEFINED AVS_CMAKE_BUILD) + include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +endif() + +add_subdirectory("src") +add_subdirectory("test") diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/AmdMetricWrapper.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/AmdMetricWrapper.h new file mode 100644 index 0000000000..69fa19fcd5 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/AmdMetricWrapper.h @@ -0,0 +1,96 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_AMDMETRICWRAPPER_H_ +#define ACSDKASSETSCOMMON_AMDMETRICWRAPPER_H_ + +#include +#include +#include +#include +#include + +#define METRIC_PREFIX_ERROR(str) ("ERROR." str) +#define METRIC_PREFIX_ERROR_CREATE(str) METRIC_PREFIX_ERROR("Create." str) +#define METRIC_PREFIX_ERROR_INIT(str) METRIC_PREFIX_ERROR("Init." str) + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +/* + * Wrapper around the MetricRecorder + */ +class AmdMetricsWrapper { +public: + /** + * Creates and starts a new metric given a program and source name + */ + AmdMetricsWrapper(const std::string& sourceName); + + static std::function creator(const std::string& sourceName) { + return [sourceName] { return AmdMetricsWrapper(sourceName); }; + } + + static void setStaticRecorder( + std::shared_ptr recorder); + + AmdMetricsWrapper(const AmdMetricsWrapper&) = delete; + AmdMetricsWrapper& operator=(const AmdMetricsWrapper& other) = delete; + AmdMetricsWrapper(AmdMetricsWrapper&& other) = default; + AmdMetricsWrapper& operator=(AmdMetricsWrapper&& other) = delete; + + ~AmdMetricsWrapper(); + + /** + * Add a count data point + */ + AmdMetricsWrapper& addCounter(const std::string& name, int count = 1); + + /** + * Add a zero count data point + */ + inline AmdMetricsWrapper& addZeroCounter(const std::string& name) { + return addCounter(name, 0); + } + + /** + * Add a timer data point + */ + AmdMetricsWrapper& addTimer(const std::string& name, std::chrono::milliseconds value); + + /** + * Add a string data point + */ + AmdMetricsWrapper& addString(const std::string& name, const std::string& str); + +private: + /** + * Activity name of the metric + */ + const std::string m_sourceName; + /** + * vector to store datapoints before submitting the metric + */ + std::vector m_dataPoints; + + static std::shared_ptr s_recorder; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_AMDMETRICWRAPPER_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/ArchiveWrapper.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/ArchiveWrapper.h new file mode 100644 index 0000000000..ecd20abbed --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/ArchiveWrapper.h @@ -0,0 +1,104 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_ARCHIVEWRAPPER_H_ +#define ACSDKASSETSCOMMON_ARCHIVEWRAPPER_H_ + +#include +#include +#include + +#include + +struct archive; + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +/** + * Wraps the libarchive library as libarchive is not thread-safe. + * One one libarchive operation could happen at a given time. + */ +class ArchiveWrapper { +public: + // Destructor + virtual ~ArchiveWrapper() = default; + + /** + * Return an instance of this class. There should be only one instance of this class. + * @return an instance of this class, create one if it did not exist. + */ + static std::shared_ptr getInstance(); + + /** + * Get the total size of the contents of an archive when uncompressed. + * NOT the size of the archive on disk. + * + * @param fileName of the archive to get the size of. + * @return size of the given archive, or SIZE_MAX if an error occurs. + */ + size_t sizeOfArchive(const std::string& fileName); + + /** + * Uncompresses a given tar.gz or zip or other compression formats supported by libarchive into the destination + * folder + * @param fileName name of compressed file + * @param destFolder directory to place on. Must end in / + * @param directoryPermission OPTIONAL, permissions to use for the unpacked directories + * @param filePermission OPTIONAL, permissions to use for the unpacked files + */ + bool unpack( + const std::string& fileName, + const std::string& destFolder, + alexaClientSDK::avsCommon::utils::filesystem::Permissions directoryPermission = + alexaClientSDK::avsCommon::utils::filesystem::DEFAULT_DIRECTORY_PERMISSIONS, + alexaClientSDK::avsCommon::utils::filesystem::Permissions filePermission = + alexaClientSDK::avsCommon::utils::filesystem::DEFAULT_FILE_PERMISSIONS); + + /** + * Uncompresses a given tar.gz or zip or other compression formats supported by libarchive into the destination + * folder + * @param readArchive archive object that read archived data + * @param writeArchive archive object that write unpacked data + * @param destFolder directory to place unpacked data in. Must not end in / + * @param directoryPermission OPTIONAL, permissions to use for the unpacked directories + * @param filePermission OPTIONAL, permissions to use for the unpacked files + */ + bool unpack( + struct archive* reader, + struct archive* writer, + const std::string& destFolder, + alexaClientSDK::avsCommon::utils::filesystem::Permissions directoryPermission = + alexaClientSDK::avsCommon::utils::filesystem::DEFAULT_DIRECTORY_PERMISSIONS, + alexaClientSDK::avsCommon::utils::filesystem::Permissions filePermission = + alexaClientSDK::avsCommon::utils::filesystem::DEFAULT_FILE_PERMISSIONS); + +private: + /// private constructor + ArchiveWrapper() = default; + + /// instance of this object + static std::shared_ptr m_instance; + + /// mutex to serialize API calls as libArchive is not thread-safe + std::mutex m_mutex; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_ARCHIVEWRAPPER_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/Base64Url.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/Base64Url.h new file mode 100644 index 0000000000..83f8576925 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/Base64Url.h @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_BASE64URL_H_ +#define ACSDKASSETSCOMMON_BASE64URL_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +/** + * This is added to wrap Base64 functionality because we have no clean way of using it across all cases. + * We need to use Base64 on device and we'll use libssl (BoringSSL) for that, but the code in OpenSSL and BoringSSL + * differs for Base64! :( So for unit tests, and for running on Mac/Ubuntu, we'll use code copied over from mbedtls. + */ +class Base64Url { +public: + /** + * Encodes plain text into URL-friendly Base64 version. + * @param plain IN The text to encode. + * @param encoded OUT Base64 string but further modified so that '+', '/' and '=' are converted to '%2B', '%2F' and + * '%3D' respectively. + * @return true if successful + */ + static bool encode(const std::string& plain, std::string& encoded); + + // Reverse operation from encode(). + // NOTE: actually, this is not used in production, it is only used by unit tests. + static bool decode(const std::string& encoded, std::string& plain); + +private: + Base64Url() = delete; + ~Base64Url() = delete; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_BASE64URL_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/CurlProgressCallbackInterface.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/CurlProgressCallbackInterface.h new file mode 100644 index 0000000000..0c830df17d --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/CurlProgressCallbackInterface.h @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_CURLPROGRESSCALLBACKINTERFACE_H_ +#define ACSDKASSETSCOMMON_CURLPROGRESSCALLBACKINTERFACE_H_ + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +class CurlProgressCallbackInterface { +public: + virtual ~CurlProgressCallbackInterface() = default; + + /** + * An event that is called with a frequent interval. While data is being transferred it will be + * called very frequently, and during slow periods like when nothing is being transferred it can + * slow down to about one call per second. + * + * @param dlTotal, total bytes need to be downloaded, 0 if Unknown/unused + * @param dlNow, number of bytes downloaded so far, 0 if Unknown/unused + * @param ulTotal, total bytes need to be uploaded, 0 if Unknown/unused + * @param ulNow, number of bytes uploaded so far, 0 if Unknown/unused + * @return Returning false will cause libcurl to abort the transfer and return + */ + virtual bool onProgressUpdate(long dlTotal, long dlNow, long ulTotal, long ulNow) = 0; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_CURLPROGRESSCALLBACKINTERFACE_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/CurlWrapper.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/CurlWrapper.h new file mode 100644 index 0000000000..deeb81ef7a --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/CurlWrapper.h @@ -0,0 +1,215 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_CURLWRAPPER_H_ +#define ACSDKASSETSCOMMON_CURLWRAPPER_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "DownloadChunkQueue.h" +#include "DownloadStream.h" +#include "ResponseSink.h" +#include "acsdkAssetsCommon/CurlProgressCallbackInterface.h" +#include "acsdkAssetsInterfaces/ResultCode.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +/** + * Wraps the libcurl library in a more C++ friendly manner. Still undecided if this will be generic or specific to + * DAVS. Likely will be DAVS specific. + */ +class CurlWrapper { +public: + // Destructor takes care of freeing the CURL handle + virtual ~CurlWrapper(); + + /** + * Creates and initializes the object. + * @param isThrottled whether the download should be slowed down for throttling + * @param authDelegate the Authentication Delegate to generate the authentication token + * @param certPath - optional - a path to a local SSL cert to use instead of maplite + * @return a valid smart pointer to a valid object that can be used, or nullptr in case of error + */ + static std::unique_ptr create( + bool isThrottled, + std::shared_ptr authDelegate, + const std::string& certPath = ""); + /** + * Executes HTTP GET request and populates the response. + * @param url IN, the URL to execute, must be a https. It should include any query parameters as well. + * @param response OUT, the ostream to populate with the HTTP entity-body; it may end up empty if entity-body is + * empty or not present. + * @param callbackObj object that implements CurlProgressCallbackInterface + * @return ResultCode of the response. + */ + commonInterfaces::ResultCode get( + const std::string& url, + std::ostream& response, + const std::weak_ptr& callbackObj); + /** + * Synchronously download a remote URL to local file or directory (if unpack is needed) + * @param url the URL to download from + * @param path the absolute path to a file (or directory) to write to (if unpack is needed) + * @param callbackObj object that implements CurlProgressCallbackInterface + * @param unpack whether unpack is needed during download, size must be specified or download would fail. + * @param size size of the file to be downloaded, if not specified or set to 0, size check will be skipped. (except + * when unpack=1) + * @return SUCCESS if successfully downloaded + * Note: if return value is false, the file may be partially written to. + */ + commonInterfaces::ResultCode download( + const std::string& url, + const std::string& path, + const std::weak_ptr& callbackObj, + bool unpack = false, + size_t size = 0); + + /// Return status for header APIs + using HeaderResults = avsCommon::utils::error::Result; + + /** + * Executes HTTP HEAD request and populates the response. + * @param url IN, the URL to execute, must use https. It should include any + * query parameters as well. + * @return the string populated with the HTTP Headers, or an empty string upon failure. + */ + HeaderResults getHeaders(const std::string& url); + /** + * Executes HTTP HEAD request and populates the response. Authorizes to access davs headers + * @param url IN, the URL to execute, must use https. It should include any + * query parameters as well. + * @return the string populated with the HTTP Headers, or an empty string upon failure. + */ + HeaderResults getHeadersAuthorized(const std::string& url); + + /** + * Executes HTTP GET request which returns a multipart response streams the + * data in chunks and downloads them to file. + * @param url IN, the URL to execute, must be a https. It should include any + * query parameters as well. + * @param sink OUT, object that is used by the multipart parser to read in the + * data. + * @param callbackObj object that implements CurlProgressCallbackInterface + * @return ResultCode of the response. + */ + commonInterfaces::ResultCode getAndDownloadMultipart( + const std::string& url, + std::shared_ptr sink, + const std::weak_ptr& callbackObj); + + static std::string getValueFromHeaders(const std::string& headers, const std::string& key); + +private: + explicit CurlWrapper( + bool isThrottled, + std::shared_ptr authDelegate, + std::string certPath = ""); + + // Initializes the object, returns true if successful. + bool init(); + + /** + * Gets MAP Authentication header. + * @param header the string to populate + * @return true if managed to read token from MAP + */ + bool getAuthorizationHeader(std::string& header); + + /** + * Streams the HTTP response into a given stream. + * @param fullUrl the URL to download via GET + * @param responseStream where to stream response + * @param callbackObj object that implements CurlProgressCallbackInterface + * @return SUCCESS if the data transfer completed successfully + * Note: if return value is false, the stream may be partially written to. + */ + commonInterfaces::ResultCode stream( + const std::string& fullUrl, + std::ostream& responseStream, + const std::weak_ptr& callbackObj); + /** + * Streams the HTTP response into a given file. + * @param downloadUrl the URL to download via GET + * @param filePath file path for the downloaded file + * @param size expected size of the file to be downloaded + * @param callbackObj object that implements CurlProgressCallbackInterface + * @return SUCCESS if the data transfer completed successfully + * Note: if return value is false, the stream may be partially written to. + */ + commonInterfaces::ResultCode streamToFile( + const std::string& downloadUrl, + const std::string& filePath, + size_t size, + const std::weak_ptr& callbackObj); + + /** + * Streams the HTTP response into download chunk queue + * @param fullUrl the URL to download via GET + * @param downloadChunkQueue queue to hold downloaded data chunks + * @param callbackObj object that implements CurlProgressCallbackInterface + * @return SUCCESS if the data transfer completed successfully + */ + commonInterfaces::ResultCode streamToQueue( + const std::string& fullUrl, + std::shared_ptr downloadChunkQueue, + const std::weak_ptr& callbackObj); + + /** + * Unpack downloaded chunks from download chunk queue + * @param downloadChunkQueue queue to hold downloaded data chunks + * @param path parent directory of unpacked files + * @return true if unpack (and download) completed successfully + */ + bool unpack(std::shared_ptr downloadChunkQueue, const std::string& path); + + /** + * Check HTTP return code for the previous curl operation, and map the HTTP response code to ResultCode + * @param logError true if an error should be logged and a metric created, false if errors can be ignored + * @return HTTP response code mapped to ResultCode + */ + commonInterfaces::ResultCode checkHTTPStatusCode(bool logError = true); + + /** + * Calls getAuthorizationHeader and sets the httpHeader curl option. + * @return the result code for the ability to set the HTTP Header + */ + commonInterfaces::ResultCode setHTTPHEADER(); + +private: + bool m_isThrottled; + std::string m_certPath; + CURLcode m_code; + CURL* m_handle; + char m_errorBuffer[CURL_ERROR_SIZE]{}; + std::string m_header; + // the curl handle does not take ownership of the memory in headers, and the handle re-uses headers + std::unique_ptr m_headers; + std::shared_ptr m_authDelegate; +}; +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_CURLWRAPPER_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DataChunk.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DataChunk.h new file mode 100644 index 0000000000..8e9fc889a9 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DataChunk.h @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_DATACHUNK_H_ +#define ACSDKASSETSCOMMON_DATACHUNK_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +/** + * Represent a binary data chunk + */ +class DataChunk { +public: + /** + * Constructor to DataChunk object + * @param data binary data to copy from + * @param size number of bytes + */ + DataChunk(char* data, size_t size); + + ~DataChunk(); + + /// number of bytes in the data chunk + size_t size() const; + + char* data() const; + +private: + // number of bytes in the data chunk + size_t m_size; + /// pointer to the binary data + char* m_data; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_DATACHUNK_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DownloadChunkQueue.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DownloadChunkQueue.h new file mode 100644 index 0000000000..0ce533643f --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DownloadChunkQueue.h @@ -0,0 +1,134 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_DOWNLOADCHUNKQUEUE_H_ +#define ACSDKASSETSCOMMON_DOWNLOADCHUNKQUEUE_H_ + +#include +#include +#include +#include + +#include "DataChunk.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +/** + * Status of streaming download/unpack + */ +enum class StreamingStatus { INPROGRESS, COMPLETED, ABORTED }; + +/* + * Consumer Producer queue with blocking wait and pop operation to store downloaded data chunks. + * It comes with downloaded file size validation. + */ +class DownloadChunkQueue { +public: + /** + * Constructing a new queue to hold downloaded data chunks + * @param expectedSize expected download size. Pushing more or less data before completion signals error + * unless the user has signaled no size check with the expected size of 0. + */ + explicit DownloadChunkQueue(size_t expectedSize); + + virtual ~DownloadChunkQueue(); + + /** + * Returning number of data chunks in the queue. + * @return number of data chunks in the queue. + */ + size_t size(); + + /** + * Producer pushes new data chunk into download queue + * @param data pointer for data chunk + * @param size number of bytes in the data chunk + * @return true when successful, false for invalid argument or if accumulated size exceeds expectedSize + */ + bool push(char* data, size_t size); + + /** + * Producer signals push completion + * @param succeeded whether download succeeded + * @return whether total downloaded size matches the expected size unless aborted if size check isn't turned off + */ + bool pushComplete(bool succeeded); + + /** + * Blocking wait and get the next data chunk from queue. + * @return next data chunk from the front of the queue, or nullptr if error has been detected or no more data. + * The last waitAndPop should be followed by popComplte() + */ + std::shared_ptr waitAndPop(); + + /** + * Consumer signals the completion of reading from queue. Will wait until producer pushComplete + * or return false if data chunks still available in queue. + * @param succeeded whether the completion is triggered by an error/succes condition + * @return false if there're still remaining chunks in the queue or download has failed + */ + bool popComplete(bool succeeded); + +private: + /** + * @return true if size checking is enabled (that is expected size is not 0) + */ + inline bool isSizeCheckingEnabled() const { + return m_expectedSize > 0; + } + +private: + /// condition variable to signal blocking pop function new chunks available + std::condition_variable m_cond; + + /// mutex to protect the member variables including m_queue + std::mutex m_mutex; + + /// queue holding downloaded data chunks + std::queue> m_queue; + + /// expected file size for the artifact to be downloaded + size_t m_expectedSize; + + /// size downloaded so far (pushed into queue) + size_t m_downloadedSize; + + /// status of producer (curl download) + StreamingStatus m_downloadStatus; + + /// status of consumer (archive unpack) + StreamingStatus m_unpackStatus; + + /// current active (unpacking) data chunk that has been popped from queue + /// We keep it here so raw data pointer would still be valid after after it has been passed to libarchive + std::shared_ptr m_activeChunk; + + /// maximum queue size reached, for analysis and future optimization purpose + size_t m_maxQueueSizeReached; + + /// Next download benchmark to log + size_t m_bytesToReport; + + /// download increment to report + size_t m_reportIncrement; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_DOWNLOADCHUNKQUEUE_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DownloadStream.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DownloadStream.h new file mode 100644 index 0000000000..7b16b8b17f --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/DownloadStream.h @@ -0,0 +1,85 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_DOWNLOADSTREAM_H_ +#define ACSDKASSETSCOMMON_DOWNLOADSTREAM_H_ + +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +/* + * Consumer Producer queue with blocking wait and pop operation to store downloaded data chunks. + * It comes with downloaded file size validation. + */ +class DownloadStream { +public: + /** + * Create download file object with expected size + * @param path file path to write to + * @param expectedSize expected download file size + * @return downloadStream object or null if file path invalid + */ + static std::shared_ptr create(const std::string& path, size_t expectedSize); + + virtual ~DownloadStream(); + + /** + * Write data chunk into the output stream + * @param data pointer for data chunk + * @param size number of bytes in the data chunk + * @return true when successful, false for invalid argument or if accumulated size exceeds expectedSize + */ + bool write(const char* data, size_t size); + + bool downloadSucceeded() const; + +private: + /** + * Constructing a new object to hold download outputstream and its expected size + * @param @outputStream ostream object where downloaded data will be written into + * @param expectedSize expected download size. + */ + explicit DownloadStream(const std::string& path, size_t expectedSize); + + /** + * Whether the previous stream operation is good (no error) + * @return true if no error, false otherwise + */ + bool good() const; + + /// mutex to protect the member variables including m_downloadedSize + mutable std::mutex m_mutex; + + /// output stream to write data to + std::ofstream m_ostream; + + /// expected file size for the artifact to be downloaded + size_t m_expectedSize; + + /// size downloaded so far + size_t m_downloadedSize; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_DOWNLOADSTREAM_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/JitterUtil.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/JitterUtil.h new file mode 100644 index 0000000000..ed7db561ca --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/JitterUtil.h @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_JITTERUTIL_H_ +#define ACSDKASSETSCOMMON_JITTERUTIL_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { +namespace jitterUtil { + +/** + * Provides a time delay off baseValue with some jitteriness + * @param baseValue value to base off of + * @param jitterFactor factor of jitter from baseValue, 0 > and < 1 + * @return + */ +std::chrono::milliseconds jitter(std::chrono::milliseconds baseValue, float jitterFactor = 0.2); + +/** + * Provides a time delay off baseValue with some jitteriness and 2x exponential back-off + * @param baseValue value to base off of + * @param jitterFactor exponentially increases baseValue by this amount with jitter + * @return + */ +std::chrono::milliseconds expJitter(std::chrono::milliseconds baseValue, float jitterFactor = 0.2); + +} // namespace jitterUtil +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_JITTERUTIL_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/ResponseSink.h b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/ResponseSink.h new file mode 100644 index 0000000000..218500de30 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/include/acsdkAssetsCommon/ResponseSink.h @@ -0,0 +1,134 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSCOMMON_RESPONSESINK_H_ +#define ACSDKASSETSCOMMON_RESPONSESINK_H_ +#include +#include + +#include "DownloadChunkQueue.h" +#include "DownloadStream.h" +#include "acsdkAssetsInterfaces/DavsRequest.h" +#include "acsdkAssetsInterfaces/VendableArtifact.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +/** + * Handle Multipart DAVS Responses. + * + */ +class ResponseSink { +public: + /** + * Constructor. + * + */ + ResponseSink(const std::shared_ptr& request, const std::string& workingDirectory); + + /** + * Destructor. + */ + virtual ~ResponseSink() = default; + + /** + * get m_artifact + * @return NULLABLE, returns m_artifact + */ + std::shared_ptr getArtifact(); + + /** + * get the full path to the artifact if it's downloaded properly + * @return path to the artifact, empty if not downloaded or error + */ + std::string getArtifactPath(); + + /** + * When we receive a header line search for boundary. If we find + * the boundary set m_boundary and notify waiting threads. + * @param header the header line which we search for the boundary. + * @return + */ + bool onHeader(const std::string& header); + + /** + * Wrapper around the multipart Parser, used to set the parsers callbacks and + * feed it data. + * @param downloadChunkQueue handles streaming data chunk by chunk. + * @return true if parsing of response succeeds + */ + bool parser(std::shared_ptr& downloadChunkQueue); + +private: + /** + * Sets the content type member variable + * @param type the current content type. + */ + void setContentType(const std::string& type); + /** + * Either appends value to m_jsonString if the content type is JSON or writes the + * buffer to the DownloadStream if the content type is ATTACHMENT + * @param buffer the data to be written + * @param size the size of the data to be written. + */ + void setData(const char* buffer, size_t size); + /** + * The function that is called at the end of receiving data. Will create an + * artifact at the end of receiving json information. + */ + void endData(); + +private: + /// Types of mime parts + enum class ContentType { + /// The default value, indicating no data. + NONE, + /// The content represents a JSON formatted string. + JSON, + /// The content represents binary data. + ATTACHMENT + }; + + /// Type of content in the current part. + ContentType m_contentType; + + /// The json string in the multipart response. + std::string m_jsonString; + + /// The download stream where the attachment will be written. + std::shared_ptr m_attachment; + + /// The parent directory where the attachment will be written. + const std::string m_parentDirectory; + + /// The full path to the artifact once it is downloaded + std::string m_artifactPath; + + /// The current DAVs request, will be used to create the artifact. + std::shared_ptr m_request; + + /// The artifact that is created from the response. + std::shared_ptr m_artifact; + + /// The boundary for the multipart response. + std::string m_boundary; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSCOMMON_RESPONSESINK_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/AmdMetricWrapper.cpp b/capabilities/DavsClient/acsdkAssetsCommon/src/AmdMetricWrapper.cpp new file mode 100644 index 0000000000..a88e7e862d --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/AmdMetricWrapper.cpp @@ -0,0 +1,90 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/AmdMetricWrapper.h" + +#include +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; +using namespace chrono; +using namespace alexaClientSDK::avsCommon::utils::metrics; + +/// String to identify log entries originating from this file. +static const std::string TAG("MetricWrapper"); +/** + * 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) + +shared_ptr AmdMetricsWrapper::s_recorder; + +void AmdMetricsWrapper::setStaticRecorder( + std::shared_ptr recorder) { + s_recorder = move(recorder); +} + +AmdMetricsWrapper::AmdMetricsWrapper(const string& sourceName) : m_sourceName{sourceName} { +} +AmdMetricsWrapper::~AmdMetricsWrapper() { + if (s_recorder == nullptr) { + ACSDK_WARN(LX("~AmdMetricsWrapper").m("Metrics Recorder is not initialized")); + return; + } + if (m_dataPoints.empty()) { + ACSDK_ERROR(LX("~AmdMetricsWrapper").m("No datapoints to record")); + return; + } + auto metricEvent = MetricEventBuilder{}.setActivityName(m_sourceName).addDataPoints(m_dataPoints).build(); + if (!metricEvent) { + ACSDK_ERROR(LX("~AmdMetricsWrapper").m("Error creating metric.")); + } + s_recorder->recordMetric(metricEvent); +} + +AmdMetricsWrapper& AmdMetricsWrapper::addCounter(const string& name, int count) { + ACSDK_DEBUG(LX("addCounter").m("addCounterDataPoint").d("name", name).d("count", count)); + m_dataPoints.push_back(DataPointCounterBuilder{}.setName(name).increment(count).build()); + + return *this; +} + +AmdMetricsWrapper& AmdMetricsWrapper::addTimer(const string& name, milliseconds value) { + ACSDK_DEBUG(LX("addTimer").m("addTimerDataPoint").d("name", name).d("duration", value.count())); + m_dataPoints.push_back(DataPointDurationBuilder{value}.setName(name).build()); + + return *this; +} + +AmdMetricsWrapper& AmdMetricsWrapper::addString(const string& name, const string& str) { + ACSDK_DEBUG(LX("addString").m("addStringDataPoint").d("name", name).d("value ", str)); + m_dataPoints.push_back(DataPointStringBuilder{}.setName(name).setValue(str).build()); + + return *this; +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/ArchiveWrapper.cpp b/capabilities/DavsClient/acsdkAssetsCommon/src/ArchiveWrapper.cpp new file mode 100644 index 0000000000..6aa64e1a1e --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/ArchiveWrapper.cpp @@ -0,0 +1,238 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/ArchiveWrapper.h" + +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; +using namespace alexaClientSDK::avsCommon::utils; + +static const std::string TAG{"ArchiveWrapper"}; +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +shared_ptr ArchiveWrapper::m_instance = nullptr; +static constexpr size_t SIXTY_FOUR_MEGABYTES = 64 * 1024 * 1024; + +shared_ptr ArchiveWrapper::getInstance() { + static mutex s_mutex; + unique_lock lock(s_mutex); + + if (ArchiveWrapper::m_instance == nullptr) { + ArchiveWrapper::m_instance.reset(new ArchiveWrapper()); + } + return ArchiveWrapper::m_instance; +} + +size_t ArchiveWrapper::sizeOfArchive(const std::string& fileName) { + auto readArchive = unique_ptr(archive_read_new(), archive_read_free); + + // Other archive formats are supported as well: see '*_all' in libarchive + archive_read_support_format_all(readArchive.get()); + + // Other filters are supported as well: see '*_all' in libarchive + archive_read_support_filter_all(readArchive.get()); + + // Open a the archive file with the maximum buffer size + auto archiveStatus = archive_read_open_filename(readArchive.get(), fileName.data(), 10240); + if (archiveStatus != ARCHIVE_OK) { + ACSDK_ERROR(LX("sizeOfArchive").m("Failed to open file").d("error", archive_error_string(readArchive.get()))); + return SIZE_MAX; + } + + size_t archiveSize = 0; + + struct archive_entry* entry; + while (true) { + archiveStatus = archive_read_next_header(readArchive.get(), &entry); + if (archiveStatus == ARCHIVE_EOF) { + break; + } + + if (archiveStatus != ARCHIVE_OK) { + ACSDK_ERROR(LX("sizeOfArchive") + .m("Failed to read next header") + .d("error", archive_error_string(readArchive.get()))); + archiveSize = SIZE_MAX; + break; + } + + int64_t archiveEntrySize = archive_entry_size(entry); + if (archiveEntrySize <= 0) { + ACSDK_ERROR(LX("sizeOfArchive").m("Archive entry has invalid").d("size", archiveEntrySize)); + archiveSize = SIZE_MAX; + break; + } + + archiveSize += archiveEntrySize; + } + + archive_read_close(readArchive.get()); + return archiveSize; +} + +static int copyData(struct archive* readArchive, struct archive* writeArchive) { + const void* buff; + size_t size; + int64_t offset; + size_t total_bytes_written = 0; + + while (true) { + auto result = archive_read_data_block(readArchive, &buff, &size, &offset); + total_bytes_written += size; + + if (total_bytes_written > SIXTY_FOUR_MEGABYTES) { + return ARCHIVE_FAILED; + } + + if (result == ARCHIVE_EOF) return (ARCHIVE_OK); + if (result != ARCHIVE_OK) return (result); + result = static_cast(archive_write_data_block(writeArchive, buff, size, offset)); + if (result != ARCHIVE_OK) { + return (result); + } + } +} + +static bool unpackLocked( + struct archive* reader, + struct archive* writer, + const string& destFolder, + filesystem::Permissions directoryPermission, + filesystem::Permissions filePermission) { + vector writtenFilesList; + auto allFilesWrittenSuccessfully = true; + + int archiveStatus = ARCHIVE_EOF; + + ACSDK_INFO(LX("unpackLocked").m("start unpacking archive").d("destination", destFolder)); + struct archive_entry* entry; + while (true) { + archiveStatus = archive_read_next_header(reader, &entry); + if (archiveStatus == ARCHIVE_EOF) { + break; + } + + if (archiveStatus != ARCHIVE_OK) { + ACSDK_ERROR(LX("unpackLocked").m("Failed to read next header").d("error", archive_error_string(reader))); + allFilesWrittenSuccessfully = false; + break; + } + + // write the file from archive into the destination folder with the same file name + const string fullOutputPath = destFolder + "/" + archive_entry_pathname(entry); + if (!filesystem::pathContainsPrefix(fullOutputPath, destFolder)) { + // path wasn't under destFolder, something isn't right + ACSDK_ERROR(LX("unpackLocked").m("Unable to write to destination").d("path", fullOutputPath)); + allFilesWrittenSuccessfully = false; + break; + } + + archive_entry_set_pathname(entry, fullOutputPath.c_str()); + writtenFilesList.push_back(fullOutputPath); + + // write this single file + archiveStatus = archive_write_header(writer, entry); + if (archiveStatus != ARCHIVE_OK) { + ACSDK_ERROR(LX("unpackLocked").m("Failed to write header").d("error", archive_error_string(writer))); + allFilesWrittenSuccessfully = false; + break; + } + + copyData(reader, writer); + archiveStatus = archive_write_finish_entry(writer); + struct stat fileStat {}; + auto rc = stat(fullOutputPath.data(), &fileStat); + if (archiveStatus != ARCHIVE_OK || rc != 0) { + ACSDK_ERROR(LX("unpackLocked") + .m("Failed to close file we just wrote") + .d("error", archive_error_string(writer))); + allFilesWrittenSuccessfully = false; + break; + } + + auto perm = (fileStat.st_mode & S_IFMT) == S_IFDIR ? directoryPermission : filePermission; + filesystem::changePermissions(fullOutputPath, perm); + } + + if (archive_read_close(reader) != ARCHIVE_OK) { + allFilesWrittenSuccessfully = false; + } + if (archive_write_close(writer) != ARCHIVE_OK) { + allFilesWrittenSuccessfully = false; + } + + // clean up written files if a file is corrupt + if (!allFilesWrittenSuccessfully) { + ACSDK_WARN(LX("unpackLocked").m("Failed to write files, cleaning up")); + for (const auto& filePath : writtenFilesList) { + filesystem::removeAll(filePath); + } + } + + return allFilesWrittenSuccessfully; +} + +bool ArchiveWrapper::unpack( + const string& fileName, + const string& destFolder, + const filesystem::Permissions directoryPermission, + const filesystem::Permissions filePermission) { + unique_lock lock(m_mutex); + ACSDK_INFO(LX("unpack").m("start unpacking").d("source", fileName).d("destination", destFolder)); + + auto readArchive = unique_ptr(archive_read_new(), archive_read_free); + auto writeArchive = + unique_ptr(archive_write_disk_new(), archive_write_free); + + // Other archive formats are supported as well: see '*_all' in libarchive + archive_read_support_format_all(readArchive.get()); + + // Other filters are supported as well: see '*_all' in libarchive + archive_read_support_filter_all(readArchive.get()); + + // Open a the archive file with the maximum buffer size + if (ARCHIVE_OK != archive_read_open_filename(readArchive.get(), fileName.data(), 10240)) { + ACSDK_ERROR(LX("unpack").m("Failed to open filename").d("error", archive_error_string(readArchive.get()))); + return false; + } + return unpackLocked(readArchive.get(), writeArchive.get(), destFolder, directoryPermission, filePermission); +} + +bool ArchiveWrapper::unpack( + struct archive* reader, + struct archive* writer, + const string& destFolder, + const filesystem::Permissions directoryPermission, + const filesystem::Permissions filePermission) { + if (reader == nullptr || writer == nullptr) { + ACSDK_ERROR(LX("unpack").m("Invalid archive reader/writer")); + return false; + } + + unique_lock lock(m_mutex); + return unpackLocked(reader, writer, destFolder, directoryPermission, filePermission); +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/Base64Url.cpp b/capabilities/DavsClient/acsdkAssetsCommon/src/Base64Url.cpp new file mode 100644 index 0000000000..c5e778f2ac --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/Base64Url.cpp @@ -0,0 +1,140 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +// Real implementations +#include +#include +#include +#include +#include + +#include "acsdkAssetsCommon/Base64Url.h" + +using namespace std; + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +/// String to identify log entries originating from this file. +static const std::string TAG{"Base64Url"}; + +/** + * 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) + +bool encodeBase64(const std::vector& binary, std::string* string) { + if (!string) { + return false; + } else if (binary.empty()) { + string->resize(0); + return true; + } + // Base64 creates 4 output bytes for each 3 bytes of input + int len = binary.size() / 3 * 4; + // If input size is not dividable by 3, Base64 creates an additional 4 byte block. + if (binary.size() % 3) { + len += 4; + } + // OpenSSL encoder adds a null character when encoding. + len++; + std::vector tmp; + tmp.resize(len); + EVP_EncodeBlock((unsigned char*)&tmp[0], (const unsigned char*)binary.data(), binary.size()); + *string = tmp.data(); + return true; +} + +bool decodeBase64(const std::string& string, std::vector* binary) noexcept { + if (!binary) { + return false; + } else if (string.empty()) { + binary->resize(0); + return true; + } + int len = string.size() / 4 * 3; + if (string.size() % 4) { + len += 3; + } + int index = 0; + binary->resize(index + len); + len = EVP_DecodeBlock(&(*binary)[index], (const unsigned char*)&string[0], string.size()); + if (len >= 0) { + return true; + } else { + return false; + } +} + +bool Base64Url::encode(const string& plain, string& encoded) { + vector binaryInput{plain.begin(), plain.end()}; + string dest; + if (!encodeBase64(binaryInput, &dest)) { + ACSDK_ERROR(LX("encode").m("Failed to encode string").d("input", plain)); + return false; + } + + auto curl = unique_ptr(curl_easy_init(), curl_easy_cleanup); + if (curl == nullptr) { + ACSDK_ERROR(LX("encode").m("Can't initialize curl")); + return false; + } + auto escaped = unique_ptr( + curl_easy_escape(curl.get(), dest.data(), static_cast(dest.length())), curl_free); + if (escaped == nullptr) { + ACSDK_ERROR(LX("encode").m("Can't URL-escape the string")); + return false; + } + + encoded = escaped.get(); + return true; +} + +bool Base64Url::decode(const string& encoded, string& plain) { + auto curl = unique_ptr(curl_easy_init(), curl_easy_cleanup); + if (curl == nullptr) { + ACSDK_ERROR(LX("decode").m("Can't initialize curl")); + return false; + } + + if (encoded.size() > INT_MAX) { + ACSDK_ERROR(LX("decode").m("Unexpected input size").d("size", encoded.size())); + return false; + } + + int unescapedSize; + auto unescaped = unique_ptr( + curl_easy_unescape(curl.get(), encoded.data(), static_cast(encoded.size()), &unescapedSize), + curl_free); + if (unescaped == nullptr) { + ACSDK_ERROR(LX("decode").m("Can't URL-escape the string")); + return false; + } + + vector binaryOutput; + if (!decodeBase64(unescaped.get(), &binaryOutput)) { + ACSDK_ERROR(LX("decode").m("Failed to decode string").d("input", encoded)); + return false; + } + plain = string{binaryOutput.begin(), binaryOutput.end()}; + return true; +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/CMakeLists.txt b/capabilities/DavsClient/acsdkAssetsCommon/src/CMakeLists.txt new file mode 100644 index 0000000000..920fed033b --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/CMakeLists.txt @@ -0,0 +1,34 @@ +add_definitions("-DACSDK_LOG_MODULE=DavsClient") + +if (NOT LibArchive_FOUND) + find_package(LibArchive) +endif() + +add_library(acsdkAssetsCommon + AmdMetricWrapper.cpp + ArchiveWrapper.cpp + Base64Url.cpp + CurlWrapper.cpp + DataChunk.cpp + DownloadChunkQueue.cpp + DownloadStream.cpp + JitterUtil.cpp + ResponseSink.cpp + ) + +target_include_directories(acsdkAssetsCommon PUBLIC + ${LibArchive_INCLUDE_DIRS} + ${CRYPTO_INCLUDE_DIRS} + ${acsdkAssetsCommon_SOURCE_DIR}/include + ) + +target_link_libraries(acsdkAssetsCommon + ${LibArchive_LIBRARIES} + ${CRYPTO_LDFLAGS} + AVSCommon + acsdkAssetsInterfaces + acsdkDavsClientInterfaces + ) + +# install target +asdk_install() diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/CurlWrapper.cpp b/capabilities/DavsClient/acsdkAssetsCommon/src/CurlWrapper.cpp new file mode 100644 index 0000000000..d21de0e77a --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/CurlWrapper.cpp @@ -0,0 +1,651 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/CurlWrapper.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "acsdkAssetsCommon/AmdMetricWrapper.h" +#include "acsdkAssetsCommon/ArchiveWrapper.h" +#include "acsdkAssetsCommon/ResponseSink.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; +using namespace chrono; +using namespace commonInterfaces; +using namespace alexaClientSDK::avsCommon::utils::filesystem; +using namespace alexaClientSDK::avsCommon::utils::string; +using namespace alexaClientSDK::avsCommon::utils::error; + +using headers_ptr = unique_ptr; + +static const auto s_metrics = AmdMetricsWrapper::creator("curlWrapper"); + +static const long HTTP_SERVER_ERROR = 500; + +static const curl_off_t THROTTLED_SPEED_KB = 256 * 1024 / 8; // 256 Kbits; + +/// Number of data chunks in download (buffering) queue before we attempt to slow down download +static const size_t DOWNLOAD_QUEUE_SIZE_THRESHOLD = 50; + +/// String to identify log entries originating from this file. +static const std::string TAG{"CurlWrapper"}; + +/** + * 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) + +/// This is needed to get around some compiler errors in MinGW around using curl functions statically +static void curlSlistFreeAllDelegate(struct curl_slist* curSlist) { + curl_slist_free_all(curSlist); +} + +string CurlWrapper::getValueFromHeaders(const std::string& headers, const std::string& key) { + auto keyLower = stringToLowerCase(key); + istringstream headerStream(headers); + string line; + + while (getline(headerStream, line)) { + if (stringToLowerCase(line).find(keyLower) == string::npos) { + continue; + } + auto separatorIndex = line.find(':'); + if (separatorIndex == string::npos) { + continue; + } + line.erase(line.find_last_not_of(" \n\r\t") + 1); + return ltrim(line.substr(separatorIndex + 1)); + } + + return ""; +} + +CurlWrapper::CurlWrapper( + bool throttled, + std::shared_ptr authDelegate, + string certPath) : + m_isThrottled(throttled), + m_certPath(move(certPath)), + m_code(CURLE_OK), + m_handle(nullptr), + m_headers(unique_ptr(nullptr, curlSlistFreeAllDelegate)), + m_authDelegate(move(authDelegate)) { +} + +CurlWrapper::~CurlWrapper() { + curl_easy_cleanup(m_handle); +} + +bool CurlWrapper::init() { + m_handle = curl_easy_init(); + if (!m_handle) return false; + + if (m_isThrottled) { + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_MAX_SEND_SPEED_LARGE, THROTTLED_SPEED_KB))) return false; + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_MAX_RECV_SPEED_LARGE, THROTTLED_SPEED_KB))) return false; + } + + // allow up to 10 redirects + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_MAXREDIRS, 10L))) return false; + // set a speed timeout to close connections if no data is transferred within 20 seconds + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_LOW_SPEED_LIMIT, 1))) return false; + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_LOW_SPEED_TIME, 20))) return false; + // Setting the connection timeout to 30 seconds + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_CONNECTTIMEOUT, 30))) return false; + // follow "Location:" headers + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1))) return false; + // setup an error buffer + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_errorBuffer))) return false; + // verify host + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYHOST, 2L))) return false; + // verify peer + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYPEER, 1L))) return false; + // Enable communication using TLS1.0 or later. CURL_SSLVERSION_TLSv1_3 is available but seems new; Anvil risk + // opened for confirmation. + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2))) return false; + + if (!m_certPath.empty()) { + ACSDK_DEBUG(LX("init").m("Using custom cert and to not verify host or peers").d("cert Path", m_certPath)); + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_CAINFO, m_certPath.c_str()))) return false; + // our cert is self-signed and is based on no known CA-Author + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYPEER, 0L))) return false; + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYHOST, 0L))) return false; + } + + return true; +} + +unique_ptr CurlWrapper::create( + bool isThrottled, + shared_ptr authDelegate, + const string& certPath) { + if (authDelegate == nullptr && certPath.empty()) { + ACSDK_ERROR(LX("create").m("Failed to initialize").d("error", "authDelegate is null and cert path is empty")); + return nullptr; + } + + auto wrapper = unique_ptr(new CurlWrapper(isThrottled, authDelegate, certPath)); + + if (!wrapper->init()) { + ACSDK_ERROR(LX("create").m("Failed to initialize").d("error", wrapper->m_code)); + return nullptr; + } + + return wrapper; +} + +bool CurlWrapper::getAuthorizationHeader(string& header) { + if (!m_authDelegate) { + ACSDK_ERROR(LX("getAuthorizationHeader").m("Failed to set HTTPHEADER, null authDelegate")); + return false; + } + auto token = m_authDelegate->getAuthToken(); + header = "Authorization: Bearer " + token; + + return true; +} + +extern "C" { +typedef size_t (*CURL_WRITE_CALLBACK)(char* ptr, size_t size, size_t nmemb, void* userdata); +typedef int (*CURL_XFERINFOFUNCTION_CALLBACK)( + void* userdata, + curl_off_t dltotal, + curl_off_t dlnow, + curl_off_t ultotal, + curl_off_t ulnow); +} + +ResultCode CurlWrapper::get( + const string& url, + ostream& response, + const weak_ptr& callbackObj) { + auto resultCode = setHTTPHEADER(); + if (resultCode != ResultCode::SUCCESS) { + ACSDK_ERROR(LX("get").m("Failed to set HTTPHEADER")); + return resultCode; + } + resultCode = stream(url, response, callbackObj); + + return resultCode; +} + +ResultCode CurlWrapper::checkHTTPStatusCode(bool logError) { + long httpStatusCode; + if ((m_code = curl_easy_getinfo(m_handle, CURLINFO_RESPONSE_CODE, &httpStatusCode))) { + return ResultCode::CONNECTION_FAILED; + } + if (httpStatusCode == static_cast(ResultCode::SUCCESS)) { + return ResultCode::SUCCESS; + } + + if (logError) { + ACSDK_ERROR(LX("checkHTTPStatusCode").d("HTTP returned code", httpStatusCode)); + s_metrics().addCounter("httpResponse_" + to_string(httpStatusCode)); + } + + switch (httpStatusCode) { + case static_cast(ResultCode::ILLEGAL_ARGUMENT): + return ResultCode::ILLEGAL_ARGUMENT; + case static_cast(ResultCode::NO_ARTIFACT_FOUND): + return ResultCode::NO_ARTIFACT_FOUND; + case static_cast(ResultCode::UNAUTHORIZED): + return ResultCode::UNAUTHORIZED; + case static_cast(ResultCode::FORBIDDEN): + return ResultCode::FORBIDDEN; + case HTTP_SERVER_ERROR: + return ResultCode::CONNECTION_FAILED; + default: + return ResultCode::CATASTROPHIC_FAILURE; + } +} + +ResultCode CurlWrapper::setHTTPHEADER() { + if (!m_certPath.empty()) { + ACSDK_INFO(LX("setHTTPHEADER").m("Using custom cert. Skipping attaching other auth-headers")); + return ResultCode::SUCCESS; + } + if (!getAuthorizationHeader(m_header)) { + return ResultCode::CONNECTION_FAILED; + } + m_headers = headers_ptr(curl_slist_append(nullptr, m_header.c_str()), curlSlistFreeAllDelegate); + if (m_headers == nullptr) { + s_metrics().addCounter("noAuthHeader"); + ACSDK_ERROR(LX("setHTTPHEADER").m("Can't append authorization header")); + return ResultCode::CONNECTION_FAILED; + } + + m_code = curl_easy_setopt(m_handle, CURLOPT_HTTPHEADER, m_headers.get()); + if (m_code != CURLE_OK) { + s_metrics().addCounter("noAuthHeader"); + ACSDK_ERROR(LX("setHTTPHEADER").m("Failed to setopt the headers").d("code", m_code)); + return ResultCode::CONNECTION_FAILED; + } + return ResultCode::SUCCESS; +} + +CurlWrapper::HeaderResults CurlWrapper::getHeadersAuthorized(const string& url) { + auto results = setHTTPHEADER(); + if (results != ResultCode::SUCCESS) { + ACSDK_ERROR(LX("getHeadersAuthorized").m("Couldn't set up HTTP Headers, won't be able to access infomation")); + return {results}; + } + return getHeaders(url); +} + +CurlWrapper::HeaderResults CurlWrapper::getHeaders(const string& url) { + auto writeFunction = [](char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t { + auto output = static_cast(userdata); + output->write(ptr, size * nmemb); + if (output->good()) { + return nmemb; + } else { + // we could use ftell() to figure out how much is written, but no need since anything < nmemb is a failure + return 0; + } + }; + + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_URL, url.c_str()))) { + return {ResultCode::CONNECTION_FAILED}; + }; + + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_HEADER, 1))) { + return {ResultCode::CONNECTION_FAILED}; + }; + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_NOBODY, 1))) { + curl_easy_setopt(m_handle, CURLOPT_HEADER, 0); + return {ResultCode::CONNECTION_FAILED}; + }; + + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, static_cast(writeFunction)))) { + curl_easy_setopt(m_handle, CURLOPT_HEADER, 0); + curl_easy_setopt(m_handle, CURLOPT_NOBODY, 0); + return {ResultCode::CONNECTION_FAILED}; + }; + + stringstream headerStream; + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, &headerStream))) { + curl_easy_setopt(m_handle, CURLOPT_HEADER, 0); + curl_easy_setopt(m_handle, CURLOPT_NOBODY, 0); + return {ResultCode::CONNECTION_FAILED}; + }; + + m_code = curl_easy_perform(m_handle); + curl_easy_setopt(m_handle, CURLOPT_HEADER, 0); + curl_easy_setopt(m_handle, CURLOPT_NOBODY, 0); + if (m_code != CURLE_OK) { + return {ResultCode::CONNECTION_FAILED}; + } + return {checkHTTPStatusCode(false), headerStream.str()}; +} + +ResultCode CurlWrapper::stream( + const string& fullUrl, + ostream& responseStream, + const weak_ptr&) { + ACSDK_INFO(LX("stream").m("Starting stream request")); + + // Curl callback function to write downloaded data chunk + auto writeFunction = [](char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t { + auto output = static_cast(userdata); + output->write(ptr, size * nmemb); + if (output->good()) { + return nmemb; + } else { + // we could use ftell() to figure out how much is written, but no need since anything < nmemb is a failure + return 0; + } + }; + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, &responseStream))) { + return ResultCode::CONNECTION_FAILED; + } + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, static_cast(writeFunction)))) { + return ResultCode::CONNECTION_FAILED; + } + + ACSDK_DEBUG9(LX("stream").m("Getting truncated url").d("url", fullUrl.substr(0, 100).c_str())); + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_URL, fullUrl.c_str()))) { + return ResultCode::CONNECTION_FAILED; + } + + m_code = curl_easy_perform(m_handle); + if (m_code != CURLE_OK) { + if (m_code == CURLE_ABORTED_BY_CALLBACK) { + // cancelled by user, do not retry + return ResultCode::CATASTROPHIC_FAILURE; + } else { + return ResultCode::CONNECTION_FAILED; + } + } + + return checkHTTPStatusCode(); +} + +ResultCode CurlWrapper::streamToFile( + const std::string& fullUrl, + const std::string& path, + size_t size, + const weak_ptr&) { + auto downloadStream = DownloadStream::create(path, size); + if (downloadStream == nullptr) { + ACSDK_ERROR(LX("streamToFile").m("fileStream is evil").d("path", path.c_str())); + s_metrics().addCounter("evilFileStream"); + return ResultCode::CATASTROPHIC_FAILURE; + } + ACSDK_INFO(LX("streamToFile").m("Downloading to").d("path", path.c_str())); + + // Curl callback function to write downloaded data chunk + auto writeFunction = [](char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t { + auto output = static_cast(userdata); + if (output->write(ptr, size * nmemb)) { + return nmemb; + } else { + return 0; // anything < nmemb is a failure + } + }; + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, downloadStream.get()))) { + return ResultCode::CONNECTION_FAILED; + } + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, static_cast(writeFunction)))) { + return ResultCode::CONNECTION_FAILED; + } + + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_URL, fullUrl.c_str()))) { + return ResultCode::CONNECTION_FAILED; + } + + m_code = curl_easy_perform(m_handle); + if (m_code != CURLE_OK) { + if (m_code == CURLE_ABORTED_BY_CALLBACK) { + // cancelled by user, do not retry + return ResultCode::CATASTROPHIC_FAILURE; + } else { + return ResultCode::CONNECTION_FAILED; + } + } + if (!downloadStream->downloadSucceeded()) { + return ResultCode::CHECKSUM_MISMATCH; + } + if (!changePermissions(path, DEFAULT_FILE_PERMISSIONS)) { + ACSDK_ERROR(LX("streamToFile").m("Failed to set DEFAULT_FILE_PERMISSIONS").d("path", path.c_str())); + return ResultCode::CATASTROPHIC_FAILURE; + } + return checkHTTPStatusCode(); +} + +ResultCode CurlWrapper::streamToQueue( + const string& fullUrl, + shared_ptr downloadChunkQueue, + const weak_ptr&) { + ACSDK_INFO(LX("streamToQueue").m("Starting streamToQueue request")); + auto resultCode = ResultCode::SUCCESS; + + FinallyGuard afterRun([&]() { downloadChunkQueue->pushComplete(resultCode == ResultCode::SUCCESS); }); + + // Curl callback function to write downloaded data chunk + auto curlWriteCallback = [](char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t { + auto queuePtr = static_cast(userdata); + if (queuePtr == nullptr) { + return 0; + } + auto numBytes = size * nmemb; + if (queuePtr->push(ptr, numBytes)) { + auto queueSize = queuePtr->size(); + if (queueSize > DOWNLOAD_QUEUE_SIZE_THRESHOLD * 2) { + ACSDK_ERROR(LX("curlWriteCallback").m("QueueSize too big, abort download.").d("QueueSize", queueSize)); + s_metrics().addCounter("UnpackingStalled"); + return 0; + } else if (queueSize > DOWNLOAD_QUEUE_SIZE_THRESHOLD) { + // usually download speed is slower than unpack speed, when this is no longer the case, + // we slow down the download so as not to increase RAM consumption unnecessarily. + // Each dataChunk is up to 16k. + ACSDK_INFO(LX("curlWriteCallback").m("Slowing down download").d("queue size", queueSize)); + this_thread::sleep_for(milliseconds(10) * queueSize); + } + return nmemb; + } else { + return 0; + } + }; + + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, downloadChunkQueue.get()))) { + resultCode = ResultCode::CONNECTION_FAILED; + return resultCode; + } + + if ((m_code = curl_easy_setopt( + m_handle, CURLOPT_WRITEFUNCTION, static_cast(curlWriteCallback)))) { + resultCode = ResultCode::CONNECTION_FAILED; + return resultCode; + } + + ACSDK_DEBUG9(LX("streamToQueue").m("Getting truncated URL").d("url", fullUrl.substr(0, 100).c_str())); + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_URL, fullUrl.c_str()))) { + return ResultCode::CONNECTION_FAILED; + } + + m_code = curl_easy_perform(m_handle); + + if (m_code != CURLE_OK) { + if (m_code == CURLE_ABORTED_BY_CALLBACK) { + // cancelled by user, do not retry + resultCode = ResultCode::CATASTROPHIC_FAILURE; + return resultCode; + } else { + resultCode = ResultCode::CONNECTION_FAILED; + return resultCode; + } + } + + resultCode = checkHTTPStatusCode(); + return resultCode; +} + +bool CurlWrapper::unpack(std::shared_ptr downloadChunkQueue, const std::string& path) { + ACSDK_INFO(LX("unpack").m("Unpacking from downloadChunkQueue").d("path", path.c_str())); + auto readArchive = unique_ptr(archive_read_new(), archive_read_free); + auto writeArchive = + unique_ptr(archive_write_disk_new(), archive_write_free); + vector writtenFileslist; + + // Other archive formats are supported as well: see '*_all' in libarchive + archive_read_support_format_all(readArchive.get()); + + // Other filters are supported as well: see '*_all' in libarchive + archive_read_support_filter_all(readArchive.get()); + + // Archive callback function to read downloaded data chunk + auto archiveReadCallback = [](struct archive* archive, void* userdata, const void** buffer) -> la_ssize_t { + auto queuePtr = static_cast*>(userdata); + if (queuePtr == nullptr || *queuePtr == nullptr) { + archive_set_error(archive, ARCHIVE_FAILED, "invalid userdata"); + // ssize_t as “signed size_t ”. ssize_t is able to represent the number -1 + return -1; + } + + auto dataChunk = (*queuePtr)->waitAndPop(); + if (dataChunk == nullptr) { + // download terminated, either encountered error or completed + // archiveCloseCallback will be able to distinguish the two cases + *buffer = nullptr; + return 0; + } + *buffer = dataChunk->data(); + // This buffer size is by default CURL_MAX_WRITE_SIZE (16kB). + // The maximum buffer size allowed to be set is CURL_MAX_READ_SIZE (512kB) + // it should be safe to assign size_t to ssize_t + return dataChunk->size(); + }; + + // Archive callback function to signal unpack completion + auto archiveCloseCallback = [](struct archive* archive, void* userdata) -> int { + auto queuePtr = static_cast*>(userdata); + if (queuePtr == nullptr || *queuePtr == nullptr) { + archive_set_error(archive, ARCHIVE_FAILED, "invalid userdata"); + return ARCHIVE_FATAL; + } + + if (!(*queuePtr)->popComplete(true)) { + archive_set_error(archive, ARCHIVE_FAILED, "download/format error"); + return ARCHIVE_FATAL; + } + return ARCHIVE_OK; + }; + + // Open the archive file with the maximum buffer size + auto archiveStatus = archive_read_open( + readArchive.get(), &downloadChunkQueue, nullptr, archiveReadCallback, archiveCloseCallback); + if (archiveStatus != ARCHIVE_OK) { + ACSDK_ERROR(LX("unpack").m("Failed to downnload and unpack").d("error", archiveStatus)); + return false; + } + return (ArchiveWrapper::getInstance())->unpack(readArchive.get(), writeArchive.get(), path); +} + +ResultCode CurlWrapper::getAndDownloadMultipart( + const std::string& url, + shared_ptr sink, + const std::weak_ptr& callbackObj) { + if (setHTTPHEADER() != ResultCode::SUCCESS) { + ACSDK_ERROR(LX("getAndDownloadMultipart").m("Unable to set HTTPHEADER")); + return ResultCode::CONNECTION_FAILED; + } + auto downloadChunkQueue = make_shared(0); + + auto curlHeadersCallback = [](char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t { + auto sink = static_cast(userdata); + if (sink == nullptr) { + return 0; + } + auto length = size * nmemb; + std::string line(ptr, length); + sink->onHeader(line); + return length; + }; + + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_HEADERDATA, sink.get()))) { + return ResultCode::CONNECTION_FAILED; + } + if ((m_code = curl_easy_setopt( + m_handle, CURLOPT_HEADERFUNCTION, static_cast(curlHeadersCallback)))) { + ACSDK_ERROR(LX("getAndDownloadMultipart").m("Bad header callback")); + return ResultCode::CONNECTION_FAILED; + } + + std::thread downloadThread(&CurlWrapper::streamToQueue, this, url, downloadChunkQueue, callbackObj); + ResultCode resultCode; + if (sink->parser(downloadChunkQueue)) { + resultCode = ResultCode::SUCCESS; + } else { + resultCode = ResultCode::CATASTROPHIC_FAILURE; + } + downloadThread.join(); + if (resultCode == ResultCode::SUCCESS) { + auto path = sink->getArtifactPath(); + if (!changePermissions(path, DEFAULT_FILE_PERMISSIONS)) { + ACSDK_ERROR(LX("getAndDownloadMultipart").m("Failed to set DEFAULT_FILE_PERMISSIONS").d("path", path)); + return ResultCode::CATASTROPHIC_FAILURE; + } + } + + return resultCode; +} + +ResultCode CurlWrapper::download( + const std::string& url, + const std::string& path, + const weak_ptr& callbackObj, + bool unpack, + size_t size) { + ACSDK_INFO(LX("download") + .sensitive("URL for download", url.c_str()) + .d("Local path to download", path.c_str()) + .d("unpack", unpack)); + + // common download cancellation function for both stream to file and stream to queue + auto curlProgressCallback = + [](void* userdata, curl_off_t dlTotal, curl_off_t dlNow, curl_off_t ulTotal, curl_off_t ulNow) -> int { + auto callbackWeakPtr = static_cast*>(userdata); + if (callbackWeakPtr == nullptr) { + return 0; + } + auto callbackPtr = callbackWeakPtr->lock(); + if (callbackPtr == nullptr) { + // Returning a non-zero value will cause libcurl to abort the transfer and return + ACSDK_WARN(LX("curlProgressCallback") + .m("CurlWrapper: stream: progressFunction: callbackWeakPtr expired")); // NOLINT + return 1; + } + auto toContinue = callbackPtr->onProgressUpdate(dlTotal, dlNow, ulTotal, ulNow); + return (toContinue) ? 0 : 1; + }; + + if ((m_code = curl_easy_setopt( + m_handle, + CURLOPT_XFERINFOFUNCTION, + static_cast(curlProgressCallback)))) { + return ResultCode::CONNECTION_FAILED; + } + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_XFERINFODATA, &callbackObj))) { + return ResultCode::CONNECTION_FAILED; + } + // If onoff is to 1, it tells the library to shut off the progress meter completely + if ((m_code = curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 0))) { + return ResultCode::CONNECTION_FAILED; + } + + if (!unpack) { + return streamToFile(url, path, size, callbackObj); + } + + // it seems for now that we can only handle one unpack task at a time, so only allow one download+unpack + static mutex s_downloadUnpackMutex; + lock_guard lock(s_downloadUnpackMutex); + + auto downloadChunkQueue = make_shared(size); + + // Download to queue in separate thread (producer) + // Before streamToQueue exit, it must call downloadChunkQueue->pushComplete to indicate success or failure + std::thread downloadThread(&CurlWrapper::streamToQueue, this, url, downloadChunkQueue, callbackObj); + + // Unpack from data in queue + ResultCode resultCode; + if (CurlWrapper::unpack(downloadChunkQueue, path)) { + resultCode = ResultCode::SUCCESS; + } else { + resultCode = ResultCode::CATASTROPHIC_FAILURE; + } + + downloadThread.join(); + ACSDK_INFO(LX("download").d("resultCode", resultCode).d("path", path)); + return resultCode; +} +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/DataChunk.cpp b/capabilities/DavsClient/acsdkAssetsCommon/src/DataChunk.cpp new file mode 100644 index 0000000000..eb56b4d1ab --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/DataChunk.cpp @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/DataChunk.h" + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +DataChunk::DataChunk(char* data, size_t size) { + if (data != nullptr && size > 0) { + m_data = static_cast(operator new(size)); + memcpy(m_data, data, size); + m_size = size; + } else { + m_data = nullptr; + m_size = 0; + } +} + +DataChunk::~DataChunk() { + if (m_data != nullptr) { + operator delete(m_data); + m_data = nullptr; + m_size = 0; + } +} + +size_t DataChunk::size() const { + return m_size; +} + +char* DataChunk::data() const { + return m_data; +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/DownloadChunkQueue.cpp b/capabilities/DavsClient/acsdkAssetsCommon/src/DownloadChunkQueue.cpp new file mode 100644 index 0000000000..fb318be708 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/DownloadChunkQueue.cpp @@ -0,0 +1,276 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/DownloadChunkQueue.h" + +#include + +#include + +#include "acsdkAssetsCommon/AmdMetricWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; +using namespace chrono; + +static const auto s_metrics = AmdMetricsWrapper::creator("DownloadChunkQueue"); +static const auto DOWNLOAD_CHUNK_MAX_WAIT_TIME = seconds(60); +static const auto DOWNLOAD_REPORT_MINIMAL_BYTES = 100000; + +/// String to identify log entries originating from this file. +static const std::string TAG{"DownloadChunkQueue"}; + +/** + * 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) + +DownloadChunkQueue::DownloadChunkQueue(size_t expectedSize) : + m_expectedSize(expectedSize), + m_downloadedSize(0), + m_downloadStatus(StreamingStatus::INPROGRESS), + m_unpackStatus(StreamingStatus::INPROGRESS), + m_maxQueueSizeReached(0), + m_bytesToReport(0), + m_reportIncrement( + m_expectedSize ? max(DOWNLOAD_REPORT_MINIMAL_BYTES, m_expectedSize / 8) + : DOWNLOAD_REPORT_MINIMAL_BYTES) { + ACSDK_INFO(LX("DownloadChunkQueue").m("Created DownloadChunkQueue").d("expectedSize", expectedSize)); +} + +DownloadChunkQueue::~DownloadChunkQueue() { + unique_lock lock(m_mutex); + while (!m_queue.empty()) { + m_queue.pop(); + } +} + +bool DownloadChunkQueue::push(char* data, size_t size) { + auto retValue = false; + + { + unique_lock lock(m_mutex); + + if (m_unpackStatus != StreamingStatus::INPROGRESS) { + ACSDK_ERROR(LX("push").m("push failed, unpack no longer in progress").d("Number of bytes", size)); + return false; + } + switch (m_downloadStatus) { + case StreamingStatus::INPROGRESS: + if (data == nullptr || size == 0) { + ACSDK_ERROR(LX("push").m("Invalid push request").d("Number of bytes", size)); + m_downloadStatus = StreamingStatus::ABORTED; + } else { + m_downloadedSize += size; + if (isSizeCheckingEnabled() && m_downloadedSize > m_expectedSize) { + ACSDK_ERROR(LX("push") + .m("Downloaded size exceeds expected size") + .d("Downloaded size", m_downloadedSize) + .d("Expected Size", m_expectedSize)); + m_downloadStatus = StreamingStatus::ABORTED; + } else { + auto dataChunk = make_shared(data, size); + m_queue.push(dataChunk); + auto currentQueueSize = m_queue.size(); + if (currentQueueSize > m_maxQueueSizeReached) { + m_maxQueueSizeReached = currentQueueSize; + } + if (m_downloadedSize > m_bytesToReport) { + if (m_expectedSize > 0) { + ACSDK_INFO(LX("push") + .m("Pushed bytes to queue") + .d("Downloaded size", m_downloadedSize) + .d("Expected Size", m_expectedSize) + .d("current queue size", currentQueueSize)); + } else { + ACSDK_INFO(LX("push") + .m("Pushed bytes to queue") + .d("Downloaded size", m_downloadedSize) + .d("current queue size", currentQueueSize)); + } + m_bytesToReport = m_downloadedSize + m_reportIncrement; + } + } + } + break; + case StreamingStatus::COMPLETED: + ACSDK_ERROR(LX("push") + .m("Invalid push of bytes after download has been completed") + .d("number of bytes", size)); + m_downloadStatus = StreamingStatus::ABORTED; + break; + case StreamingStatus::ABORTED: + ACSDK_ERROR(LX("push") + .m("Invalid push of bytes after download has been aborted") + .d("number of bytes", size)); + break; + } + retValue = (m_downloadStatus == StreamingStatus::INPROGRESS); + } + m_cond.notify_all(); + return retValue; +} + +bool DownloadChunkQueue::pushComplete(bool succeeded) { + auto retValue = false; + + { + std::unique_lock lock(m_mutex); + if (m_unpackStatus != StreamingStatus::INPROGRESS) { + ACSDK_ERROR(LX("pushComplete").m("pushComplete - unpack no longer in progress")); + return false; + } + switch (m_downloadStatus) { + case StreamingStatus::COMPLETED: + ACSDK_WARN(LX("pushComplete").m("pushComplete has already been invoked")); + // although not supposed to happen (calling pushComplete multiple times, + // it should cause no harm. Continue on inprogress case, no need to break here + case StreamingStatus::INPROGRESS: + if (!succeeded) { + m_downloadStatus = StreamingStatus::ABORTED; + } else { + if (isSizeCheckingEnabled() && m_downloadedSize != m_expectedSize) { + ACSDK_ERROR(LX("pushComplete") + .m("download size mismatch expected size") + .d("downoload size", m_downloadedSize) + .d("exepected size", m_expectedSize)); + m_downloadStatus = StreamingStatus::ABORTED; + } else { + ACSDK_INFO(LX("pushComplete") + .d("Pushed bytes", m_downloadedSize) + .d("m_maxQueueSizeReached ", m_maxQueueSizeReached)); + m_downloadStatus = StreamingStatus::COMPLETED; + } + } + break; + case StreamingStatus::ABORTED: + // once error is detected, it will remain errored out + break; + } // switch(m_downloadStatus) + retValue = (m_downloadStatus == StreamingStatus::COMPLETED); + } // lock + m_cond.notify_all(); + return retValue; +} + +shared_ptr DownloadChunkQueue::waitAndPop() { + shared_ptr dataChunk; + unique_lock lock(m_mutex); + switch (m_unpackStatus) { + case StreamingStatus::COMPLETED: + ACSDK_ERROR(LX("waitAndPop").m("waitAndPop invoked after unpack Completed")); + break; + case StreamingStatus::ABORTED: + ACSDK_ERROR(LX("waitAndPop").m("waitAndPop invoked after unpack Aborted")); + break; + case StreamingStatus::INPROGRESS: + while (m_downloadStatus == StreamingStatus::INPROGRESS && m_queue.empty()) { + m_cond.wait_for(lock, DOWNLOAD_CHUNK_MAX_WAIT_TIME); + } + switch (m_downloadStatus) { + case StreamingStatus::ABORTED: + ACSDK_ERROR(LX("waitAndPop").m("waitAndPop failed, download Aborted")); + break; + case StreamingStatus::COMPLETED: + if (!m_queue.empty()) { + dataChunk = m_queue.front(); + m_queue.pop(); + } else { + ACSDK_INFO(LX("waitAndPop").m("waitAndPop done, no more chunks")); + } + break; + case StreamingStatus::INPROGRESS: + if (!m_queue.empty()) { + dataChunk = m_queue.front(); + m_queue.pop(); + } else { + ACSDK_ERROR(LX("waitAndPop").m("waitAndPop timedout")); + } + break; + } // switch (m_downloadStatus) + break; + } // switch (m_unpackStatus) + // We need to keep a reference to dataChunk object so when dataChunk->data is passed to unpack callback + // the data is still valid. When pop is called again, the previous dataChunk has already finished processing + // by unpack function + m_activeChunk = dataChunk; + return dataChunk; +} + +bool DownloadChunkQueue::popComplete(bool succeeded) { + unique_lock lock(m_mutex); + switch (m_unpackStatus) { + case StreamingStatus::ABORTED: + ACSDK_ERROR(LX("popComplete").m("popComplete invoked after unpack Aborted")); + break; + case StreamingStatus::COMPLETED: + if (!succeeded) { + ACSDK_ERROR(LX("popComplete").m("popComplete initiated abort after unpack Completed")); + m_unpackStatus = StreamingStatus::ABORTED; + } else { + ACSDK_WARN(LX("popComplete").m("popComplete initiated abort after unpack aborted")); + } + break; + case StreamingStatus::INPROGRESS: + if (!succeeded) { + ACSDK_ERROR(LX("popComplete").m("popComplete initiated abort while unpack in progress")); + m_unpackStatus = StreamingStatus::ABORTED; + } else { + while (m_downloadStatus == StreamingStatus::INPROGRESS && m_queue.empty()) { + ACSDK_INFO(LX("popComplete").m("popComplete wait for download pushComplete event")); + m_cond.wait_for(lock, DOWNLOAD_CHUNK_MAX_WAIT_TIME); + } + switch (m_downloadStatus) { + case StreamingStatus::ABORTED: + ACSDK_ERROR(LX("popComplete").m("popComplete initiated while download aborted")); + m_unpackStatus = StreamingStatus::ABORTED; + break; + case StreamingStatus::COMPLETED: + if (!m_queue.empty()) { + ACSDK_ERROR(LX("popComplete") + .m("popComplete initiated while download chucks left") + .d("download chunks left", m_queue.size())); + m_unpackStatus = StreamingStatus::ABORTED; + } else { + ACSDK_INFO(LX("popComplete").m("popComplete initiated while download completed")); + m_unpackStatus = StreamingStatus::COMPLETED; + } + break; + case StreamingStatus::INPROGRESS: + ACSDK_ERROR(LX("popComplete") + .m("popComplete initiated while download in progress") + .d("chucks in queue", m_queue.size())); + m_unpackStatus = StreamingStatus::ABORTED; + break; + } // switch (m_downloadStatus) + } + break; // case StreamingStatus::INPROGRESS: + } // switch (m_unpackStatus) + return (m_unpackStatus == StreamingStatus::COMPLETED); +} + +size_t DownloadChunkQueue::size() { + unique_lock lock(m_mutex); + return m_queue.size(); +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/DownloadStream.cpp b/capabilities/DavsClient/acsdkAssetsCommon/src/DownloadStream.cpp new file mode 100644 index 0000000000..f9770fe77a --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/DownloadStream.cpp @@ -0,0 +1,89 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/DownloadStream.h" + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; + +/// String to identify log entries originating from this file. +static const std::string TAG{"DownloadStream"}; + +/** + * 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) + +shared_ptr DownloadStream::create(const string& path, size_t expectedSize) { + shared_ptr downloadStream(new DownloadStream(path, expectedSize)); + return (downloadStream->good()) ? downloadStream : nullptr; +} + +DownloadStream::DownloadStream(const std::string& path, size_t expectedSize) : + m_ostream(path), + m_expectedSize(expectedSize), + m_downloadedSize(0) { +} + +bool DownloadStream::good() const { + unique_lock lock(m_mutex); + return m_ostream.good(); +} + +DownloadStream::~DownloadStream() { + unique_lock lock(m_mutex); + m_ostream.close(); +} + +bool DownloadStream::write(const char* data, size_t size) { + if (data == nullptr) { + ACSDK_ERROR(LX("write").m("Cannot write bytes from nullptr").d("number of bytes", size)); + return false; + } + unique_lock lock(m_mutex); + if (m_expectedSize != 0 && (m_downloadedSize > m_expectedSize || size > m_expectedSize - m_downloadedSize)) { + ACSDK_ERROR(LX("write").m("Downloaded size exceeds expected size").d("expected size", m_expectedSize)); + return false; + } + m_ostream.write(data, size); + auto ret = m_ostream.good(); + if (ret) { + m_downloadedSize += size; + } + return ret; +} + +bool DownloadStream::downloadSucceeded() const { + unique_lock lock(m_mutex); + if (m_expectedSize != 0 && m_downloadedSize != m_expectedSize) { + ACSDK_ERROR(LX("downloadSucceeded") + .m("Downloaded size mismatch expected size") + .d("downloaded size", m_downloadedSize) + .d("expected size", m_expectedSize)); + return false; + } + return true; +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/JitterUtil.cpp b/capabilities/DavsClient/acsdkAssetsCommon/src/JitterUtil.cpp new file mode 100644 index 0000000000..fb632c4335 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/JitterUtil.cpp @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/JitterUtil.h" + +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { +namespace jitterUtil { + +using namespace std; +using namespace chrono; + +/// String to identify log entries originating from this file. +static const std::string TAG{"JitterUtil"}; + +/** + * 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) + +milliseconds jitter(milliseconds baseValue, float jitterFactor) { + std::random_device rd; + std::mt19937 mt{rd()}; + + if (jitterFactor <= 0 || jitterFactor >= 1) { + ACSDK_ERROR(LX("jitter").m("Returning without jitter").d("bad jitter", jitterFactor)); + return baseValue; + } + + // jitter between +/- JITTER_FACTOR of current time + auto jitterSize = static_cast(jitterFactor * baseValue.count()); + auto jitterRand = std::uniform_int_distribution(-jitterSize, jitterSize); + return baseValue + milliseconds(jitterRand(mt)); +} + +milliseconds expJitter(milliseconds baseValue, float jitterFactor) { + return baseValue + jitter(baseValue, jitterFactor); +} + +} // namespace jitterUtil +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/src/ResponseSink.cpp b/capabilities/DavsClient/acsdkAssetsCommon/src/ResponseSink.cpp new file mode 100644 index 0000000000..a29e832930 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/src/ResponseSink.cpp @@ -0,0 +1,169 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/ResponseSink.h" + +#include +#include +#include + +#include "acsdkAssetsCommon/CurlWrapper.h" +#include "acsdkAssetsCommon/DownloadStream.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace alexaClientSDK::avsCommon::utils; +using namespace commonInterfaces; + +/// 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"; +/// 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(); + +/// String to identify log entries originating from this file. +static const std::string TAG{"ResponseSink"}; + +/** + * 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) + +ResponseSink::ResponseSink(const std::shared_ptr& request, const std::string& workingDirectory) : + m_parentDirectory(workingDirectory) { + m_request = request; + m_contentType = ContentType::NONE; + m_boundary = ""; +} +void ResponseSink::setContentType(const std::string& type) { + if (type == MIME_JSON_CONTENT_TYPE) { + m_contentType = ContentType::JSON; + } else if (type == MIME_OCTET_STREAM_CONTENT_TYPE && nullptr == m_attachment) { + m_contentType = ContentType::ATTACHMENT; + filesystem::makeDirectory(m_parentDirectory); + // file name is just id since there is no s3 URL. + if (nullptr == m_artifact) { + ACSDK_ERROR(LX("setContentType").m("Artifact was null can't create Attachment")); + return; + } + m_artifactPath = m_parentDirectory + "/" + m_artifact->getId(); + if (!filesystem::pathContainsPrefix(m_artifactPath, m_parentDirectory)) { + ACSDK_ERROR(LX("setContentType").m("Invalid URL file path").d("path", m_artifactPath)); + } + m_attachment = DownloadStream::create(m_artifactPath, m_artifact->getArtifactSizeBytes()); + } else { + ACSDK_ERROR(LX("setContentType").m("Unexpected Content Type for Multipart")); + m_contentType = ContentType::NONE; + } +} + +void ResponseSink::setData(const char* buffer, size_t size) { + if (nullptr == buffer) { + ACSDK_ERROR(LX("setData").m("Data is null, can't set data")); + return; + } + if (m_contentType == ContentType::JSON) { + m_jsonString.append(buffer, size); + } else if (m_contentType == ContentType::ATTACHMENT) { + m_attachment->write(buffer, size); + } +} + +void ResponseSink::endData() { + if (m_contentType == ContentType::JSON) { + m_artifact = VendableArtifact::create(m_request, m_jsonString, true); + } else if (m_contentType == ContentType::ATTACHMENT) { + ACSDK_INFO(LX("endData").m("Close the attachment")); + } +} +std::shared_ptr ResponseSink::getArtifact() { + if (nullptr == m_artifact) { + ACSDK_ERROR(LX("getArtifact").m("Response Sink Artifact is null")); + return nullptr; + } + return m_artifact; +} + +std::string ResponseSink::getArtifactPath() { + return m_artifactPath; +} + +bool ResponseSink::onHeader(const std::string& header) { + // If the boundary is found skip the rest of the checks + // which are time consuming + if (!m_boundary.empty()) { + return true; + } + auto line = CurlWrapper::getValueFromHeaders(header, "Content-Type"); + if (line.find(BOUNDARY_PREFIX) != std::string::npos) { + auto startOfBoundary = line.substr(line.find(BOUNDARY_PREFIX)); + m_boundary = startOfBoundary.substr(BOUNDARY_PREFIX_SIZE, startOfBoundary.find("\r\n") - BOUNDARY_PREFIX_SIZE); + return true; + } + return false; +} +bool ResponseSink::parser(std::shared_ptr& downloadChunkQueue) { + if (nullptr == downloadChunkQueue) { + ACSDK_ERROR(LX("parser").m("Download Chunk Queue is null")); + return false; + } + auto dataChunk = downloadChunkQueue->waitAndPop(); + if (nullptr == dataChunk) { + ACSDK_ERROR(LX("parser").m("Data Chunk didn't populate ")); + return false; + } + + auto parser = MultipartReader(); + parser.onPartData = [](const char* buffer, size_t size, void* userData) { + static_cast(userData)->setData(buffer, size); + }; + parser.onPartBegin = [](const MultipartHeaders& headers, void* userData) { + auto contentType = headers["Content-Type"]; + ACSDK_DEBUG(LX("parser").d("Starting part", contentType.c_str())); + static_cast(userData)->setContentType(contentType); + }; + parser.onPartEnd = [](void* userData) { + ACSDK_DEBUG(LX("parser").m("Ending Current Data Part")); + static_cast(userData)->endData(); + }; + parser.userData = this; + parser.setBoundary(m_boundary); + while (dataChunk) { + parser.feed(dataChunk->data(), dataChunk->size()); + if (parser.hasError()) { + ACSDK_ERROR(LX("parser").m("Multipart Parser Error").d("error message", parser.getErrorMessage())); + return false; + } + dataChunk = downloadChunkQueue->waitAndPop(); + } + if (!downloadChunkQueue->popComplete(true)) { + ACSDK_ERROR(LX("parser").m("Pop didn't complete properly")); + return false; + } + + return true; +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/AmdMetricWrapperTest.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/AmdMetricWrapperTest.cpp new file mode 100644 index 0000000000..6f949779ec --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/AmdMetricWrapperTest.cpp @@ -0,0 +1,80 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "TestUtil.h" +#include "acsdkAssetsCommon/AmdMetricWrapper.h" + +namespace test { + +using namespace std; +using namespace chrono; +using namespace ::testing; +using namespace alexaClientSDK::acsdkAssets::common; +using namespace alexaClientSDK::avsCommon::utils::metrics; + +class MetricRecorderTest : public MetricRecorderInterface { +public: + ~MetricRecorderTest() override = default; + void recordMetric(std::shared_ptr metricEvent) override { + m_metricEvents.push_back(metricEvent); + } + + vector> m_metricEvents; +}; + +class AmdMetricWrapperTest : public Test { +public: + void SetUp() override { + metricRecorder = std::make_shared(); + AmdMetricsWrapper::setStaticRecorder(metricRecorder); + } + + void TearDown() override { + AmdMetricsWrapper::setStaticRecorder(nullptr); + } + + shared_ptr metricRecorder; + string source = "source"; + string count1 = "count1"; + string count2 = "count2"; + string timerS = "timerS"; + string timerMS = "timerMS"; + string testString = "string1"; +}; + +TEST_F(AmdMetricWrapperTest, SubmittingMetricWithAllPossibleFormats) { // NOLINT + AmdMetricsWrapper(source) + .addCounter(count1) + .addTimer(timerS, seconds(3)) + .addString(testString, "Test 1") + .addCounter(count2, 2) + .addTimer(timerMS, seconds(5)); + + ASSERT_TRUE(waitUntil([this] { return metricRecorder->m_metricEvents.size() == 1; })); + auto metric = metricRecorder->m_metricEvents[0]; + ASSERT_EQ(metric->getActivityName(), source); + ASSERT_EQ(metric->getDataPoint(count1, DataType::COUNTER).value().getValue(), "1"); + ASSERT_EQ(metric->getDataPoint(timerS, DataType::DURATION).value().getValue(), "3000"); + ASSERT_EQ(metric->getDataPoint(testString, DataType::STRING).value().getValue(), "Test 1"); + ASSERT_EQ(metric->getDataPoint(count2, DataType::COUNTER).value().getValue(), "2"); + ASSERT_EQ(metric->getDataPoint(timerMS, DataType::DURATION).value().getValue(), "5000"); +} + +} // namespace test diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/CMakeLists.txt b/capabilities/DavsClient/acsdkAssetsCommon/test/CMakeLists.txt new file mode 100644 index 0000000000..e49723762c --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/CMakeLists.txt @@ -0,0 +1,10 @@ +project(acsdkAssetsCommonTest LANGUAGES CXX) +add_subdirectory(mocks) + +set(LIBS + "AVSCommon" + "acsdkAssetsCommon" + "acsdkAssetsMocks" + ) + +discover_unit_tests("" "${LIBS}") \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/CurlWrapperTest.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/CurlWrapperTest.cpp new file mode 100644 index 0000000000..1d8a2e49f0 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/CurlWrapperTest.cpp @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/CurlWrapper.h" + +using namespace std; +using namespace std::chrono; +using namespace ::testing; +using namespace alexaClientSDK::acsdkAssets::common; + +class CurlWrapperTest : public Test {}; + +TEST_F(CurlWrapperTest, parsingValidHeaderTest) { + auto header = + "HTTP/2 200\r\n" + "Content-Type:application/json\r\n" + "Server: Server\r\n" + "Date : Wed, 18 Aug 2021 22:55:02 GMT \r\n" + "\n"; + + ASSERT_EQ(CurlWrapper::getValueFromHeaders(header, "Content-Type"), "application/json"); + ASSERT_EQ(CurlWrapper::getValueFromHeaders(header, "content-type"), "application/json"); + ASSERT_EQ(CurlWrapper::getValueFromHeaders(header, "server"), "Server"); + ASSERT_EQ(CurlWrapper::getValueFromHeaders(header, "DATE"), "Wed, 18 Aug 2021 22:55:02 GMT"); + ASSERT_EQ(CurlWrapper::getValueFromHeaders(header, "ASDF"), ""); +} + +TEST_F(CurlWrapperTest, parsingInvalidHeaderTest) { + ASSERT_EQ(CurlWrapper::getValueFromHeaders("Content-Type : ", "Content-Type"), ""); + ASSERT_EQ(CurlWrapper::getValueFromHeaders("Content-Type :", "Content-Type"), ""); + ASSERT_EQ(CurlWrapper::getValueFromHeaders("Content-Type", "Content-Type"), ""); + ASSERT_EQ(CurlWrapper::getValueFromHeaders("", "Content-Type"), ""); +} \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/DownloadChunkQueueTest.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/DownloadChunkQueueTest.cpp new file mode 100644 index 0000000000..d4fcdc4c42 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/DownloadChunkQueueTest.cpp @@ -0,0 +1,146 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/DownloadChunkQueue.h" + +using namespace std; +using namespace alexaClientSDK::acsdkAssets::common; + +class DownloadChunkQueueTest : public ::testing::Test { +public: + void SetUp() override { + } + + void TearDown() override { + } +}; + +TEST_F(DownloadChunkQueueTest, queueSizeZero) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(0)); + ASSERT_TRUE(queue != nullptr); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_EQ(1u, queue->size()); +} + +TEST_F(DownloadChunkQueueTest, pushEmptyBuffer) { + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_FALSE(queue->push(nullptr, 1)); + ASSERT_EQ(0u, queue->size()); +} + +TEST_F(DownloadChunkQueueTest, pushZeroByte) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_FALSE(queue->push(data, 0)); + ASSERT_EQ(0u, queue->size()); +} + +TEST_F(DownloadChunkQueueTest, pushSizeOver) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_TRUE(queue->push(data, 2)); + ASSERT_FALSE(queue->push(data, 100)); + ASSERT_EQ(2u, queue->size()); +} + +TEST_F(DownloadChunkQueueTest, pushCompleteMisMatch) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_FALSE(queue->pushComplete(true)); + ASSERT_EQ(1u, queue->size()); +} + +TEST_F(DownloadChunkQueueTest, pushCompleteMatch) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_TRUE(queue->push(data, 7)); + ASSERT_TRUE(queue->pushComplete(true)); + ASSERT_EQ(2u, queue->size()); +} + +TEST_F(DownloadChunkQueueTest, pushAbort) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_FALSE(queue->pushComplete(false)); + ASSERT_EQ(1u, queue->size()); +} + +TEST_F(DownloadChunkQueueTest, pushCompleteAbort) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_TRUE(queue->push(data, 7)); + ASSERT_FALSE(queue->pushComplete(false)); + ASSERT_EQ(2u, queue->size()); +} + +TEST_F(DownloadChunkQueueTest, pop) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_EQ(1u, queue->size()); + ASSERT_TRUE(nullptr != queue->waitAndPop()); + ASSERT_EQ(0u, queue->size()); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_TRUE(nullptr != queue->waitAndPop()); + + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_TRUE(queue->push(data, 5)); + ASSERT_EQ(2u, queue->size()); + ASSERT_TRUE(nullptr != queue->waitAndPop()); + ASSERT_TRUE(nullptr != queue->waitAndPop()); + ASSERT_EQ(0u, queue->size()); + + ASSERT_TRUE(queue->pushComplete(true)); + ASSERT_TRUE(queue->popComplete(true)); +} + +TEST_F(DownloadChunkQueueTest, popAfterPushAbort) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_FALSE(queue->pushComplete(false)); + ASSERT_EQ(nullptr, queue->waitAndPop()); + ASSERT_FALSE(queue->popComplete(true)); + ASSERT_EQ(1u, queue->size()); +} + +TEST_F(DownloadChunkQueueTest, pushAfterPopAbort) { + char data[16] = {0}; + shared_ptr queue(new DownloadChunkQueue(8)); + ASSERT_TRUE(queue != nullptr); + ASSERT_TRUE(queue->push(data, 1)); + ASSERT_FALSE(queue->popComplete(false)); + ASSERT_FALSE(queue->push(data, 1)); + ASSERT_EQ(1u, queue->size()); +} \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/DownloadStreamTest.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/DownloadStreamTest.cpp new file mode 100644 index 0000000000..1ec05d45eb --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/DownloadStreamTest.cpp @@ -0,0 +1,84 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "TestUtil.h" +#include "acsdkAssetsCommon/DownloadStream.h" + +using namespace std; +using namespace alexaClientSDK::acsdkAssets::common; +using namespace alexaClientSDK::avsCommon::utils; + +class DownloadStreamTest : public ::testing::Test { +public: + void SetUp() override { + DOWNLOAD_TEST_DIR = createTmpDir("temp"); + } + + void TearDown() override { + filesystem::removeAll(DOWNLOAD_TEST_DIR); + } + + std::string DOWNLOAD_TEST_DIR; +}; + +TEST_F(DownloadStreamTest, createReadOnlyFile) { + auto readOnlyFile = "/etc/hosts"; + auto downloadStream = DownloadStream::create(readOnlyFile, 10); + ASSERT_EQ(nullptr, downloadStream); +} + +TEST_F(DownloadStreamTest, create) { + auto tempFile = DOWNLOAD_TEST_DIR + "/temp"; + auto downloadStream = DownloadStream::create(tempFile, 10); + ASSERT_TRUE(nullptr != downloadStream); + + auto tempData = "12345"; + ASSERT_TRUE(downloadStream->write(tempData, 5)); + ASSERT_TRUE(downloadStream->write(tempData, 1)); + ASSERT_FALSE(downloadStream->write(tempData, 5)); +} + +TEST_F(DownloadStreamTest, writeNullptr) { + auto tempFile = DOWNLOAD_TEST_DIR + "/temp"; + auto downloadStream = DownloadStream::create(tempFile, 10); + ASSERT_TRUE(nullptr != downloadStream); + ASSERT_FALSE(downloadStream->write(nullptr, 1)); +} + +TEST_F(DownloadStreamTest, writeZeroByte) { + auto tempFile = DOWNLOAD_TEST_DIR + "/temp"; + auto downloadStream = DownloadStream::create(tempFile, 0); + ASSERT_TRUE(nullptr != downloadStream); + ASSERT_TRUE(downloadStream->downloadSucceeded()); + + auto tempData = "12345"; + ASSERT_TRUE(downloadStream->write(tempData, 0)); + ASSERT_TRUE(downloadStream->downloadSucceeded()); +} + +TEST_F(DownloadStreamTest, downloadSucceeded) { + auto tempFile = DOWNLOAD_TEST_DIR + "/temp"; + auto downloadStream = DownloadStream::create(tempFile, 10); + ASSERT_TRUE(nullptr != downloadStream); + + auto tempData = "12345"; + ASSERT_TRUE(downloadStream->write(tempData, 5)); + ASSERT_FALSE(downloadStream->downloadSucceeded()); + ASSERT_TRUE(downloadStream->write(tempData, 5)); + ASSERT_TRUE(downloadStream->downloadSucceeded()); +} \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/JitterUtilTest.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/JitterUtilTest.cpp new file mode 100644 index 0000000000..69ab9dda6d --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/JitterUtilTest.cpp @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsCommon/JitterUtil.h" + +using namespace std; +using namespace std::chrono; +using namespace ::testing; +using namespace alexaClientSDK::acsdkAssets::common::jitterUtil; + +static const int NUMBER_OF_TRIES = 1000000; +static constexpr milliseconds DELAY_MS(1000); +static const float FACTOR = 0.2; + +class JitterUtilTest : public Test {}; + +TEST_F(JitterUtilTest, jitterNeverFallsTest) { + const milliseconds MIN_RANGE(800); + const milliseconds MAX_RANGE(1200); + milliseconds minValue = MAX_RANGE; + milliseconds maxValue = MIN_RANGE; + auto value = DELAY_MS; + for (int i = 0; i < NUMBER_OF_TRIES; i++) { + auto newValue = jitter(value, FACTOR); + ASSERT_GE(newValue, MIN_RANGE); + ASSERT_LE(newValue, MAX_RANGE); + minValue = (minValue < newValue) ? minValue : newValue; + maxValue = (maxValue > newValue) ? maxValue : newValue; + } + + // Test if it's truly random + ASSERT_GT(maxValue.count(), 1190); + ASSERT_LT(minValue.count(), 810); +} + +TEST_F(JitterUtilTest, jitterNeverZeroTest) { + auto value = milliseconds(DELAY_MS); + for (int i = 0; i < NUMBER_OF_TRIES; i++) { + value = jitter(value, FACTOR); + ASSERT_NE(value.count(), 0); + } +} + +TEST_F(JitterUtilTest, expJitterAlwaysGreaterTest) { + auto value = milliseconds(DELAY_MS); + for (int i = 0; i < NUMBER_OF_TRIES; i++) { + ASSERT_GT(expJitter(value, FACTOR).count(), value.count()); + } +} diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/AuthDelegateMock.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/AuthDelegateMock.cpp new file mode 100644 index 0000000000..8984d14c4f --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/AuthDelegateMock.cpp @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "AuthDelegateMock.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; +using namespace alexaClientSDK::avsCommon::sdkInterfaces; + +static const string TAG{"MAPLiteAuthDelegateMock"}; +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +shared_ptr AuthDelegateMock::create() { + auto mapLiteAuthDelegate = shared_ptr(new AuthDelegateMock()); + return mapLiteAuthDelegate; +} + +string AuthDelegateMock::getAuthToken() { + string token{ + "Atna|EwICIO_" + "XWrM1cci3ZH9SSe0dudUhslX6PLgTfHHoTM1gHfERnr47HIVXjYMNzbxqb3lzP9tQ2SeTr77BGEAlYm0CTjtY3FN0s7TLXe93SruK68eel" + "F" + "WwB7Q0zw_gUE4fXcCZv_f8mYsTJnox_UoFNYFKqdBzE12g8TrNn4rkyPhKQRbrQDSdTQe_" + "znY9tCt8EnDwxgJR09tPttEPfcsxFoSk8Qi36ptGGoLljVcCBM6Ef37XB9OseRkTQqfmtbTkBW8ikC--EfgLhVLnfceHs653mJ-" + "oMpTNK2YBOTx-klj5iuWpHa3dq3xgjBpRI-1ocgqcYOk"}; + return token; +} + +void AuthDelegateMock::onAuthFailure(const std::string& token) { + ACSDK_ERROR(LX("onAuthFailure").sensitive("token", token)); +} +void AuthDelegateMock::addAuthObserver( + std::shared_ptr observer) { + ACSDK_INFO(LX(("addAuthObserver"))); +} + +void AuthDelegateMock::removeAuthObserver( + std::shared_ptr observer) { + ACSDK_INFO(LX(("removeAuthObserver"))); +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/CMakeLists.txt b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/CMakeLists.txt new file mode 100644 index 0000000000..2759209c5c --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/CMakeLists.txt @@ -0,0 +1,42 @@ +include(CheckCXXCompilerFlag) +project(acsdkAssetsMocks LANGUAGES CXX) + +add_library(acsdkAssetsMocks + AuthDelegateMock.cpp + CurlWrapperMock.cpp + DavsServiceMock.cpp + InternetConnectionMonitorMock.cpp + TestUtil.cpp + ) + +target_compile_definitions(acsdkAssetsMocks + PUBLIC + RUNS_ON_HOST=1 + PRIVATE + ACSDK_LOG_MODULE=AssetsMock) + +target_include_directories(acsdkAssetsMocks PUBLIC include) + +target_link_libraries(acsdkAssetsMocks + AVSCommon + acsdkAssetsInterfaces + acsdkAssetsCommon + ) + +CHECK_CXX_COMPILER_FLAG("-Wno-deprecated-declarations" HAS_NO_DEPRECATED_DECLARATIONS) +if (HAS_NO_DEPRECATED_DECLARATIONS) + target_compile_options(acsdkAssetsMocks PUBLIC + -Wno-deprecated-declarations + ) +endif() +CHECK_CXX_COMPILER_FLAG("-Wno-attributes" HAS_NO_ATTRIBUTES) +if (HAS_NO_ATTRIBUTES) + target_compile_options(acsdkAssetsMocks PUBLIC + -Wno-attributes + ) +endif() + +# install target +install(TARGETS acsdkAssetsMocks DESTINATION lib) +install(DIRECTORY include/ + DESTINATION include/acsdkAssetsMocks) diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/CurlWrapperMock.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/CurlWrapperMock.cpp new file mode 100644 index 0000000000..4f096eac55 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/CurlWrapperMock.cpp @@ -0,0 +1,246 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "CurlWrapperMock.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "DavsServiceMock.h" +#include "acsdkAssetsCommon/Base64Url.h" +#include "acsdkAssetsCommon/CurlWrapper.h" + +using namespace std; +using namespace rapidjson; + +using namespace alexaClientSDK::acsdkAssets::common; +using namespace alexaClientSDK::acsdkAssets::commonInterfaces; + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +bool CurlWrapperMock::getResult = false; +string CurlWrapperMock::root = ""; +string CurlWrapperMock::capturedRequest = ""; +string CurlWrapperMock::mockResponse = ""; +bool CurlWrapperMock::useDavsService = false; +bool CurlWrapperMock::downloadShallFail = false; +string CurlWrapperMock::header = "Content-Type: application/json\n Content-Length: 160000"; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +// Mock CURL by using stub implementations; this enables much better coverage than mocking the entire CurlWrapper. + +extern "C" { + +typedef size_t (*WRITE_CALLBACK)(char* ptr, size_t size, size_t nmemb, void* userdata); + +struct MyCurlContext { + WRITE_CALLBACK callback; + void* callbackData; + string preparedResponse; + bool returnNotFound; + bool headerRequest = false; + bool headAndData = false; + WRITE_CALLBACK headerCallback; + void* headerCallbackData; +}; + +CURL* curl_easy_init(void) { + MyCurlContext* c = new MyCurlContext(); + c->returnNotFound = false; + return (CURL*)c; +} + +static void prepareResponseBasedOnFile(MyCurlContext* c, const string& fileName) { + fstream response(fileName, ios::in); + stringstream ss; + if (response.good()) { + ss << response.rdbuf(); + c->preparedResponse = ss.str(); + } + c->returnNotFound = c->preparedResponse.empty(); +} + +CURLcode curl_easy_setopt(CURL* curl, CURLoption option, ...) { + auto c = static_cast(curl); + va_list args; +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wvarargs" +#endif + va_start(args, option); +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + if (option == CURLOPT_WRITEFUNCTION) { + c->callback = va_arg(args, WRITE_CALLBACK); + } else if (option == CURLOPT_WRITEDATA) { + c->callbackData = va_arg(args, void*); + } else if (option == CURLOPT_NOBODY) { + c->headerRequest = va_arg(args, int) == 1; + } else if (option == CURLOPT_HEADERDATA) { + c->headerCallbackData = va_arg(args, void*); + } else if (option == CURLOPT_HEADERFUNCTION) { + c->headerCallback = va_arg(args, WRITE_CALLBACK); + c->headAndData = true; + } else if (option == CURLOPT_URL) { + string url = va_arg(args, const char*); + string artifactStart = "artifacts/"; + auto typeStart = url.find(artifactStart) + artifactStart.size(); + auto keyStart = url.find('/', typeStart) + 1; + auto queryStart = url.find('?', keyStart) + 1; + auto type = url.substr(typeStart, keyStart - typeStart - 1); + auto key = url.substr(keyStart, queryStart - keyStart - 1); + + const char* filterPart = "encodedFilters="; + const char* substr = strstr(url.c_str(), filterPart); + if (strstr(url.c_str(), "test://") != nullptr) { + prepareResponseBasedOnFile(c, &url[7]); + if (c->returnNotFound) { + va_end(args); + return CURLE_HTTP_RETURNED_ERROR; + } + } else if (substr != nullptr) { + string capturedFilter; + Base64Url::decode(substr + strlen(filterPart), capturedFilter); + CurlWrapperMock::capturedRequest = R"({"artifactType":")" + type + R"(","artifactKey":")" + key + R"(",)" + + R"("filters":)" + capturedFilter + R"(})"; + + if (CurlWrapperMock::useDavsService) { + Document document(kObjectType); + document.Parse(capturedFilter.c_str()); + DavsServiceMock::FilterMap filterMap; + for (auto elem = document.MemberBegin(); elem != document.MemberEnd(); elem++) { + for (auto& subFilter : elem->value.GetArray()) { + filterMap[elem->name.GetString()].insert(subFilter.GetString()); + } + } + auto id = string() + type + "_" + key + "_" + DavsServiceMock::getId(filterMap); + + prepareResponseBasedOnFile(c, CurlWrapperMock::root + "/" + id + ".response"); + } else { + c->preparedResponse = CurlWrapperMock::mockResponse; + c->returnNotFound = false; + } + } else { + CurlWrapperMock::capturedRequest.clear(); + const char* idPart = strrchr(url.c_str(), '/'); + const char* tgzPart = strstr(url.c_str(), ".tar.gz"); + if (idPart == nullptr || tgzPart == nullptr) { + c->returnNotFound = true; + } else { + string id(idPart + 1, tgzPart); + prepareResponseBasedOnFile(c, CurlWrapperMock::root + "/" + id + ".artifact"); + } + } + } + + va_end(args); + + return CURLE_OK; +} + +CURLcode curl_easy_perform(CURL* curl) { + auto c = static_cast(curl); + + if (c->returnNotFound) { + return CURLE_HTTP_NOT_FOUND; + } + size_t written; + size_t expectedSize; + if (c->headerRequest) { + written = + c->callback((char*)CurlWrapperMock::header.c_str(), 1, CurlWrapperMock::header.size(), c->callbackData); + expectedSize = CurlWrapperMock::header.size(); + } else { + written = c->callback((char*)c->preparedResponse.c_str(), 1, c->preparedResponse.size(), c->callbackData); + expectedSize = c->preparedResponse.size(); + if (c->headAndData) { + c->headerCallback( + (char*)CurlWrapperMock::header.c_str(), 1, CurlWrapperMock::header.size(), c->headerCallbackData); + } + } + if (written == expectedSize) { + if (CurlWrapperMock::useDavsService || CurlWrapperMock::getResult) { + return CURLE_OK; + } else { + return CURLE_HTTP_RETURNED_ERROR; + } + } else { + return CURLE_WRITE_ERROR; + } +} + +void curl_easy_cleanup(CURL* curl) { + auto c = static_cast(curl); + delete c; +} + +CURLcode curl_easy_getinfo(CURL* curl, CURLINFO info, ...) { + auto c = static_cast(curl); + va_list args; +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wvarargs" +#endif + va_start(args, info); +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + if (info == CURLINFO_RESPONSE_CODE) { + if (CurlWrapperMock::downloadShallFail) { + long* codePtr = va_arg(args, long*); + *codePtr = 500; + } else { + long* codePtr = va_arg(args, long*); + *codePtr = c->returnNotFound ? 404 : 200; + } + } + va_end(args); + return CURLE_OK; +} + +struct curl_slist* curl_slist_append(struct curl_slist* existing, const char* data) { + curl_slist* newHead = (curl_slist*)malloc(sizeof(curl_slist)); + newHead->next = existing; + newHead->data = (char*)data; + return newHead; +} + +void free_recursively(struct curl_slist* head) { + if (head == nullptr) { + return; + } + + free_recursively(head->next); + free(head); +} + +void curl_slist_free_all(struct curl_slist* head) { + free_recursively(head); +} +} \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/DavsServiceMock.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/DavsServiceMock.cpp new file mode 100644 index 0000000000..633b2e5a1a --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/DavsServiceMock.cpp @@ -0,0 +1,121 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "DavsServiceMock.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "CurlWrapperMock.h" +#include "TestUtil.h" +#include "acsdkAssetsCommon/Base64Url.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; +using namespace chrono; +using namespace rapidjson; +using namespace alexaClientSDK::avsCommon::utils; + +DavsServiceMock::DavsServiceMock() { + CurlWrapperMock::root = createTmpDir("davs_service"); +} + +DavsServiceMock::~DavsServiceMock() { + filesystem::removeAll(CurlWrapperMock::root); + CurlWrapperMock::root = ""; +} + +void DavsServiceMock::uploadBinaryArtifact( + const string& type, + const string& key, + const FilterMap& metadata, + const string& filePath, + milliseconds ttlDelta, + const std::string& id) { + fstream input(filePath, ios::in | ios::binary); + if (input.good()) { + uploadArtifact(type, key, metadata, input, ttlDelta, id); + } +} + +void DavsServiceMock::uploadBase64Artifact( + const string& type, + const string& key, + const FilterMap& metadata, + const string& encodedBinary, + milliseconds ttlDelta, + const std::string& id) { + string content; + if (Base64Url::decode(encodedBinary, content)) { + uploadArtifact(type, key, metadata, stringstream(content), ttlDelta, id); + } +} + +void DavsServiceMock::uploadArtifact( + const string& type, + const string& key, + const FilterMap& metadata, + const istream& input, + milliseconds ttlDelta, + const std::string& id) { + auto file = type + "_" + key + "_" + getId(metadata); + + fstream artifact(CurlWrapperMock::root + "/" + file + ".artifact", ios::out | ios::binary); + if (artifact.fail()) { + return; + } + artifact << input.rdbuf(); + + auto size = artifact.tellp(); + auto ttl = duration_cast(system_clock::now().time_since_epoch() + ttlDelta).count(); + string url = "https://device-artifacts-v2.s3.amazonaws.com/" + file + ".tar.gz"; + + fstream response(CurlWrapperMock::root + "/" + file + ".response", ios::out); + response << "{" + << R"("urlExpiryEpoch": )" << ttl << "," << endl + << R"("artifactType": ")" << type << "\"," << endl + << R"("artifactSize": )" << size << "," << endl + << R"("artifactKey": ")" << key << "\"," << endl + << R"("artifactTimeToLive": )" << ttl << "," << endl + << R"("downloadUrl": ")" << url << "\"," << endl + << R"("artifactIdentifier": ")" << (id.empty() ? file : id) << "\"" << endl + << "}"; +} + +string DavsServiceMock::getId(const FilterMap& map) { + stringstream ss; + for (const auto& elem : map) { + ss << elem.first << ":"; + for (const auto& subElem : elem.second) { + ss << subElem; + } + } + return to_string(hash()(ss.str())); +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/InternetConnectionMonitorMock.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/InternetConnectionMonitorMock.cpp new file mode 100644 index 0000000000..6f78849e14 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/InternetConnectionMonitorMock.cpp @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "InternetConnectionMonitorMock.h" + +#include + +#include "acsdkAssetsCommon/AmdMetricWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; + +static const string TAG{"MAPLiteAuthDelegateMock"}; +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +InternetConnectionMonitorMock::~InternetConnectionMonitorMock() = default; + +std::shared_ptr InternetConnectionMonitorMock::create() { + ACSDK_INFO(LX(("create"))); + auto internetConnectionMonitor = shared_ptr(new InternetConnectionMonitorMock()); + return internetConnectionMonitor; +} + +void InternetConnectionMonitorMock::addInternetConnectionObserver( + std::shared_ptr observer) { + ACSDK_INFO(LX(("addInternetConnectionObserver"))); + + observer->onConnectionStatusChanged(true); +} +void InternetConnectionMonitorMock::removeInternetConnectionObserver( + std::shared_ptr observer) { + ACSDK_INFO(LX(("removeInternetConnectionObserver"))); +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/TestUtil.cpp b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/TestUtil.cpp new file mode 100644 index 0000000000..8df38f8e34 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/TestUtil.cpp @@ -0,0 +1,76 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +#if defined(FILE_SYSTEM_UTILS_ENABLED) + +#include "TestUtil.h" + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; +using namespace chrono; +using namespace alexaClientSDK::avsCommon::utils; + +bool waitUntil(const function& validate, milliseconds timeout) { + auto stopTime = steady_clock::now() + timeout; + do { + if (validate()) return true; + this_thread::sleep_for(milliseconds(1)); + } while (steady_clock::now() < stopTime); + return false; +} + +std::string createTmpDir(const std::string& postfix) { + char dirName[L_tmpnam + 1]{}; + if (tmpnam(dirName) == nullptr) { + cerr << "Could not get temporary directory name!" << endl; + exit(1); + } + auto path = dirName + postfix; + if (!filesystem::makeDirectory(path) || !filesystem::exists(path)) { + cerr << "Could not create temporary path!" << endl; + exit(1); + } + +#if defined(__linux__) || defined(__APPLE__) + // on some OS, the temp path is symbolically linked, which can cause issues for prefix tests + // to accommodate this, get the realpath of the temp directory + char resolved_path[PATH_MAX + 1]; + if (::realpath(path.c_str(), resolved_path) == nullptr) { + cerr << "Could not get real path of the temporary directory!" << endl; + exit(1); + } + path = string(resolved_path); +#endif + + while (*path.rbegin() == '/') { + path.pop_back(); + } + return path; +} + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/AuthDelegateMock.h b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/AuthDelegateMock.h new file mode 100644 index 0000000000..42d0bf9bd1 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/AuthDelegateMock.h @@ -0,0 +1,72 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_AUTHDELEGATEMOCK_H_ +#define AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_AUTHDELEGATEMOCK_H_ + +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace alexaClientSDK::avsCommon::sdkInterfaces; +/** + * Implementation of AuthDelegate that uses MAPLite. + */ +class AuthDelegateMock : public AuthDelegateInterface { +public: + ~AuthDelegateMock() override = default; + + /** + * Creates an instance of AuthDelegateInterface to be used with the SDK. + * @param setupMode Setup Manager used for communication with OOBE + * @return a new instance of MAPLiteAuthDelegate is created. + */ + static std::shared_ptr create(); + + /** + * @copydoc AuthDelegateInterface::getAuthToken + */ + std::string getAuthToken() override; + + /** + * @copydoc AuthDelegateInterface::onAuthFailure + */ + void onAuthFailure(const std::string& token) override; + + void addAuthObserver( + std::shared_ptr observer) override; + + void removeAuthObserver( + std::shared_ptr observer) override; + +private: + /** + * Constructor. + */ + AuthDelegateMock() = default; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_AUTHDELEGATEMOCK_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/CurlWrapperMock.h b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/CurlWrapperMock.h new file mode 100644 index 0000000000..95717bbbb8 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/CurlWrapperMock.h @@ -0,0 +1,40 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_CURLWRAPPERMOCK_H_ +#define AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_CURLWRAPPERMOCK_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +class CurlWrapperMock { +public: + static std::string root; + static std::string capturedRequest; + static std::string mockResponse; + static bool getResult; + static bool useDavsService; + static bool downloadShallFail; + static std::string header; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_CURLWRAPPERMOCK_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/DavsServiceMock.h b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/DavsServiceMock.h new file mode 100644 index 0000000000..529fa87a90 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/DavsServiceMock.h @@ -0,0 +1,109 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_DAVSSERVICEMOCK_H_ +#define AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_DAVSSERVICEMOCK_H_ + +#include +#include +#include +#include +#include +#include + +#include "acsdkAssetsInterfaces/DavsRequest.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +class DavsServiceMock { +public: + using FilterMap = commonInterfaces::DavsRequest::FilterMap; + + // Initializes empty service in /tmp/davs_service_mock + DavsServiceMock(); + + // Cleans up files + virtual ~DavsServiceMock(); + + /** + * This mimics the step of uploading artifact to DAVS. + * See https://w.amazon.com/bin/view/DeviceArtifactVendingService/DAVS2.0_SOP/#HPUTAPI-Uploadartifact + * + * @param type the type of artifact such as "wakeword" or "fingerprint" + * @param key the key such as "alexa" or "amazon" + * @param filterMap ordered map of filters + * @param filePath absolute path to a file to upload, such as "/tmp/file.tar.gz" + * @param ttlDelta the delta time from current time when artifact expires + * @param id OPTIONAL, id of the artifact as it sits in the cloud, if not provided, then a concatination of + * key+type+metadata is used + * + * Some analysis of fields expected by actual publishing call and how it affects the flow and if it matters to our + * mocking: clientArtifactId: DAVS expects that publisher creates the ID; we'll create the ID automatically by using + * the type_key_locale artifactMD5Checksum: DAVS uses this for validation of the upload; we don't care and we'll not + * use artifactKey: we'll use 'key' supplied by the consumer of this API; will be returned in response artifactType: + * we'll use 'type'; will be returned in response uploaderId: ignored description: ignored contentLength: we'll + * populate automatically and return in response artifactTimeToLive: we'll use supplied delta from current time + * metadata: given by the consumer of this API + * + * After this method is called, it is expected that CURL request to + * https://api.amazonalexa.com/v2/deviceArtifacts/?artifactFilter= will inspect the part and + * return a response JSON containing following valid fields: "artifactSize" will be the size of the local file + * "artifactKey" and "artifactType" will be the same as given here (BTW, client side doesn't care right now but + * might in future for further validation) "artifactTimeToLive" and "urlExpiryEpoch" will be currentTime + given + * delta, returned in MS like DAVS does "artifactIdentifier" will be the ID generated by this API and will + * internally be stored in /tmp/davs_service_mock "downloadUrl" will be something like + * "https://device-artifacts-v2.s3.amazonaws.com/type-key-ID.tar.gz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20180919T223612Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=AKIAJTPKJI7A3WTMPCQQ%2F20180919%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=87160eda6c8325e9ce61120974cdf2f81c4b8a3d47c1192f6cf4b7500ed17165" + * + * Once client issues a request to that download URL, it is expected that the content of the original file pointed + * to by filePath is returned. Once uploadBinaryArtifact() completes, the file is no longer needed and can be + * deleted. DAVS will make its own copy. + */ + void uploadBinaryArtifact( + const std::string& type, + const std::string& key, + const FilterMap& metadata, + const std::string& filePath, + std::chrono::milliseconds ttlDelta, + const std::string& id = ""); + + // The same as above, except that the file is expected to be given as base64-encoded string. For convenience, we + // expect that the caller uses Base64Url helper class to encode. + void uploadBase64Artifact( + const std::string& type, + const std::string& key, + const FilterMap& metadata, + const std::string& encodedBinary, + std::chrono::milliseconds ttlDelta, + const std::string& id = ""); + + static std::string getId(const FilterMap& map); + +private: + void uploadArtifact( + const std::string& type, + const std::string& key, + const FilterMap& metadata, + const std::istream& input, + std::chrono::milliseconds ttlDelta, + const std::string& id = ""); +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_DAVSSERVICEMOCK_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/InternetConnectionMonitorMock.h b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/InternetConnectionMonitorMock.h new file mode 100644 index 0000000000..d7a0ea1f30 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/InternetConnectionMonitorMock.h @@ -0,0 +1,48 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_INTERNETCONNECTIONMONITORMOCK_H_ +#define AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_INTERNETCONNECTIONMONITORMOCK_H_ +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { +class InternetConnectionMonitorMock + : public alexaClientSDK::avsCommon::sdkInterfaces::InternetConnectionMonitorInterface { +public: + ~InternetConnectionMonitorMock() override; + + static std::shared_ptr create(); + + void addInternetConnectionObserver( + std::shared_ptr observer) + override; + void removeInternetConnectionObserver( + std::shared_ptr observer) + override; + +private: + InternetConnectionMonitorMock() = default; +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_INTERNETCONNECTIONMONITORMOCK_H_ diff --git a/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/TestUtil.h b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/TestUtil.h new file mode 100644 index 0000000000..17a2e4b94a --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsCommon/test/mocks/include/TestUtil.h @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_TESTUTIL_H_ +#define AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_TESTUTIL_H_ + +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace common { + +using namespace std; +using namespace chrono; + +/** + * Keeps executing a given function every given amount, and will return true as soon as the function returns true. + * Otherwise, it will return false after timeout time. + */ +bool waitUntil(const std::function& validate, std::chrono::milliseconds timeout = std::chrono::seconds(5)); + +/** + * Creates a temporary directory starting with . Asserts false upon failure. + * @note you are responsible for deleting this directory after usage. + */ +std::string createTmpDir(const std::string& prefix = "test"); + +struct PrintDescription { + template + std::string operator()(const TestParamInfoType& info) const { + auto s = info.param.description; + std::transform(s.begin(), s.end(), s.begin(), [](char ch) { return isalnum(ch) ? ch : '_'; }); + return s; + } +}; + +} // namespace common +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // AVS_CAPABILITIES_DAVSCLIENT_ACSDKASSETSCOMMON_TEST_MOCKS_TESTUTIL_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/CMakeLists.txt b/capabilities/DavsClient/acsdkAssetsInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..4f9b641763 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkAssetsInterfaces LANGUAGES CXX) + +if (DEFINED AVS_CMAKE_BUILD) + include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +endif() + +add_subdirectory("src") diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/ArtifactRequest.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/ArtifactRequest.h new file mode 100644 index 0000000000..76cc9cdd5c --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/ArtifactRequest.h @@ -0,0 +1,76 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_ARTIFACTREQUEST_H_ +#define ACSDKASSETSINTERFACES_ARTIFACTREQUEST_H_ + +#include +#include +#include + +#include "Type.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +/** + * Artifact Request will be a common parent class for all types of request that will be supported by Asset Manager + * and will maintain the common interface needed to represent a unique request. + */ +class ArtifactRequest { +public: + static constexpr auto UNPACK = true; + + /** + * Destructor. + */ + virtual ~ArtifactRequest() = default; + + /** + * @return the type of the request. + */ + virtual Type getRequestType() const = 0; + + /** + * @return weather the artifact needs to be unpacked or not. + */ + virtual bool needsUnpacking() const = 0; + + /** + * @return a concatenated string that describes the request. + */ + virtual std::string getSummary() const = 0; + + /** + * @return a JSON representation of this request that includes all of its component. + */ + virtual std::string toJsonString() const = 0; +}; + +/** Use summary to determine if two requests are identical */ +inline bool operator==(const ArtifactRequest& lhs, const ArtifactRequest& rhs) { + return lhs.getSummary() == rhs.getSummary(); +} + +inline bool operator!=(const ArtifactRequest& lhs, const ArtifactRequest& rhs) { + return !(lhs == rhs); +} + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_ARTIFACTREQUEST_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Communication/AmdCommunicationInterface.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Communication/AmdCommunicationInterface.h new file mode 100644 index 0000000000..5003fc95ce --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Communication/AmdCommunicationInterface.h @@ -0,0 +1,70 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_COMMUNICATION_AMDCOMMUNICATIONINTERFACE_H_ +#define ACSDKASSETSINTERFACES_COMMUNICATION_AMDCOMMUNICATIONINTERFACE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +class AmdCommunicationInterface + : public virtual acsdkCommunicationInterfaces::CommunicationPropertiesHandlerInterface + , public virtual acsdkCommunicationInterfaces::CommunicationPropertiesHandlerInterface + , public virtual acsdkCommunicationInterfaces::CommunicationInvokeHandlerInterface + , public virtual acsdkCommunicationInterfaces::CommunicationInvokeHandlerInterface { +public: + /** + * Virtual destructor to assure proper cleanup of derived types. + */ + ~AmdCommunicationInterface() override = default; + + /* + * Overriding CommunicationPropertyInterface for int and string + */ + using CommunicationPropertiesHandlerInterface::registerProperty; + using CommunicationPropertiesHandlerInterface::deregisterProperty; + using CommunicationPropertiesHandlerInterface::writeProperty; + using CommunicationPropertiesHandlerInterface::readProperty; + using CommunicationPropertiesHandlerInterface::subscribeToPropertyChangeEvent; + using CommunicationPropertiesHandlerInterface::unsubscribeToPropertyChangeEvent; + + using CommunicationPropertiesHandlerInterface::registerProperty; + using CommunicationPropertiesHandlerInterface::deregisterProperty; + using CommunicationPropertiesHandlerInterface::writeProperty; + using CommunicationPropertiesHandlerInterface::readProperty; + using CommunicationPropertiesHandlerInterface::subscribeToPropertyChangeEvent; + using CommunicationPropertiesHandlerInterface::unsubscribeToPropertyChangeEvent; + + /* + * Overriding CommunicationInvokeHandlerInterface for string + */ + using CommunicationInvokeHandlerInterface::registerFunction; + using CommunicationInvokeHandlerInterface::deregister; + using CommunicationInvokeHandlerInterface::invoke; + + using CommunicationInvokeHandlerInterface::registerFunction; + using CommunicationInvokeHandlerInterface::deregister; + using CommunicationInvokeHandlerInterface::invoke; +}; + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_COMMUNICATION_AMDCOMMUNICATIONINTERFACE_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Communication/InMemoryAmdCommunicationHandler.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Communication/InMemoryAmdCommunicationHandler.h new file mode 100644 index 0000000000..bac7953cb5 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Communication/InMemoryAmdCommunicationHandler.h @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_COMMUNICATION_INMEMORYAMDCOMMUNICATIONHANDLER_H_ +#define ACSDKASSETSINTERFACES_COMMUNICATION_INMEMORYAMDCOMMUNICATIONHANDLER_H_ +#include +#include +#include "AmdCommunicationInterface.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +class InMemoryAmdCommunicationHandler + : public virtual AmdCommunicationInterface + , public acsdkCommunication::InMemoryCommunicationPropertiesHandler + , public acsdkCommunication::InMemoryCommunicationPropertiesHandler + , public acsdkCommunication::InMemoryCommunicationInvokeHandler + , public acsdkCommunication::InMemoryCommunicationInvokeHandler { +public: + ~InMemoryAmdCommunicationHandler() override = default; + + static std::shared_ptr create() { + return std::shared_ptr(new InMemoryAmdCommunicationHandler()); + } + +private: + InMemoryAmdCommunicationHandler() = default; +}; + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_COMMUNICATION_INMEMORYAMDCOMMUNICATIONHANDLER_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/DavsRequest.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/DavsRequest.h new file mode 100644 index 0000000000..adcb321cbf --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/DavsRequest.h @@ -0,0 +1,124 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_DAVSREQUEST_H_ +#define ACSDKASSETSINTERFACES_DAVSREQUEST_H_ + +#include +#include + +#include "ArtifactRequest.h" +#include "Region.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +class DavsRequest : public ArtifactRequest { +public: + using FilterMap = std::map>; + + /** + * Creates an Artifact Request that will contain all the necessary information to identify an artifact with DAVS. + * API information: https://wiki.labcollab.net/confluence/display/Doppler/DAVS+2.0+GET+ARTIFACT+API + * + * @param type REQUIRED, used to identify a family of artifact (WW, earcon, alarms...). + * @param key REQUIRED, used to narrow down the scope per type, for WW (alexa, amazon), for earcons (tones, + * local-dependant), etc... + * @param filters REQUIRED, extra filters that are flexible in number (Locale, compatibility versions, etc...). + * @param endpoint OPTIONAL, specifies the endpoint for the request to download from, defaults to NA. + * @param unpack OPTIONAL, if true, then artifact will be unpacked and the directory will be provided. + * @return NULLABLE, a smart pointer to a request if all params are valid. + */ + static std::shared_ptr create( + std::string type, + std::string key, + FilterMap filters, + Region endpoint = Region::NA, + bool unpack = false); + + /** + * @return the Type which is used to identify the main component of this DAVS request. + */ + const std::string& getType() const; + + /** + * @return the Key which is used to identify the subcomponent of this DAVS request. + */ + const std::string& getKey() const; + + /** + * @return the map of filter sets used to distinguish this DAVS request from similar components. + */ + const FilterMap& getFilters() const; + + /** + * @return the DAVS Region which this request is targeting. + */ + Region getRegion() const; + + /// @name { ArtifactRequest methods. + /// @{ + Type getRequestType() const override; + bool needsUnpacking() const override; + std::string getSummary() const override; + std::string toJsonString() const override; + /// @} + +private: + DavsRequest(std::string type, std::string key, FilterMap filters, Region endpoint, bool unpack); + +private: + const std::string m_type; + const std::string m_key; + const FilterMap m_filters; + const Region m_region; + const bool m_unpack; + std::string m_summary; +}; + +inline const std::string& DavsRequest::getType() const { + return m_type; +} + +inline const std::string& DavsRequest::getKey() const { + return m_key; +} + +inline const DavsRequest::FilterMap& DavsRequest::getFilters() const { + return m_filters; +} + +inline Region DavsRequest::getRegion() const { + return m_region; +} + +inline Type DavsRequest::getRequestType() const { + return Type::DAVS; +} + +inline bool DavsRequest::needsUnpacking() const { + return m_unpack; +} + +inline std::string DavsRequest::getSummary() const { + return m_summary; +} + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_DAVSREQUEST_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Endpoint.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Endpoint.h new file mode 100644 index 0000000000..8fc6981290 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Endpoint.h @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_ENDPOINT_H_ +#define ACSDKASSETSINTERFACES_ENDPOINT_H_ + +namespace amazon { +namespace davs { + +/** + * Specific DAVS endpoint region to connect to. + */ +enum class Endpoint { NA, EU, FE }; + +} // namespace davs +} // namespace amazon + +#endif // ACSDKASSETSINTERFACES_ENDPOINT_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Priority.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Priority.h new file mode 100644 index 0000000000..4ff2ed46f9 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Priority.h @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_PRIORITY_H_ +#define ACSDKASSETSINTERFACES_PRIORITY_H_ + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +/** + * The priority given to an artifact that determines the order of its deletion when space is low. + * ACTIVE and PENDING_ACTIVATION are never to be deleted. + */ +enum class Priority { ACTIVE, PENDING_ACTIVATION, LIKELY_TO_BE_ACTIVE, UNUSED_IMPORTANT, UNUSED }; + +inline bool isValidPriority(int value) { + return value >= static_cast(Priority::ACTIVE) && value <= static_cast(Priority::UNUSED); +} + +inline std::string toString(Priority priority) { + switch (priority) { + case Priority::ACTIVE: + return "ACTIVE"; + case Priority::PENDING_ACTIVATION: + return "PENDING_ACTIVATION"; + case Priority::LIKELY_TO_BE_ACTIVE: + return "LIKELY_TO_BE_ACTIVE"; + case Priority::UNUSED_IMPORTANT: + return "UNUSED_IMPORTANT"; + case Priority::UNUSED: + return "UNUSED"; + } + return ""; +} + +inline std::ostream& operator<<(std::ostream& os, Priority value) { + return os << toString(value); +} + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_PRIORITY_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Region.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Region.h new file mode 100644 index 0000000000..824c388d0a --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Region.h @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_REGION_H_ +#define ACSDKASSETSINTERFACES_REGION_H_ + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +/** + * Specific DAVS endpoint region to connect to. + */ +enum class Region { NA, EU, FE }; + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_REGION_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/ResultCode.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/ResultCode.h new file mode 100644 index 0000000000..a1fc2501c9 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/ResultCode.h @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_RESULTCODE_H_ +#define ACSDKASSETSINTERFACES_RESULTCODE_H_ + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +// Result codes values here are a match between DAVS server codes and davs client codes +enum class ResultCode { + CONNECTION_FAILED = 47, + CONNECTION_TIMED_OUT = -51, + CHECKSUM_MISMATCH = -52, + NO_SPACE_AVAILABLE = -53, + SUCCESS = 200, + UP_TO_DATE = 304, + ILLEGAL_ARGUMENT = 400, + UNAUTHORIZED = 401, + FORBIDDEN = 403, + NO_ARTIFACT_FOUND = 404, + UNHANDLED_MIME_TYPE = -998, + CATASTROPHIC_FAILURE = -999 +}; + +inline std::ostream& operator<<(std::ostream& os, ResultCode result) { + switch (result) { + case ResultCode::CONNECTION_FAILED: + return os << "CONNECTION_FAILED"; + case ResultCode::CONNECTION_TIMED_OUT: + return os << "CONNECTION_TIMED_OUT"; + case ResultCode::CHECKSUM_MISMATCH: + return os << "CHECKSUM_MISMATCH"; + case ResultCode::NO_SPACE_AVAILABLE: + return os << "NO_SPACE_AVAILABLE"; + case ResultCode::SUCCESS: + return os << "SUCCESS"; + case ResultCode::UP_TO_DATE: + return os << "UP_TO_DATE"; + case ResultCode::ILLEGAL_ARGUMENT: + return os << "ILLEGAL_ARGUMENT"; + case ResultCode::UNAUTHORIZED: + return os << "UNAUTHORIZED"; + case ResultCode::FORBIDDEN: + return os << "FORBIDDEN"; + case ResultCode::NO_ARTIFACT_FOUND: + return os << "NO_ARTIFACT_FOUND"; + case ResultCode::UNHANDLED_MIME_TYPE: + return os << "UNHANDLED_MIME_TYPE"; + case ResultCode::CATASTROPHIC_FAILURE: + return os << "CATASTROPHIC_FAILURE"; + } + return os; +} + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_RESULTCODE_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/State.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/State.h new file mode 100644 index 0000000000..2650ee2bd3 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/State.h @@ -0,0 +1,61 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_STATE_H_ +#define ACSDKASSETSINTERFACES_STATE_H_ + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { +/* + * The state of the DAVS artifacts on the system. + * + * @startuml + * (INIT) --> (LOADED) : From Storage + * (INIT) --> (REQUESTING) : From DAVS + * (REQUESTING) --> (LOADED) : Already found \n on device + * (REQUESTING) --> (DOWNLOADING) + * (REQUESTING) --> (INVALID) : Failed + * (DOWNLOADING) --> (LOADED) + * (DOWNLOADING) --> (INVALID) : Failed + * @enduml + */ +enum class State { INIT, REQUESTING, DOWNLOADING, INVALID, LOADED }; + +inline std::string toString(State state) { + switch (state) { + case State::INIT: + return "INIT"; + case State::REQUESTING: + return "REQUESTING"; + case State::DOWNLOADING: + return "DOWNLOADING"; + case State::INVALID: + return "INVALID"; + case State::LOADED: + return "LOADED"; + } + return ""; +} + +inline std::ostream& operator<<(std::ostream& os, State value) { + return os << toString(value); +} + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_STATE_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Type.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Type.h new file mode 100644 index 0000000000..7d532c6118 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/Type.h @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_TYPE_H_ +#define ACSDKASSETSINTERFACES_TYPE_H_ + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +/** + * Types of requests to download. + */ +enum class Type { DAVS, URL }; + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_TYPE_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/UrlRequest.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/UrlRequest.h new file mode 100644 index 0000000000..3250036d38 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/UrlRequest.h @@ -0,0 +1,105 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_URLREQUEST_H_ +#define ACSDKASSETSINTERFACES_URLREQUEST_H_ + +#include "ArtifactRequest.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +class UrlRequest : public ArtifactRequest { +public: + /** + * Creates an Artifact Request that will contain all the necessary info to download a file using a url. + * + * @param url REQUIRED, url used to download the desired artifact. + * @param filename REQUIRED, name of the resource to be stored on the device. + * @param unpack OPTIONAL, if true, then artifact will be unpacked and the directory will be provided. + * @param certPath OPTIONAL, if not-emtpy, then the download request will be made with the SSL cert at the cerPath. + * @return NULLABLE, a smart pointer to a request if all params are valid. + */ + static std::shared_ptr create( + std::string url, + std::string filename, + bool unpack = false, + std::string certPath = ""); + + /** + * @return the URL used to download the requested file. + */ + const std::string& getUrl() const; + + /** + * @return the filename to be used for this artifact in case we cannot fetch the name from HTTP headers. + */ + const std::string& getFilename() const; + + /** + * @return the optional filepath to the SSL Certificate which can be used for this request. + */ + const std::string& getCertPath() const; + + /// @name { ArtifactRequest methods. + /// @{ + Type getRequestType() const override; + bool needsUnpacking() const override; + std::string getSummary() const override; + std::string toJsonString() const override; + /// @} + +private: + UrlRequest(std::string url, std::string filename, bool unpack, std::string certPath); + +private: + const std::string m_url; + const std::string m_filename; + const bool m_unpack; + std::string m_summary; + const std::string m_certPath; + std::string m_certHash; +}; + +inline const std::string& UrlRequest::getUrl() const { + return m_url; +} + +inline const std::string& UrlRequest::getFilename() const { + return m_filename; +} + +inline const std::string& UrlRequest::getCertPath() const { + return m_certPath; +} + +inline Type UrlRequest::getRequestType() const { + return Type::URL; +} + +inline bool UrlRequest::needsUnpacking() const { + return m_unpack; +} + +inline std::string UrlRequest::getSummary() const { + return m_summary; +} + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_URLREQUEST_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/VendableArtifact.h b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/VendableArtifact.h new file mode 100644 index 0000000000..5b6ec8f4f0 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/include/acsdkAssetsInterfaces/VendableArtifact.h @@ -0,0 +1,131 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKASSETSINTERFACES_VENDABLEARTIFACT_H_ +#define ACSDKASSETSINTERFACES_VENDABLEARTIFACT_H_ + +#include + +#include "DavsRequest.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +class VendableArtifact { +public: + using TimeEpoch = std::chrono::system_clock::time_point; + + /** + * Creates a Vendable Artifact containing all the information about a given artifact and how to get it. + * + * @param request REQUIRED, contains the original request that's parsed by DAVS. + * @param id REQUIRED, uniquely identifies an artifact (hash of the file itself). + * @param artifactSizeBytes REQUIRED, size of the artifact that is being downloaded. + * @param artifactExpiry REQUIRED, epoch when the artifact should be checked again for update. + * @param s3Url REQUIRED, signed url where we can download the artifact. + * @param urlExpiry REQUIRED, epoch when the url for the artifact download will expire. + * @param currentSizeBytes OPTIONAL? TODO: still not sure what this is... + * @param multipart is the vendable artifact constructed by a multipart response + * @return NULLABLE, a smart pointer to Vendable Artifact if all parameters are valid. + */ + static std::unique_ptr create( + std::shared_ptr request, + std::string id, + size_t artifactSizeBytes, + TimeEpoch artifactExpiry, + std::string s3Url, + TimeEpoch urlExpiry, + size_t currentSizeBytes, + bool multipart); + + /** + * Creates a Vendable Artifact from JSON string. + * + * @param request REQUIRED, contains the original request that's parsed by DAVS. + * @param jsonString REQUIRED, contains the JSON string to parse and read values from. + * @param isMultipart Optional, defaults to false. If the artifact is a multipart artifact. + * @return NULLABLE, a smart pointer to Vendable Artifact if given JSON is valid, otherwise nullptr. + */ + static std::unique_ptr create( + std::shared_ptr request, + const std::string& jsonString, + const bool isMultipart = false); + + inline const std::shared_ptr& getRequest() const { + return m_request; + } + + inline const std::string& getId() const { + return m_id; + } + + inline const std::string& getS3Url() const { + return m_s3Url; + } + + inline size_t getArtifactSizeBytes() const { + return m_artifactSizeBytes; + } + + inline const TimeEpoch& getArtifactExpiry() const { + return m_artifactExpiry; + } + + inline const TimeEpoch& getUrlExpiry() const { + return m_urlExpiry; + } + + inline size_t getCurrentSizeBytes() const { + return m_currentSizeBytes; + } + + inline const std::string& getUniqueIdentifier() const { + return m_uuid; + } + + inline bool isMultipart() const { + return m_multipart; + } + +private: + VendableArtifact( + std::shared_ptr request, + std::string id, + size_t artifactSizeBytes, + TimeEpoch artifactExpiry, + std::string s3Url, + TimeEpoch urlExpiry, + size_t currentSizeBytes, + std::string uuid, + bool multipart); + +private: + const std::shared_ptr m_request; + const std::string m_id; + const size_t m_artifactSizeBytes; + const TimeEpoch m_artifactExpiry; + const std::string m_s3Url; + const TimeEpoch m_urlExpiry; + const size_t m_currentSizeBytes; + const std::string m_uuid; + const bool m_multipart; +}; + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKASSETSINTERFACES_VENDABLEARTIFACT_H_ diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/src/CMakeLists.txt b/capabilities/DavsClient/acsdkAssetsInterfaces/src/CMakeLists.txt new file mode 100644 index 0000000000..7ec3c1fa87 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/src/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.1) + +add_library(acsdkAssetsInterfaces + DavsRequest.cpp + UrlRequest.cpp + VendableArtifact.cpp + ) + +target_include_directories(acsdkAssetsInterfaces PUBLIC + "${acsdkAssetsInterfaces_SOURCE_DIR}/include" + "${RAPIDJSON_INCLUDE_DIR}" + ) + +target_link_libraries(acsdkAssetsInterfaces + AVSCommon + acsdkNotifier + acsdkCommunicationInterfaces + acsdkCommunication + ) + +# install target +asdk_install() \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/src/DavsRequest.cpp b/capabilities/DavsClient/acsdkAssetsInterfaces/src/DavsRequest.cpp new file mode 100644 index 0000000000..0a2632abec --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/src/DavsRequest.cpp @@ -0,0 +1,118 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsInterfaces/DavsRequest.h" + +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +using namespace std; +using namespace avsCommon::utils::json; + +static const char* ARTIFACT_TYPE = "artifactType"; +static const char* ARTIFACT_KEY = "artifactKey"; +static const char* ARTIFACT_FILTERS = "filters"; +static const char* ARTIFACT_UNPACK = "unpack"; +static const char* ARTIFACT_ENDPOINT = "endpoint"; + +/// String to identify log entries originating from this file. +static const std::string TAG{"DavsRequest"}; + +/** + * 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) + +shared_ptr DavsRequest::create(string type, string key, FilterMap filters, Region endpoint, bool unpack) { + if (type.empty()) { + ACSDK_ERROR(LX("create").m("Empty type")); + return nullptr; + } + + if (key.empty()) { + ACSDK_ERROR(LX("create").m("Empty key")); + return nullptr; + } + + for (const auto& filter : filters) { + if (filter.first.empty()) { + ACSDK_ERROR(LX("create").m("Empty filter key")); + return nullptr; + } + if (filter.second.empty()) { + ACSDK_ERROR(LX("create").m("Empty filter value for key").d("key", filter.first)); + return nullptr; + } + } + + return unique_ptr(new DavsRequest(move(type), move(key), move(filters), endpoint, unpack)); +} + +DavsRequest::DavsRequest(string type, string key, FilterMap filters, Region endpoint, bool unpack) : + m_type(move(type)), + m_key(move(key)), + m_filters(move(filters)), + m_region(endpoint), + m_unpack(unpack) { + m_summary = this->m_type + "_" + this->m_key; + + for (const auto& filter : this->m_filters) { + for (const auto& item : filter.second) { + m_summary += "_" + item; + } + } + + // NA endpoint can be left without a suffix, to mimic the url endpoint pattern + if (endpoint == Region::EU) { + m_summary += "_EU"; + } else if (endpoint == Region::FE) { + m_summary += "_FE"; + } + + if (unpack) { + m_summary += "_unpacked"; + } + + // remove special characters that would be incompatible as property names or file names + m_summary.erase( + remove_if(m_summary.begin(), m_summary.end(), [](char c) { return c != '_' && !isalnum(c); }), + m_summary.end()); +} + +std::string DavsRequest::toJsonString() const { + JsonGenerator generator; + generator.addMember(ARTIFACT_TYPE, m_type); + generator.addMember(ARTIFACT_KEY, m_key); + generator.startObject(ARTIFACT_FILTERS); + for (const auto& filter : m_filters) { + generator.addStringArray(filter.first, filter.second); + } + generator.finishObject(); + generator.addMember(ARTIFACT_ENDPOINT, static_cast(m_region)); + generator.addMember(ARTIFACT_UNPACK, m_unpack); + return generator.toString(); +} + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/src/UrlRequest.cpp b/capabilities/DavsClient/acsdkAssetsInterfaces/src/UrlRequest.cpp new file mode 100644 index 0000000000..501099d2e5 --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/src/UrlRequest.cpp @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsInterfaces/UrlRequest.h" + +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +using namespace std; +using namespace avsCommon::utils::json; + +static const char* ARTIFACT_URL = "url"; +static const char* ARTIFACT_FILENAME = "filename"; +static const char* ARTIFACT_UNPACK = "unpack"; +static const char* ARTIFACT_CERT_PATH = "certPath"; + +/// String to identify log entries originating from this file. +static const std::string TAG{"UrlRequest"}; + +/** + * 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) + +static std::string getHash(const std::string& str) { + return std::to_string(std::hash{}(str)); +}; + +shared_ptr UrlRequest::create(string url, string filename, bool unpack, string certPath) { + if (url.empty()) { + ACSDK_ERROR(LX("create").m("Empty url")); + return nullptr; + } + if (filename.empty()) { + ACSDK_ERROR(LX("create").m("Empty filename")); + return nullptr; + } + if (filename.find("..") != std::string::npos) { + ACSDK_ERROR(LX("create").m("Filename containing '..' not allowed").d("file name", filename.c_str())); + return nullptr; + } + if (!certPath.empty()) { + ACSDK_INFO(LX("create").m("Using custom cert from path").d("path", certPath)); + } + + return unique_ptr(new UrlRequest(move(url), move(filename), unpack, move(certPath))); +} + +UrlRequest::UrlRequest(string url, string filename, bool unpack, string certPath) : + m_url(move(url)), + m_filename(move(filename)), + m_unpack(unpack), + m_certPath(certPath), + m_certHash(certPath.empty() ? "" : getHash(certPath)) { + m_summary = "url_" + getHash(this->m_url) + "_" + this->m_filename; + + m_summary += this->m_certHash; + + if (m_unpack) { + m_summary += "_unpacked"; + } + + // remove special characters that would be incompatible as property names or file names + m_summary.erase( + remove_if(m_summary.begin(), m_summary.end(), [](char c) { return c != '_' && !isalnum(c); }), + m_summary.end()); +} + +string UrlRequest::toJsonString() const { + JsonGenerator generator; + generator.addMember(ARTIFACT_URL, m_url); + generator.addMember(ARTIFACT_FILENAME, m_filename); + generator.addMember(ARTIFACT_CERT_PATH, m_certPath); + generator.addMember(ARTIFACT_UNPACK, m_unpack); + return generator.toString(); +} + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkAssetsInterfaces/src/VendableArtifact.cpp b/capabilities/DavsClient/acsdkAssetsInterfaces/src/VendableArtifact.cpp new file mode 100644 index 0000000000..389231309a --- /dev/null +++ b/capabilities/DavsClient/acsdkAssetsInterfaces/src/VendableArtifact.cpp @@ -0,0 +1,196 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAssetsInterfaces/VendableArtifact.h" + +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace commonInterfaces { + +using namespace std; +using namespace rapidjson; + +/// String to identify log entries originating from this file. +static const std::string TAG{"VendableArtifact"}; + +/** + * 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) + +unique_ptr VendableArtifact::create( + shared_ptr request, + string id, + size_t artifactSizeBytes, + TimeEpoch artifactExpiry, + string s3Url, + TimeEpoch urlExpiry, + size_t currentSizeBytes, + bool multipart) { + if (request == nullptr) { + ACSDK_ERROR(LX("create").m("Null request")); + return nullptr; + } + + if (id.empty()) { + ACSDK_ERROR(LX("create").m("Empty id")); + return nullptr; + } + + if (artifactSizeBytes == 0) { + ACSDK_ERROR(LX("create").m("Artifact Size is zero")); + return nullptr; + } + + if (!multipart && s3Url.empty()) { + ACSDK_ERROR(LX("create").m("Empty S3 URL")); + return nullptr; + } + + auto uuid = request->getType() + "_" + request->getKey() + "_" + id; + if (request->needsUnpacking()) { + uuid += "_unpack"; + } + return unique_ptr(new VendableArtifact( + move(request), + move(id), + artifactSizeBytes, + artifactExpiry, + move(s3Url), + urlExpiry, + currentSizeBytes, + move(uuid), + multipart)); +} + +/** + * Read JSON member into given string. + * @param destination the string to write to + * @param source the JSON object to read from + * @param key the JSON key to read + * @return true if the key exists and is a valid string + */ +static bool readStringMember(string& destination, const Document& source, const char* key) { + if (!source.HasMember(key) || !source[key].IsString()) { + return false; + } + + string value = source[key].GetString(); + if (value.empty() || value == "null") { + return false; + } + + destination = value; + return true; +} + +/** + * Read JSON member into given uint64_t. + * @param destination the uint64_t to write to + * @param source the JSON object to read from + * @param key the JSON key to read + * @return true if the key exists and is a valid number + */ +static bool readSizeMember(uint64_t& destination, const Document& source, const char* key) { + if (!source.HasMember(key) || !source[key].IsUint64()) { + return false; + } + + destination = source[key].GetUint64(); + return true; +} + +unique_ptr VendableArtifact::create( + std::shared_ptr request, + const string& jsonString, + const bool isMultipart) { + Document document; + document.Parse(jsonString); + if (document.HasParseError() || !document.IsObject()) { + ACSDK_ERROR(LX("create").m("Can't parse JSON").d("json", jsonString.c_str())); + return nullptr; + } + + string s3Url; + string id; + uint64_t urlExpiry; + uint64_t ttl; + uint64_t size; + if (!isMultipart) { + if (!readStringMember(s3Url, document, "downloadUrl")) { + ACSDK_ERROR(LX("create").m("Failed to parse download URL")); + return nullptr; + } + if (!readSizeMember(urlExpiry, document, "urlExpiryEpoch")) { + ACSDK_ERROR(LX("create").m("Failed to parse URL Expiry Epoch")); + return nullptr; + } + } + if (!readStringMember(id, document, "artifactIdentifier")) { + ACSDK_ERROR(LX("create").m("Failed to parse Artifact Identifier")); + return nullptr; + } + if (!readSizeMember(ttl, document, "artifactTimeToLive") && + !readSizeMember(ttl, document, "suggestedPollInterval")) { + ACSDK_ERROR(LX("create").m("Failed to parse TTL or Polling Interval")); + return nullptr; + } + if (!readSizeMember(size, document, "artifactSize")) { + ACSDK_ERROR(LX("create").m("Failed to parse Artifact Size")); + return nullptr; + } + + return create( + move(request), + id, + size, + TimeEpoch(chrono::milliseconds(ttl)), + s3Url, + TimeEpoch(chrono::milliseconds(urlExpiry)), + 0, + isMultipart); +} + +VendableArtifact::VendableArtifact( + shared_ptr request, + string id, + size_t artifactSizeBytes, + TimeEpoch artifactExpiry, + string s3Url, + TimeEpoch urlExpiry, + size_t currentSizeBytes, + string uuid, + bool multipart) : + m_request(move(request)), + m_id(move(id)), + m_artifactSizeBytes(artifactSizeBytes), + m_artifactExpiry(artifactExpiry), + m_s3Url(move(s3Url)), + m_urlExpiry(urlExpiry), + m_currentSizeBytes(currentSizeBytes), + m_uuid(move(uuid)), + m_multipart(multipart) { +} + +} // namespace commonInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkDavsClient/CMakeLists.txt b/capabilities/DavsClient/acsdkDavsClient/CMakeLists.txt new file mode 100644 index 0000000000..0c6a20d826 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkDavsClient LANGUAGES CXX) + +if (DEFINED AVS_CMAKE_BUILD) + include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +endif() + +add_subdirectory("src") +add_subdirectory("test") diff --git a/capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsClient.h b/capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsClient.h new file mode 100644 index 0000000000..19cc2ac533 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsClient.h @@ -0,0 +1,168 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKDAVSCLIENT_DAVSCLIENT_H_ +#define ACSDKDAVSCLIENT_DAVSCLIENT_H_ + +#include +#include +#include +#include +#include "DavsHandler.h" +#include "acsdkDavsClientInterfaces/DavsEndpointHandlerInterface.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davs { + +class DavsClient + : public davsInterfaces::ArtifactHandlerInterface + , public alexaClientSDK::avsCommon::sdkInterfaces::InternetConnectionObserverInterface { +public: + ~DavsClient() override = default; + + /** + * Creates a DAVS Client given a working directory. + * + * @param workingDirectory REQUIRED, place to store the temporary davs files, will be wiped on initialization. + * @param authDelegate REQUIRED, the Authentication Delegate to generate the authentication token + * @param wifiMonitor REQUIRED, the InternetConnectionMonitor that Davs client will subscribe to. It will check to + * see if we are connected to the internet or not and notify the DAVS client. + * @param davsEndpointHandler REQUIRED, the endpoint handler which is used to generate the proper DAVS request URL. + * @param metricRecorder OPTIONAL, metric recorder used for recording metrics. + * @param forcedUpdateInterval OPTIONAL, sets the update interval for all artifact to a specific value. This is to + * be use only for testing! your devices will be throttled if used in production! + * @return NULLABLE, new DAVS Client + */ + static std::shared_ptr create( + std::string workingDirectory, + std::shared_ptr authDelegate, + std::shared_ptr wifiMonitor, + std::shared_ptr davsEndpointHandler, + std::shared_ptr metricRecorder = nullptr, + std::chrono::seconds forcedUpdateInterval = std::chrono::seconds(0)); + + /** + * @return true if the device is idle + */ + bool getIdleState() const; + + void setIdleState(bool idleState); + + /// @name ArtifactHandlerInterface Functions + /// @{ + std::string registerArtifact( + std::shared_ptr artifactRequest, + std::shared_ptr downloadCallback, + std::shared_ptr checkCallback, + bool downloadImmediately) override; + void deregisterArtifact(const std::string& requestUUID) override; + std::string downloadOnce( + std::shared_ptr artifactRequest, + std::shared_ptr downloadCallback, + std::shared_ptr checkCallback) override; + void enableAutoUpdate(const std::string& requestUUID, bool enable) override; + /// @} + + /** + * @name InternetConnectionObserverInterface functions + */ + void onConnectionStatusChanged(bool connected) override; + + /*** + * Information from parsed Json Artifact Push Notification + */ + struct ArtifactGroup { + std::string type; + std::string key; + }; + + /*** + * Take a json containing list of artifact group + * Parse the json. E.g:{"artifactList":[{"type":"test","key":"tar"}]} + * Then check and update the corresponding artifact + * @param jsonArtifactList + */ + void checkAndUpdateArtifactGroupFromJson(const std::string& jsonArtifactList); + + /*** + * Take a vector of ArtifactGroup and iterate every Artifact to check and then update + * @param artifactVector containing artifact requesting to be updated + */ + void checkAndUpdateArtifactGroupVector(const std::vector& artifactVector); + +private: + DavsClient( + std::string workingDirectory, + std::shared_ptr authDelegate, + std::shared_ptr davsEndpointHandler, + std::shared_ptr metricRecorder = nullptr, + std::chrono::seconds forcedUpdateInterval = std::chrono::seconds(0)); + + std::string handleRequest( + std::shared_ptr artifactRequest, + std::shared_ptr downloadCallback, + std::shared_ptr checkCallback, + bool enableAutoUpdate, + bool downloadImmediately); + + /*** + * Check below if an artifact requesting update before do it: + * + Still relevant + * + Enabled update + * + Registered + * @param artifactGroup that is requesting an update + */ + void executeUpdateRegisteredArtifact(const ArtifactGroup& artifactGroup); + + /** + * Utility: Parse the json from AIPC + * @param jsonArtifactList from APIC callback value + * @return Artifact Group Vector + */ + std::vector executeParseArtifactGroupFromJson(const std::string& jsonArtifactList); + +private: + const std::string m_workingDirectory; + + /// AuthDelegate that curlWrapper will use to get the Authentication Token + const std::shared_ptr m_authDelegate; + + /// Endpoint handler which is used to generate the proper DAVS request URL. + const std::shared_ptr m_davsEndpointHandler; + + /// Metric Recorder used for recording metrics if provided. + const std::shared_ptr m_metricRecorder; + + /// If set above 0, then this interval will be used to override the update polling interval dicated by the cloud TTL + const std::chrono::seconds m_forcedUpdateInterval; + + /// PowerResource used to acquire/release the wakelock + const std::shared_ptr m_powerResource; + + /// Map of Request UUID to Davs Handlers + std::unordered_map> m_handlers; + + alexaClientSDK::avsCommon::utils::threading::Executor m_executor; + + bool m_isDeviceIdle; + bool m_isConnected; +}; + +} // namespace davs +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKDAVSCLIENT_DAVSCLIENT_H_ diff --git a/capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsEndpointHandlerV3.h b/capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsEndpointHandlerV3.h new file mode 100644 index 0000000000..c520e97ce9 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsEndpointHandlerV3.h @@ -0,0 +1,74 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKDAVSCLIENT_DAVSENDPOINTHANDLERV3_H_ +#define ACSDKDAVSCLIENT_DAVSENDPOINTHANDLERV3_H_ + +#include "acsdkDavsClientInterfaces/DavsEndpointHandlerInterface.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davs { + +/** + * This class implements the DavsEndpointHandlerInterface and uses the following to construct a proper DAVS Url: + * 1. Segment ID: Required, opaque identifier provided by Amazon for AVS customers + * 2. DAVS Request: Required, to generate the path for the specific DAVS Artifact + * 3. Locale(s): Optional, can be used to specify which artifact to use in certain cases + */ +class DavsEndpointHandlerV3 : public davsInterfaces::DavsEndpointHandlerInterface { +public: + /** + * Creates a new DAVS Endpoint Handler for V3 API given Segment ID (and optionally locale(s)) + * + * @param segmentId REQUIRED, opaque identifier generated by Amazon for an AVS customer + * @param locale OPTIONAL, specifies the locale(s) used for the endpoint, multiple locales can be separated by "," + * @return a new DAVS Endpoint Handler for V3 upon success, nullptr otherwise. + */ + static std::shared_ptr create(const std::string& segmentId, const std::string& locale = ""); + + /** + * Destructor. + */ + ~DavsEndpointHandlerV3() override = default; + + /// @name DavsEndpointHandlerInterface Functions + /// @{ + std::string getDavsUrl(std::shared_ptr request) override; + /// @} + + /** + * Sets the locale for future DAVS requests + * + * @param newLocale new locale(s) to set (empty to avoid using this field) + * @note you can specify one locale (ie. en-US) or multiple locales separated by "," (ie. en-US,en-CA) + */ + void setLocale(std::string newLocale); + +private: + DavsEndpointHandlerV3(std::string segmentId, std::string locale); + +private: + /// Segment ID used for constructing the DAVS request URL + const std::string m_segmentId; + /// Optional locale which can be used to construct the DAVS request URL + std::string m_locale; +}; + +} // namespace davs +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKDAVSCLIENT_DAVSENDPOINTHANDLERV3_H_ diff --git a/capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsHandler.h b/capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsHandler.h new file mode 100644 index 0000000000..2a41e33949 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/include/acsdkDavsClient/DavsHandler.h @@ -0,0 +1,371 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKDAVSCLIENT_DAVSHANDLER_H_ +#define ACSDKDAVSCLIENT_DAVSHANDLER_H_ + +#include +#include +#include +#include + +#include +#include +#include + +#include "acsdkAssetsCommon/AmdMetricWrapper.h" +#include "acsdkAssetsCommon/CurlProgressCallbackInterface.h" +#include "acsdkAssetsInterfaces/VendableArtifact.h" +#include "acsdkDavsClientInterfaces/ArtifactHandlerInterface.h" +#include "acsdkDavsClientInterfaces/DavsEndpointHandlerInterface.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davs { + +class DavsHandler + : public common::CurlProgressCallbackInterface + , public std::enable_shared_from_this { +public: + /** + * Creates a Handler that will take care of the check and download requests from Davs. + * + * @param artifactRequest REQUIRED, a valid request containing information for the artifact to be downloaded. + * @param downloadCallback REQUIRED, a listener that will handle what to do with the artifact when its downloaded or + * failed. + * @param checkCallback REQUIRED, a listener that will handle checking if the artifact should be downloaded. + * @param baseBackOffTime REQUIRED, the starting backoff time we should wait after a first-time failure + * @param maxBackOffTime REQUIRED,the maximum value of back-off we can tolerate + * @param authDelegate REQUIRED, the Authentication Delegate to generate the authentication token + * @param davsEndpointHandler REQUIRED, the endpoint handler which is used to generate the proper DAVS request URL. + * @param powerResource OPTIONAL, if provided, then the PowerResource will be used to acquire the wakelock + * @param forcedUpdateInterval OPTIONAL, sets the update interval for all artifact to a specific value. This is to + * be use only for testing! your devices will be throttled if used in production! + * @return a smart pointer to Davs Handler. + */ + static std::shared_ptr create( + std::shared_ptr artifactRequest, + const std::shared_ptr& downloadRequester, + const std::shared_ptr& checkRequester, + std::string workingDirectory, + std::chrono::milliseconds baseBackOffTime, + std::chrono::milliseconds maxBackOffTime, + std::shared_ptr authDelegate, + std::shared_ptr davsEndpointHandler, + std::shared_ptr powerResource = nullptr, + std::chrono::seconds forcedUpdateInterval = std::chrono::seconds(0)); + /** + * Destructor to terminate the thread. + */ + ~DavsHandler() override; + + /** + * @return if the listeners are still alive. + */ + inline bool isRelevant() { + return !m_checkRequester.expired() && !m_downloadRequester.expired(); + } + + /** + * Performs a check and download operation to get and store the artifact. + * @param isUserInitiated whether download is initiated by user, throttle or not + */ + void requestAndDownload(bool isUserInitiated); + + /** + * Cancel the current download/request and clean up. + */ + void cancel(); + + /** + * Enables or disables checking for updates when the artifact's TTL is done. + * @param enable whether update is enabled when the artifact's TTL is done + */ + inline void enableUpdate(bool enable) { + std::lock_guard lock(m_eventMutex); + m_updateEnabled = enable; + handleUpdateLocked(); + } + + /** + * If an artifact enabled download to perform update + * @param None + */ + inline bool isUpdateEnabled() { + return m_updateEnabled; + } + + /*** + * Expose the inside ArtifactRequest containing type and key to DavsClient check then update + * @return ArtifactRequest + */ + inline std::shared_ptr getDavsRequest() { + return m_artifactRequest; + } + + /** + * If the first download needs to back off, set this. Otherwise its default is 0ms; + */ + inline void setFirstBackOff(std::chrono::milliseconds firstBackOffTime) { + m_firstBackOffTime = firstBackOffTime; + } + + /** + * Gets an exponentially growing time to backoff before another download + * @param prevBackOffTime the backoff time resulting in current failure + * @return + */ + std::chrono::milliseconds getBackOffTime(std::chrono::milliseconds prevBackOffTime); + + /** + * Takes an link and attempts to parse out the file name being provided. + * + * @param url to be parsed. + * @param defaultValue that will be returned if the parsing fails. + * @return filename if the parsing was successful, defaultValue otherwise. + */ + static std::string parseFileFromLink(const std::string& url, const std::string& defaultValue); + + bool isThrottled() const; + + void setThrottled(bool throttle); + + void setConnectionState(bool connected); + +private: + DavsHandler( + std::shared_ptr artifactRequest, + const std::shared_ptr& downloadRequester, + const std::shared_ptr& checkRequester, + std::string workingDirectory, + std::chrono::milliseconds baseBackOffTime, + std::chrono::milliseconds maxBackOffTime, + std::shared_ptr authDelegate, + std::shared_ptr davsEndpointHandler, + std::shared_ptr powerResource, + std::chrono::seconds forcedUpdateInterval); + + /** + * Main runner thread to handle the bulk of the logic. + * @param isUserInitiated whether download is initiated by user, throttle or not + */ + void runner(bool isUserInitiated); + + /** + * Performs the GetArtifact request to get the artifact metadata. + * + * @param artifact OUT, information retrieved from DAVS if the call is successful. + * @return Specific result code of the call. + */ + commonInterfaces::ResultCode checkArtifact(std::shared_ptr& artifact); + + /** + * Performs the file download request and handles writing to disk. + * + * @param artifact REQUIRED, artifact to download given s3url and expiry. + * @param isUserInitiated whether the download is an update from device or request from user + * @param path OUT, path where the file is downloaded. + * @return Specific result code of the call. + */ + commonInterfaces::ResultCode downloadArtifact( + const std::shared_ptr& artifact, + bool isUserInitiated, + std::string& path); + /** + * Inside a runner, starts checking attempts with retries and exists if all attempts fails or the check succeeds. + * + * @param artifact OUT, information retrieved from DAVS if the call is successful. + * @return Specific result code of the call. + */ + commonInterfaces::ResultCode checkWithRetryLoop(std::shared_ptr& artifact); + + /** + * Inside a runner, starts download attempts with retries and exists if all attempts fails or there's a success + * + * @param artifact the object to download + * @param isUserInitiated whether the download is an update from device or request from user + */ + void downloadWithRetryLoop( + const std::shared_ptr& artifact, + bool isUserInitiated); + + /** + * Waits for a given amount of time or until shutdown is set. If shutdown is set, then return false, otherwise + * return true. This also adjusts the given waitTime for the next time that it is called. + * + * @param waitTime time to wait, will be adjusted according to retry logic. + * @return if we should continue or not. + */ + bool waitForRetry(std::chrono::milliseconds& waitTime); + + /** + * Waits for a wifi network to be connected until a certain timeout. + * + * @return if the network is connected or not. + */ + bool waitForNetworkConnection(); + + /** + * Checks to see if we need to schedule an update or not. + */ + void handleUpdateLocked(); + + /** + * @return the temporary directory for downloading artifacts + */ + inline std::string getTmpParentDirectory() const { + return m_workingDirectory + "/" + m_artifactRequest->getSummary(); + } + + inline std::shared_ptr getChecker() { + auto checker = m_checkRequester.lock(); + if (checker == nullptr) { + ACSDK_WARN(alexaClientSDK::avsCommon::utils::logger::LogEntry("DavsHandler", "getChecker") + .m("Check requester is no longer available") + .d("request", m_artifactRequest->getSummary())); + s_metrics().addCounter(METRIC_PREFIX_ERROR("checkRequesterNotAvailable")); + return nullptr; + } + return checker; + } + + inline std::shared_ptr getDownloader() { + auto downloader = m_downloadRequester.lock(); + if (downloader == nullptr) { + ACSDK_WARN(alexaClientSDK::avsCommon::utils::logger::LogEntry("DavsHandler", "getDownloader") + .m("Download requester is no longer available") + .d("request", m_artifactRequest->getSummary())); + s_metrics().addCounter(METRIC_PREFIX_ERROR("downloadRequesterNotAvailable")); + return nullptr; + } + return downloader; + } + + inline void sendOnCheckFailure(commonInterfaces::ResultCode resultCode) { + auto checker = getChecker(); + if (checker == nullptr) { + return; + } + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry("DavsHandler", "sendOnCheckFailure") + .m("Check failed") + .d("request", m_artifactRequest->getSummary())); + s_metrics().addZeroCounter("downloadCheckSuccess").addCounter(METRIC_PREFIX_ERROR("downloadCheckFailed")); + checker->onCheckFailure(resultCode); + } + + inline bool sendCheckIfOkToDownload( + const std::shared_ptr& artifact, + size_t spaceNeeded) { + auto checker = getChecker(); + if (checker == nullptr) { + return false; + } + s_metrics().addCounter("downloadCheckSuccess"); + if (!checker->checkIfOkToDownload(artifact, spaceNeeded)) { + ACSDK_WARN(alexaClientSDK::avsCommon::utils::logger::LogEntry("DavsHandler", "sendCheckIfOkToDownload") + .m("Requester rejected download") + .d("request", m_artifactRequest->getSummary())); + s_metrics().addCounter("downloadRejected"); + return false; + } + return true; + } + + inline bool sendOnStartAndCheckIfAvailable() { + auto downloader = getDownloader(); + if (downloader == nullptr) { + return false; + } + ACSDK_INFO(alexaClientSDK::avsCommon::utils::logger::LogEntry("DavsHandler", "sendOnStartAndCheckIfAvailable") + .m("Download started") + .d("request", m_artifactRequest->getSummary())); + downloader->onStart(); + return true; + } + + inline void sendOnDownloadFailure(commonInterfaces::ResultCode resultCode) { + auto downloader = getDownloader(); + if (downloader == nullptr) { + return; + } + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry("DavsHandler", "sendOnDownloadFailure") + .m("Download utterly failed") + .d("request", m_artifactRequest->getSummary())); + s_metrics().addZeroCounter("downloadSuccess").addCounter(METRIC_PREFIX_ERROR("downloadFailed")); + downloader->onDownloadFailure(resultCode); + } + + inline void sendOnArtifactDownloaded( + const std::shared_ptr& artifact, + const std::string& path) { + auto downloader = m_downloadRequester.lock(); + if (downloader == nullptr) { + return; + } + ACSDK_INFO(alexaClientSDK::avsCommon::utils::logger::LogEntry("DavsHandler", "sendOnArtifactDownloaded") + .m("Download succeeded") + .d("request", m_artifactRequest->getSummary())); + s_metrics().addCounter("downloadSuccess"); + downloader->onArtifactDownloaded(artifact, path); + } + + /// @name CurlProgressCallbackInterface Functions + /// @{ + bool onProgressUpdate(long dlTotal, long dlNow, long ulTotal, long ulNow) override; + /// @} +private: + static const std::function s_metrics; + + const std::shared_ptr m_artifactRequest; + const std::weak_ptr m_downloadRequester; + const std::weak_ptr m_checkRequester; + const std::string m_workingDirectory; + + // controls retry logic and how much to backoff + const std::chrono::milliseconds m_baseBackOffTime; + const std::chrono::milliseconds m_maxBackOffTime; + /// AuthDelegate that curlWrapper will use to get the Authentication Token + const std::shared_ptr m_authDelegate; + /// Endpoint handler which is used to generate the proper DAVS request URL. + const std::shared_ptr m_davsEndpointHandler; + /// PowerResource used to acquire/release the wakelock + std::shared_ptr m_powerResource; + /// If set above 0, then this interval will be used to override the update polling interval dicated by the cloud TTL + const std::chrono::seconds m_forcedUpdateInterval; + std::chrono::milliseconds m_firstBackOffTime; + + // whether cancel request has been issued during DAVS download + std::mutex m_eventMutex; + std::condition_variable m_eventTrigger; + bool m_shutdown; + std::atomic_flag m_running; + std::future m_taskFuture; + alexaClientSDK::avsCommon::utils::timing::Timer m_scheduler; + + bool m_updateEnabled; + commonInterfaces::VendableArtifact::TimeEpoch m_artifactExpiry; + + bool m_throttled; + bool m_networkConnected; + + /// whether the artifact needs to be unpacked after download + bool m_unpack; +}; + +} // namespace davs +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKDAVSCLIENT_DAVSHANDLER_H_ diff --git a/capabilities/DavsClient/acsdkDavsClient/src/CMakeLists.txt b/capabilities/DavsClient/acsdkDavsClient/src/CMakeLists.txt new file mode 100644 index 0000000000..b9a0345e07 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/src/CMakeLists.txt @@ -0,0 +1,29 @@ +set(acsdkDavsClient_SOURCES + DavsClient.cpp + DavsEndpointHandlerV3.cpp + DavsHandler.cpp + ) +set(acsdkDavsClient_INCLUDES + ${acsdkDavsClient_SOURCE_DIR}/include + ) +set(acsdkDavsClient_LIBRARIES + AVSCommon + acsdkDavsClientInterfaces + acsdkAssetsInterfaces + acsdkAssetsCommon + ) + +# Setup the Library first +add_library(acsdkDavsClient ${acsdkDavsClient_SOURCES}) +target_include_directories(acsdkDavsClient PUBLIC ${acsdkDavsClient_INCLUDES}) +target_link_libraries(acsdkDavsClient ${acsdkDavsClient_LIBRARIES}) +target_compile_definitions(acsdkDavsClient PRIVATE ACSDK_LOG_MODULE=DavsClient) + +# install target +asdk_install() + +# Setup the Testing library +add_library(acsdkDavsClientForTesting ${acsdkDavsClient_SOURCES}) +target_include_directories(acsdkDavsClientForTesting PUBLIC ${acsdkDavsClient_INCLUDES}) +target_link_libraries(acsdkDavsClientForTesting ${acsdkDavsClient_LIBRARIES}) +target_compile_definitions(acsdkDavsClientForTesting PUBLIC UNIT_TEST=1 PRIVATE ACSDK_LOG_MODULE=DavsClientMock) diff --git a/capabilities/DavsClient/acsdkDavsClient/src/DavsClient.cpp b/capabilities/DavsClient/acsdkDavsClient/src/DavsClient.cpp new file mode 100644 index 0000000000..c0629c9a58 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/src/DavsClient.cpp @@ -0,0 +1,291 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkDavsClient/DavsClient.h" + +#include +#include +#include +#include + +#include +#include "acsdkAssetsCommon/AmdMetricWrapper.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davs { + +using namespace std; +using namespace chrono; +using namespace common; +using namespace commonInterfaces; +using namespace davsInterfaces; +using namespace alexaClientSDK::avsCommon::utils; +using namespace alexaClientSDK::avsCommon::sdkInterfaces; +using namespace rapidjson; +using namespace alexaClientSDK::avsCommon::utils::json::jsonUtils; + +#if UNIT_TEST == 1 +// For tests, because we don't want to wait hours for it to finish... +static constexpr auto BASE_BACKOFF_VALUE_MS = milliseconds(10); +static constexpr auto MAX_BACKOFF_VALUE_MS = seconds(1); +#else +static constexpr auto BASE_BACKOFF_VALUE_MS = milliseconds(500); +static constexpr auto MAX_BACKOFF_VALUE_MS = minutes(60); +#endif + +static const auto s_metrics = AmdMetricsWrapper::creator("DavsClient"); + +static constexpr auto JSON_ARTIFACT_KEY_SYMBOL = "key"; +static constexpr auto JSON_ARTIFACT_TYPE_SYMBOL = "type"; +static constexpr auto JSON_ARTIFACT_LIST_SYMBOL = "artifactList"; +/// String to identify log entries originating from this file. +static const string TAG{"DavsClient"}; + +/** + * 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) + +shared_ptr DavsClient::create( + string workingDirectory, + shared_ptr authDelegate, + shared_ptr wifiMonitor, + shared_ptr davsEndpointHandler, + shared_ptr metricRecorder, + seconds forcedUpdateInterval) { + if (workingDirectory.empty()) { + ACSDK_CRITICAL(LX("create").m("Working directory is empty")); + return nullptr; + } + if (authDelegate == nullptr) { + ACSDK_CRITICAL(LX("create").m("Auth Delegate is null")); + return nullptr; + } + if (wifiMonitor == nullptr) { + ACSDK_CRITICAL(LX("create").m("Wifi Monitor is null")); + return nullptr; + } + if (davsEndpointHandler == nullptr) { + ACSDK_CRITICAL(LX("create").m("DAVS Endpoint Handler is null")); + return nullptr; + } + + filesystem::removeAll(workingDirectory); + if (!filesystem::makeDirectory(workingDirectory)) { + ACSDK_CRITICAL(LX("create").m("Failed to create working directory")); + return nullptr; + } + + auto client = shared_ptr(new DavsClient( + move(workingDirectory), + move(authDelegate), + move(davsEndpointHandler), + move(metricRecorder), + max(seconds(0), forcedUpdateInterval))); + wifiMonitor->addInternetConnectionObserver(client); + + return client; +} + +DavsClient::DavsClient( + string workingDirectory, + shared_ptr authDelegate, + shared_ptr davsEndpointHandler, + shared_ptr metricRecorder, + seconds forcedUpdateInterval) : + m_workingDirectory(move(workingDirectory)), + m_authDelegate(move(authDelegate)), + m_davsEndpointHandler(move(davsEndpointHandler)), + m_metricRecorder(move(metricRecorder)), + m_forcedUpdateInterval(forcedUpdateInterval), + m_powerResource(power::PowerMonitor::getInstance()->createLocalPowerResource(TAG)), + m_isDeviceIdle(false), + m_isConnected(false) { + AmdMetricsWrapper::setStaticRecorder(m_metricRecorder); +} + +string DavsClient::handleRequest( + shared_ptr artifactRequest, + shared_ptr downloadCallback, + shared_ptr checkCallback, + bool enableAutoUpdate, + bool downloadImmediately) { + auto handler = DavsHandler::create( + artifactRequest, + downloadCallback, + checkCallback, + m_workingDirectory, + BASE_BACKOFF_VALUE_MS, + MAX_BACKOFF_VALUE_MS, + m_authDelegate, + m_davsEndpointHandler, + m_powerResource, + m_forcedUpdateInterval); + if (handler == nullptr) { + ACSDK_ERROR(LX("handleRequest").m("Failed to create a Davs Handler due to invalid parameters")); + return ""; + } + + auto requestHash = artifactRequest->getSummary(); + ACSDK_INFO(LX("handleRequest").m("Registering artifact").d("artifact", requestHash)); + m_executor.submit([this, handler, requestHash, enableAutoUpdate, downloadImmediately] { + s_metrics().addCounter("requestAndDownload-userInitiated"); + + auto existingHandler = m_handlers.find(requestHash); + if (existingHandler != m_handlers.end() && existingHandler->second->isRelevant()) { + // This is a user-initiated, disable throttle for this request + if (downloadImmediately) { + existingHandler->second->requestAndDownload(true); + } + existingHandler->second->enableUpdate(enableAutoUpdate); + return; + } + + handler->setConnectionState(m_isConnected); + if (downloadImmediately) { + handler->requestAndDownload(true); + } + handler->enableUpdate(enableAutoUpdate); + m_handlers[requestHash] = handler; + }); + + return requestHash; +} + +string DavsClient::registerArtifact( + shared_ptr artifactRequest, + shared_ptr downloadCallback, + shared_ptr checkCallback, + bool downloadImmediately) { + return handleRequest(move(artifactRequest), move(downloadCallback), move(checkCallback), true, downloadImmediately); +} + +void DavsClient::deregisterArtifact(const string& requestUUID) { + ACSDK_INFO(LX("deregisterArtifact").m("Deregistering artifact").d("artifact", requestUUID)); + m_executor.submit([this, requestUUID] { + auto handler = m_handlers.find(requestUUID); + if (handler == m_handlers.end()) { + return; + } + handler->second->cancel(); + m_handlers.erase(handler); + }); +} + +string DavsClient::downloadOnce( + shared_ptr artifactRequest, + shared_ptr downloadCallback, + shared_ptr checkCallback) { + return handleRequest(move(artifactRequest), move(downloadCallback), move(checkCallback), false, true); +} + +void DavsClient::enableAutoUpdate(const string& requestUUID, bool enable) { + m_executor.submit([this, requestUUID, enable] { + auto handler = m_handlers.find(requestUUID); + if (handler != m_handlers.end() && handler->second->isRelevant()) { + handler->second->enableUpdate(enable); + } + }); +} + +bool DavsClient::getIdleState() const { + return m_isDeviceIdle; +} + +void DavsClient::setIdleState(const bool idleState) { + m_executor.submit([this, idleState] { + m_isDeviceIdle = idleState; + + for (const auto& pair : m_handlers) { + if (pair.second->isRelevant()) { + pair.second->setThrottled(!m_isDeviceIdle); + } + } + }); +} +void DavsClient::onConnectionStatusChanged(bool connected) { + m_executor.submit([this, connected] { + m_isConnected = connected; + + for (const auto& pair : m_handlers) { + if (pair.second->isRelevant()) { + pair.second->setConnectionState(m_isConnected); + } + } + }); +} +vector DavsClient::executeParseArtifactGroupFromJson(const string& jsonArtifactList) { + Document document; + vector artifactVector; + + if (!parseJSON(jsonArtifactList, &document)) { + ACSDK_ERROR(LX("parseArtifactInfoFromJsonNotificationFailed").d("jsonArtifactList", jsonArtifactList)); + return artifactVector; + } + + vector> elements; + if (!retrieveArrayOfStringMapFromArray(document, JSON_ARTIFACT_LIST_SYMBOL, elements)) { + return artifactVector; + } + + for (auto element : elements) { + string typeValue{element[JSON_ARTIFACT_TYPE_SYMBOL]}; + string keyValue{element[JSON_ARTIFACT_KEY_SYMBOL]}; + if (typeValue.empty() || keyValue.empty()) { + ACSDK_ERROR(LX("parseArtifactInfoFromJsonNotificationFailed").d("reason", "emptyMemberFound")); + // Clear out everything and return an empty vector as we commit as a whole + artifactVector.clear(); + return artifactVector; + } + artifactVector.emplace_back(ArtifactGroup{typeValue, keyValue}); + } + return artifactVector; +} +void DavsClient::checkAndUpdateArtifactGroupFromJson(const string& jsonArtifactList) { + m_executor.submit([this, jsonArtifactList] { + auto artifactVector = executeParseArtifactGroupFromJson(jsonArtifactList); + checkAndUpdateArtifactGroupVector(artifactVector); + }); +} +void DavsClient::checkAndUpdateArtifactGroupVector(const std::vector& artifactVector) { + for (auto& it : artifactVector) { + executeUpdateRegisteredArtifact(it); + } +} +void DavsClient::executeUpdateRegisteredArtifact(const ArtifactGroup& artifactGroup) { + bool artifactUpdated = false; + for (const auto& pair : m_handlers) { + auto& currentHandler = pair.second; + if (currentHandler->isUpdateEnabled() && currentHandler->isRelevant() && + currentHandler->getDavsRequest()->getType() == artifactGroup.type && + currentHandler->getDavsRequest()->getKey() == artifactGroup.key) { + currentHandler->requestAndDownload(false); + artifactUpdated = true; + } + } + + // If nothing updated, there should be a notification + if (!artifactUpdated) { + ACSDK_INFO(LX("executeUpdateRegisteredArtifact") + .m("Could not find anything to update artifact group") + .d("type", artifactGroup.type) + .d("key", artifactGroup.key)); + } +} +} // namespace davs +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkDavsClient/src/DavsEndpointHandlerV3.cpp b/capabilities/DavsClient/acsdkDavsClient/src/DavsEndpointHandlerV3.cpp new file mode 100644 index 0000000000..20524ff4c8 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/src/DavsEndpointHandlerV3.cpp @@ -0,0 +1,117 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkDavsClient/DavsEndpointHandlerV3.h" + +#include +#include +#include + +#include "acsdkAssetsCommon/Base64Url.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davs { + +using namespace std; +using namespace commonInterfaces; +using namespace common; +using namespace avsCommon::utils::json; +using namespace avsCommon::utils::error; + +static const string TAG{"DavsEndpointHandlerV3"}; +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// Generate encoded filters using filter map from a request +static SuccessResult generatedEncodedFilters(const DavsRequest::FilterMap& filtersMap) { + if (filtersMap.empty()) { + return SuccessResult::success(""); + } + + JsonGenerator generator; + for (const auto& filter : filtersMap) { + generator.addStringArray(filter.first, filter.second); + } + + string filtersEncoded; + auto filtersJson = generator.toString(); + + if (!Base64Url::encode(filtersJson, filtersEncoded)) { + ACSDK_ERROR(LX("generatedEncodedFilters").m("Could not encode request")); + return SuccessResult::failure(); + } + return SuccessResult::success(filtersEncoded); +} + +/// get the proper endpoint for davs per region +static string getUrlEndpoint(Region endpoint) { + switch (endpoint) { + case Region::NA: + return "api.amazonalexa.com"; + case Region::EU: + return "api.eu.amazonalexa.com"; + case Region::FE: + return "api.fe.amazonalexa.com"; + } + return ""; +} + +std::shared_ptr DavsEndpointHandlerV3::create( + const string& segmentId, + const std::string& locale) { + if (segmentId.empty()) { + return nullptr; + } + + return shared_ptr(new DavsEndpointHandlerV3(segmentId, locale)); +} + +DavsEndpointHandlerV3::DavsEndpointHandlerV3(std::string segmentId, std::string locale) : + m_segmentId(move(segmentId)), + m_locale(move(locale)) { +} + +string DavsEndpointHandlerV3::getDavsUrl(shared_ptr request) { + if (request == nullptr) { + ACSDK_ERROR(LX("getDavsUrl").m("Null DavsRequest")); + return ""; + } + + auto encodedFilters = generatedEncodedFilters(request->getFilters()); + if (!encodedFilters.isSucceeded()) { + ACSDK_ERROR(LX("getDavsUrl").m("Failed to generate encoded filters")); + return ""; + } + + auto requestUrl = "https://" + getUrlEndpoint(request->getRegion()) + "/v3/segments/" + m_segmentId + + "/artifacts/" + request->getType() + "/" + request->getKey(); + if (!m_locale.empty()) { + requestUrl += "?locale=" + m_locale; + } + + if (!encodedFilters.value().empty()) { + requestUrl += m_locale.empty() ? "?" : "&"; + requestUrl += "encodedFilters=" + encodedFilters.value(); + } + + return requestUrl; +} +void DavsEndpointHandlerV3::setLocale(std::string newLocale) { + m_locale = move(newLocale); +} + +} // namespace davs +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkDavsClient/src/DavsHandler.cpp b/capabilities/DavsClient/acsdkDavsClient/src/DavsHandler.cpp new file mode 100644 index 0000000000..7b9ed3e65c --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/src/DavsHandler.cpp @@ -0,0 +1,571 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkDavsClient/DavsHandler.h" + +#include +#include +#include +#include + +#include + +#include "acsdkAssetsCommon/Base64Url.h" +#include "acsdkAssetsCommon/CurlWrapper.h" +#include "acsdkAssetsCommon/JitterUtil.h" +#include "acsdkAssetsCommon/ResponseSink.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davs { + +using namespace std; +using namespace chrono; +using namespace common; +using namespace commonInterfaces; +using namespace davsInterfaces; +using namespace alexaClientSDK::avsCommon::utils; +using namespace alexaClientSDK::avsCommon::utils::timing; +using namespace alexaClientSDK::avsCommon::utils::error; +using namespace alexaClientSDK::avsCommon::utils::power; +using namespace alexaClientSDK::avsCommon::utils::json; +using namespace alexaClientSDK::avsCommon::sdkInterfaces; + +static const float JITTER_FACTOR = 0.3; + +#if UNIT_TEST == 1 +// For tests, because we don't want to wait hours for it to finish... +static constexpr auto MIN_UPDATE_INTERVAL = milliseconds(10); +static constexpr auto MAX_CHECK_RETRY = 2; +static constexpr auto MAX_DOWNLOAD_RETRY = 2; +static constexpr auto NETWORK_CONNECTION_WAIT_TIME = milliseconds(20); +#else +static constexpr auto MIN_UPDATE_INTERVAL = minutes(15); +static constexpr auto MAX_CHECK_RETRY = 4; +static constexpr auto MAX_DOWNLOAD_RETRY = 8; +static constexpr auto NETWORK_CONNECTION_WAIT_TIME = seconds(20); +#endif + +static constexpr auto MS_IN_SEC = 1000; +static constexpr auto BYTES_IN_KB = 1024; + +/// String to identify log entries originating from this file. +static const string TAG{"DavsHandler"}; + +/** + * 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 function DavsHandler::s_metrics = AmdMetricsWrapper::creator("davsHandler"); + +shared_ptr DavsHandler::create( + shared_ptr artifactRequest, + const shared_ptr& downloadRequester, + const shared_ptr& checkRequester, + string workingDirectory, + milliseconds baseBackOffTime, + milliseconds maxBackOffTime, + shared_ptr authDelegate, + shared_ptr davsEndpointHandler, + shared_ptr powerResource, + seconds forcedUpdateInterval) { + if (artifactRequest == nullptr) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullArtifactRequest")); + ACSDK_ERROR(LX("create").m("Null Artifact Request")); + return nullptr; + } + + if (downloadRequester == nullptr) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullDownloadRequestor")); + ACSDK_ERROR(LX("create").m("Null Download Requester")); + return nullptr; + } + + if (checkRequester == nullptr) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("nullCheckRequester")); + ACSDK_ERROR(LX("create").m("Null Check Requester")); + return nullptr; + } + if (workingDirectory.empty()) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("invalidWorkingDirectory")); + ACSDK_ERROR(LX("create").m("Invalid working directory")); + return nullptr; + } + + if (baseBackOffTime <= milliseconds::zero()) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("invalidBackoffTime")); + ACSDK_ERROR(LX("create").m("Invalid base backoff time")); + return nullptr; + } + + if (maxBackOffTime <= milliseconds::zero() || maxBackOffTime < baseBackOffTime) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("invalidBackoffTime")); + ACSDK_ERROR(LX("create").m("Invalid max backoff time")); + return nullptr; + } + + if (authDelegate == nullptr) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("invalidAuthDelegate")); + ACSDK_ERROR(LX("create").m("Invalid Auth Delegate")); + return nullptr; + } + + if (davsEndpointHandler == nullptr) { + s_metrics().addCounter(METRIC_PREFIX_ERROR_CREATE("invalidDavsEndpointHandler")); + ACSDK_ERROR(LX("create").m("Invalid DAVS Endpoint Handler")); + return nullptr; + } + + return unique_ptr(new DavsHandler( + move(artifactRequest), + downloadRequester, + checkRequester, + move(workingDirectory), + baseBackOffTime, + maxBackOffTime, + move(authDelegate), + move(davsEndpointHandler), + move(powerResource), + max(seconds::zero(), forcedUpdateInterval))); +} + +DavsHandler::DavsHandler( + shared_ptr artifactRequest, + const shared_ptr& downloadRequester, + const shared_ptr& checkRequester, + string workingDirectory, + milliseconds baseBackOffTime, + milliseconds maxBackOffTime, + shared_ptr authDelegate, + shared_ptr davsEndpointHandler, + shared_ptr powerResource, + seconds forcedUpdateInterval) : + m_artifactRequest(move(artifactRequest)), + m_downloadRequester(downloadRequester), + m_checkRequester(checkRequester), + m_workingDirectory(move(workingDirectory)), + m_baseBackOffTime(baseBackOffTime), + m_maxBackOffTime(maxBackOffTime), + m_authDelegate(move(authDelegate)), + m_davsEndpointHandler(move(davsEndpointHandler)), + m_powerResource(move(powerResource)), + m_forcedUpdateInterval(forcedUpdateInterval), + m_firstBackOffTime(milliseconds::zero()), + m_shutdown(false), + m_updateEnabled(false), + m_throttled(false), + m_networkConnected(false), + m_unpack(this->m_artifactRequest->needsUnpacking()) { + m_running.clear(); +} + +DavsHandler::~DavsHandler() { + ACSDK_DEBUG(LX("~DavsHandler").m("Cancelling current request")); + cancel(); +} + +void DavsHandler::requestAndDownload(bool isUserInitiated) { + if (!isRelevant()) { + ACSDK_WARN(LX("requestAndDownload").m("Requesting a download to a Davs Handler that's no longer relevant")); + return; + } + + s_metrics().addCounter("requestAndDownload-throttled", isThrottled() ? 1 : 0); + s_metrics().addCounter("requestAndDownload"); + + lock_guard lock(m_eventMutex); + if (!m_running.test_and_set()) { + ACSDK_DEBUG(LX("requestAndDownload") + .m("Launching new check/download thread") + .d("request", m_artifactRequest->getSummary())); + m_taskFuture = async(launch::async, &DavsHandler::runner, this, isUserInitiated); + m_shutdown = false; + } +} + +void DavsHandler::handleUpdateLocked() { + if (!m_updateEnabled) { + ACSDK_DEBUG(LX("handleUpdateLocked") + .m("Removing scheduled update task") + .d("request", m_artifactRequest->getSummary())); + m_scheduler.stop(); + return; + } + + milliseconds nextChk = m_forcedUpdateInterval > seconds::zero() + ? m_forcedUpdateInterval + : max(duration_cast(MIN_UPDATE_INTERVAL), + duration_cast(m_artifactExpiry - system_clock::now())); + auto nextJitteredCheck = jitterUtil::jitter(nextChk, JITTER_FACTOR); + ACSDK_DEBUG(LX("handleUpdateLocked") + .m("Scheduling another check") + .d("request", m_artifactRequest->getSummary()) + .d("jitteredIntervalMS", nextJitteredCheck.count()) + .d("baseMS", nextChk.count())); + m_scheduler.stop(); + m_scheduler.start(nextJitteredCheck, Timer::PeriodType::RELATIVE, Timer::getForever(), [this]() { + this->requestAndDownload(false); + }); +} + +string DavsHandler::parseFileFromLink(const string& url, const string& defaultValue) { + // handle s3 links + if (url.find("amazonaws.com") != string::npos) { + auto lastSlash = url.find_last_of('/'); + if (lastSlash != string::npos) { + auto firstQuestion = url.find('?', lastSlash); + auto size = (firstQuestion != string::npos) ? firstQuestion - lastSlash - 1 : string::npos; + auto result = url.substr(lastSlash + 1, size); + return result.empty() ? defaultValue : result; + } + } + + ACSDK_WARN(LX("parseFileFromLink").m("Couldn't parse a filename from the given url")); + s_metrics().addCounter(METRIC_PREFIX_ERROR("parseFileFromLink")).addString("url", url); + return defaultValue; +} + +milliseconds DavsHandler::getBackOffTime(milliseconds prevBackOffTime) { + // set the sleep to base backoff for first retry + if (prevBackOffTime < m_baseBackOffTime) { + return m_baseBackOffTime; + } else if (prevBackOffTime >= m_maxBackOffTime) { + return m_maxBackOffTime; + } else { + return jitterUtil::expJitter(prevBackOffTime, JITTER_FACTOR); + } +} + +void DavsHandler::runner(bool isUserInitiated) { + WakeGuard guard(m_powerResource); + FinallyGuard afterRun([this]() { + lock_guard lock(m_eventMutex); + handleUpdateLocked(); + m_running.clear(); + filesystem::removeAll(getTmpParentDirectory()); + }); + shared_ptr artifact; + auto checkResult = checkWithRetryLoop(artifact); + + if (checkResult != ResultCode::SUCCESS || artifact == nullptr) { + sendOnCheckFailure(checkResult); + return; + } + + m_artifactExpiry = artifact->getArtifactExpiry(); + // TODO this is insufficient when handling multiple requests + auto availableSpace = filesystem::availableSpace(m_workingDirectory); + auto artifactSize = artifact->getArtifactSizeBytes(); + if (m_unpack) { + // Attempt to free up 1.5x the compressed file to ensure that we have enough space for the data. + artifactSize *= 3 / 2; + } + auto spaceNeeded = availableSpace > artifactSize ? 0 : artifactSize - availableSpace; + + if (!sendCheckIfOkToDownload(artifact, spaceNeeded)) { + return; + } + // So we don't call download after multipart artifact is downloaded + if (artifact->isMultipart()) { + auto parentDirectory = getTmpParentDirectory(); + auto path = parentDirectory + "/" + artifact->getId(); + sendOnArtifactDownloaded(artifact, path); + return; + } + downloadWithRetryLoop(artifact, isUserInitiated); +} + +bool DavsHandler::waitForRetry(chrono::milliseconds& waitTime) { + unique_lock lock(m_eventMutex); + m_eventTrigger.wait_for(lock, waitTime, [this] { return m_shutdown; }); + if (m_shutdown) { + return false; + } + + waitTime = getBackOffTime(waitTime); + return true; +} + +bool DavsHandler::waitForNetworkConnection() { + unique_lock lock(m_eventMutex); + return m_eventTrigger.wait_for(lock, NETWORK_CONNECTION_WAIT_TIME, [this] { return m_networkConnected; }); +} + +ResultCode DavsHandler::checkWithRetryLoop(shared_ptr& artifact) { + auto result = ResultCode::CATASTROPHIC_FAILURE; + auto timeToNextAttempt = m_firstBackOffTime; + auto summary = m_artifactRequest->getSummary(); + + if (!waitForNetworkConnection()) { + ACSDK_WARN(LX("checkWithRetryLoop").m("No network connection, will attempt regardless")); + } + + for (auto retryAttempt = 0; retryAttempt <= MAX_CHECK_RETRY && getChecker() != nullptr; ++retryAttempt) { + // sleep with backoff starting at 0 and grow exponentially + if (!waitForRetry(timeToNextAttempt)) { + ACSDK_INFO(LX("checkWithRetryLoop").m("Cancelling check")); + return result; + } + + result = checkArtifact(artifact); + + if (result == ResultCode::SUCCESS) { + ACSDK_DEBUG(LX("checkWithRetryLoop").m("Received a valid response").d("request", summary)); + s_metrics().addCounter("downloadCheckAttempt" + to_string(retryAttempt)); + return result; + } + + if (result == ResultCode::NO_ARTIFACT_FOUND || result == ResultCode::FORBIDDEN) { + ACSDK_ERROR(LX("checkWithRetryLoop").m("Didn't find artifact in DAVS").d("artifact", summary)); + return result; + } + + if (result == ResultCode::CATASTROPHIC_FAILURE) { + ACSDK_ERROR(LX("checkWithRetryLoop").m("Check utterly failed").d("request", summary)); + return result; + } + + ACSDK_WARN(LX("checkWithRetryLoop").m("Check failed").d("artifact", summary).d("code", result)); + s_metrics().addCounter(METRIC_PREFIX_ERROR("checkFailedWithRetry")); + } + + ACSDK_ERROR(LX("checkWithRetryLoop").m("Check failed for too long, giving up").d("artifact", summary)); + + return result; +} + +void DavsHandler::downloadWithRetryLoop(const shared_ptr& artifact, bool isUserInitiated) { + string path; + auto timeToNextAttempt = m_firstBackOffTime; + ResultCode downloadResult = ResultCode::CATASTROPHIC_FAILURE; + FinallyGuard removeTmpPath([&path, this] { + filesystem::removeAll(path); + filesystem::removeAll(getTmpParentDirectory()); + }); + + if (!sendOnStartAndCheckIfAvailable()) { + return; + } + + auto startTime = steady_clock::now(); + + // Download has started, while running in a thread, will retry on failed attempts with back-off sleep + for (auto retryAttempt = 0; retryAttempt <= MAX_DOWNLOAD_RETRY && getDownloader() != nullptr; ++retryAttempt) { + // sleep with backoff starting at 0 and grow exponentially + if (!waitForRetry(timeToNextAttempt)) { + ACSDK_INFO(LX("downloadWithRetryLoop").m("Cancelling download")); + return; + } + + downloadResult = downloadArtifact(artifact, isUserInitiated, path); + + if (downloadResult == ResultCode::SUCCESS) { + sendOnArtifactDownloaded(artifact, path); + auto duration = max(milliseconds(1), duration_cast(steady_clock::now() - startTime)); + auto rate = artifact->getArtifactSizeBytes() / BYTES_IN_KB * MS_IN_SEC / duration.count(); + s_metrics() + .addCounter("downloadAttempt" + to_string(retryAttempt)) + .addCounter("downloadRateKBps", static_cast(rate)) + .addTimer("downloadTime", duration); + return; + } + + if (downloadResult == ResultCode::CATASTROPHIC_FAILURE) { + sendOnDownloadFailure(downloadResult); + return; + } + + // Retry the download on all network failures + ACSDK_WARN(LX("downloadWithRetryLoop") + .m("Check failed") + .d("request", m_artifactRequest->getSummary()) + .d("code", static_cast(downloadResult))); + + s_metrics().addCounter(METRIC_PREFIX_ERROR("downloadFailedWithRetry")); + } + + ACSDK_ERROR(LX("downloadWithRetryLoop") + .m("Download failed for too long, giving up") + .d("request", m_artifactRequest->getSummary())); + + sendOnDownloadFailure(downloadResult); +} + +void DavsHandler::cancel() { + ACSDK_INFO(LX("cancel").m("DavsHandler: cancel requested")); + unique_lock lock(m_eventMutex); + m_shutdown = true; + lock.unlock(); + m_eventTrigger.notify_all(); + + if (m_taskFuture.valid()) { + m_taskFuture.wait(); + } +} + +static inline bool isValidMimeType(const string& contentType) { + ACSDK_DEBUG(LX("isValidMimeType").d("Content-Type", contentType)); + + return contentType.find("application/json") != string::npos || + contentType.find("application/octet-stream") != string::npos || + contentType.find("multipart/mixed") != string::npos; +} +static inline bool isMultpart(const string& contentType) { + return contentType.find("multipart/mixed") != string::npos; +} + +ResultCode DavsHandler::checkArtifact(shared_ptr& artifact) { + stringstream response; + string contentType; + + auto wrapper = CurlWrapper::create(false, m_authDelegate); + if (wrapper == nullptr) { + s_metrics().addCounter(METRIC_PREFIX_ERROR("checkArtifactFailed")); + ACSDK_ERROR(LX("checkArtifact").m("Can't create CurlWrapper")); + return ResultCode::ILLEGAL_ARGUMENT; + } + auto requestUrl = m_davsEndpointHandler->getDavsUrl(m_artifactRequest); + if (requestUrl.empty()) { + s_metrics().addCounter(METRIC_PREFIX_ERROR("checkArtifactFailed")); + ACSDK_ERROR(LX("checkArtifact").m("Failed to generate a URL request")); + return ResultCode::ILLEGAL_ARGUMENT; + } + auto headerResult = wrapper->getHeadersAuthorized(requestUrl); + string headerString; + if (headerResult.status() == ResultCode::SUCCESS) { + headerString = headerResult.value(); + ACSDK_DEBUG(LX("checkArtifact").d("headerResponse", headerString)); + } + contentType = CurlWrapper::getValueFromHeaders(headerString, "Content-Type"); + if (!headerString.empty() && !isValidMimeType(contentType)) { + s_metrics().addCounter(METRIC_PREFIX_ERROR("checkArtifactFailed")); + ACSDK_ERROR(LX("checkArtifact").m("Got back unhandled MIME Type from DAVS").d("content-type", contentType)); + return ResultCode::UNHANDLED_MIME_TYPE; + } + shared_ptr responseArtifact; + if (headerString.empty() || !isMultpart(contentType)) { + auto resultCode = wrapper->get(requestUrl, response, shared_from_this()); + if (resultCode != ResultCode::SUCCESS) { + s_metrics().addCounter(METRIC_PREFIX_ERROR("checkArtifactFailed")); + ACSDK_ERROR(LX("checkArtifact").m("Failed to get response from DAVS")); + + return resultCode; + } + responseArtifact = VendableArtifact::create(m_artifactRequest, response.str()); + } else { + shared_ptr sink = make_shared(m_artifactRequest, getTmpParentDirectory()); + auto resultCode = wrapper->getAndDownloadMultipart(requestUrl, sink, shared_from_this()); + if (resultCode != ResultCode::SUCCESS) { + s_metrics().addCounter(METRIC_PREFIX_ERROR("checkArtifactFailed")); + ACSDK_ERROR(LX("checkArtifact").m("Failed to get response from DAVS")); + + return resultCode; + } + responseArtifact = sink->getArtifact(); + } + if (responseArtifact == nullptr) { + s_metrics().addCounter(METRIC_PREFIX_ERROR("checkArtifactFailed")); + ACSDK_ERROR(LX("checkArtifact").m("Failed to create Vendable Artifact")); + + return ResultCode::CATASTROPHIC_FAILURE; + } + + artifact = move(responseArtifact); + return ResultCode::SUCCESS; +} + +ResultCode DavsHandler::downloadArtifact( + const shared_ptr& artifact, + bool isUserInitiated, + string& path) { + if (artifact == nullptr) { + return ResultCode::CATASTROPHIC_FAILURE; + } + + auto currentTime = chrono::system_clock::now(); + if (artifact->getUrlExpiry() < currentTime) { + s_metrics().addCounter(METRIC_PREFIX_ERROR("invalidUrlExpiryTime")); + ACSDK_ERROR(LX("downloadArtifact") + .m("Invalid URL expiry time, Expiry time set before current time") + .d("expiry time", artifact->getUrlExpiry().time_since_epoch().count()) + .d("current time", currentTime.time_since_epoch().count())); + + return ResultCode::ILLEGAL_ARGUMENT; + } + + auto wrapper = CurlWrapper::create(!isUserInitiated && m_throttled, m_authDelegate); + if (wrapper == nullptr) { + ACSDK_ERROR(LX("downloadArtifact").m("Can't create CurlWrapper")); + + return ResultCode::ILLEGAL_ARGUMENT; + } + + auto parentDirectory = getTmpParentDirectory(); + filesystem::makeDirectory(parentDirectory); + if (!m_unpack) { + path = parentDirectory + "/" + parseFileFromLink(artifact->getS3Url(), artifact->getId()); + } else { + path = parentDirectory + "/" + "unpacked"; + } + if (!filesystem::pathContainsPrefix(path, parentDirectory)) { + // path wasn't under parentDirectory, link may have excessive directory traversal characters ../ + ACSDK_ERROR(LX("downloadArtifact").m("Invalid URL file path").d("path", path)); + + return ResultCode::ILLEGAL_ARGUMENT; + } + + if (m_unpack) { + filesystem::makeDirectory(path); + } + auto downloadResult = wrapper->download( + artifact->getS3Url(), path, shared_from_this(), m_unpack, artifact->getArtifactSizeBytes()); + + if (downloadResult != ResultCode::SUCCESS) { + s_metrics().addCounter(METRIC_PREFIX_ERROR("downloadArtifactFailed")); + filesystem::removeAll(path); + return downloadResult; + } + + // clear user initiated flag for next attempt + return ResultCode::SUCCESS; +} + +bool DavsHandler::onProgressUpdate(long dlTotal, long dlNow, long ulTotal, long ulNow) { + // Check whether cancel has been requested + // Returning false will cause libcurl to abort the transfer and return + lock_guard lock(m_eventMutex); + return (!m_shutdown); +} + +bool DavsHandler::isThrottled() const { + return m_throttled; +} + +void DavsHandler::setThrottled(const bool throttle) { + m_throttled = throttle; +} + +void DavsHandler::setConnectionState(bool connected) { + lock_guard lock(m_eventMutex); + m_networkConnected = connected; + m_eventTrigger.notify_all(); +} + +} // namespace davs +} // namespace acsdkAssets +} // namespace alexaClientSDK diff --git a/capabilities/DavsClient/acsdkDavsClient/test/CMakeLists.txt b/capabilities/DavsClient/acsdkDavsClient/test/CMakeLists.txt new file mode 100644 index 0000000000..46f78200b7 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/test/CMakeLists.txt @@ -0,0 +1,7 @@ +set(LIBS + "AVSCommon" + "acsdkDavsClientForTesting" + "acsdkAssetsMocks" + ) + +discover_unit_tests("" "${LIBS}") \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkDavsClient/test/DavsClientTest.cpp b/capabilities/DavsClient/acsdkDavsClient/test/DavsClientTest.cpp new file mode 100644 index 0000000000..6a735b0a59 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/test/DavsClientTest.cpp @@ -0,0 +1,436 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "AuthDelegateMock.h" +#include "CurlWrapperMock.h" +#include "DavsServiceMock.h" +#include "TestUtil.h" +#include "acsdkAssetsCommon/Base64Url.h" +#include "acsdkDavsClient/DavsEndpointHandlerV3.h" +#include "acsdkDavsClient/DavsHandler.h" + +using namespace std; +using namespace chrono; +using namespace testing; +using namespace alexaClientSDK::acsdkAssets::common; +using namespace alexaClientSDK::acsdkAssets::commonInterfaces; +using namespace alexaClientSDK::acsdkAssets::davsInterfaces; +using namespace alexaClientSDK::acsdkAssets::davs; +using namespace alexaClientSDK::avsCommon::utils; + +// through trials, initial time is the minimum time we need to wait for downloadCheck to finish +static milliseconds INTIIAL_BACKOFF_VALUE = milliseconds(100); +static milliseconds BASE_BACKOFF_VALUE = milliseconds(10); +static milliseconds MAX_BACKOFF_VALUE = milliseconds(50); + +// Same as: cat source | sed -e 's/what/replacement/' except that it crashes if it can't find what in source +static string replace_string(const string& source, const string& what, const string& replacement); + +class MyDownloader : public DavsDownloadCallbackInterface { +public: + void onStart() override { + started = true; + } + void onArtifactDownloaded(std::shared_ptr, const std::string& artifactPath) override { + path = artifactPath; + downloaded = true; + failure = false; + } + void onDownloadFailure(ResultCode) override { + failure = true; + } + void onProgressUpdate(int prog) override { + this->progress = prog; + } + + bool started; + bool downloaded; + bool failure; + int progress; + string path; +}; + +class MyChecker : public DavsCheckCallbackInterface { +public: + bool checkIfOkToDownload(std::shared_ptr, size_t) override { + return okToDownload; + } + void onCheckFailure(ResultCode) override { + checkFailure = true; + } + + bool okToDownload; + bool checkFailure; +}; + +struct TestDataForCheckArtifact { + string description; + string request; + bool getResult; + string response; + bool checkFailure; + bool downloadAttempted; + string contentType; +}; + +class CheckArtifactTest + : public Test + , public WithParamInterface { +public: + void SetUp() override { + DAVS_TEST_DIR = createTmpDir("Artifact"); + testData = GetParam(); + + checker = make_shared(); + downloader = make_shared(); + + checker->okToDownload = true; + checker->checkFailure = false; + downloader->started = false; + downloader->downloaded = false; + downloader->failure = false; + + CurlWrapperMock::mockResponse = testData.response; + CurlWrapperMock::getResult = testData.getResult; + CurlWrapperMock::header = testData.contentType; + } + + void TearDown() override { + filesystem::removeAll(DAVS_TEST_DIR); + } + + bool jsonEquals(const string& json1, const string& json2) { + using namespace rapidjson; + + Document d1(kObjectType); + Document d2(kObjectType); + + d1.Parse(json1.c_str()); + d2.Parse(json2.c_str()); + + return (d1 == d2); + } + + string DAVS_TEST_DIR; + + TestDataForCheckArtifact testData; + shared_ptr checker; + shared_ptr downloader; + shared_ptr request; + shared_ptr handler; +}; + +TEST_P(CheckArtifactTest, parametrizedTest) { // NOLINT + DavsRequest::FilterMap filterMap; + filterMap["locale"] = {"en-US"}; + filterMap["modelClass"] = {"B"}; + + CurlWrapperMock::useDavsService = false; + + request = DavsRequest::create("wakeword", "alexa", filterMap); + handler = DavsHandler::create( + request, + downloader, + checker, + DAVS_TEST_DIR, + BASE_BACKOFF_VALUE, + MAX_BACKOFF_VALUE, + AuthDelegateMock::create(), + DavsEndpointHandlerV3::create("123")); + handler->requestAndDownload(true); + + ASSERT_TRUE(waitUntil([this] { return checker->checkFailure == testData.checkFailure; })); + ASSERT_TRUE(waitUntil([this] { return downloader->started == testData.downloadAttempted; })); + ASSERT_TRUE(jsonEquals(CurlWrapperMock::capturedRequest, testData.request)); +} + +string valid_request = + R"({"artifactType":"wakeword","artifactKey":"alexa","filters":{"locale":["en-US"],"modelClass":["B"]}})"; +string valid_response = R"( +{ + "urlExpiryEpoch": 1537400172798, + "artifactType": "wakeword", + "artifactSize": 4485147, + "artifactKey": "alexa", + "artifactTimeToLive": 1537400172798, + "downloadUrl": "https://device-artifacts-v2.s3.amazonaws.com/wakeword-alexa-8aac547c6d1c48cc16dc317900d3ba8e.tar.gz?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20180919T223612Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=AKIAJTPKJI7A3WTMPCQQ%2F20180919%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=87160eda6c8325e9ce61120974cdf2f81c4b8a3d47c1192f6cf4b7500ed17165", + "artifactIdentifier": "8aac547c6d1c48cc16dc317900d3ba8e" +})"; +auto insecure_response = replace_string(valid_response, "https://", "http://"); +auto negative_expiry = replace_string(valid_response, "\"urlExpiryEpoch\": 1537400172798,", "\"urlExpiryEpoch\": -12,"); +auto int_as_string = replace_string(valid_response, "4485147", "\"4485147\""); + +// clang-format off +string valid_multipart_full_response = R"(--90378d5d-5961-4c14-9105-d968ad9f2ba8\r +Content-Type: application/json + +{"artifactType": "wakeword","artifactKey": "alexa","artifactTimeToLive": 1570483487469,"artifactIdentifier": "56e662bbcaafd80f6eadcbe1bb6d837a","artifactSize": 55, "checksum": {"md5": "56e662bbcaafd80f6eadcbe1bb6d837a"}} +--90378d5d-5961-4c14-9105-d968ad9f2ba8\r +Content-Type: application/octet-stream + +000 blobbyblobbyblobbyblobby +001 blobbyblobbyblobbyblobby +002 blobbyblobbyblobbyblobby +003 blobbyblobbyblobbyblobby +004 blobbyblobbyblobbyblobby +005 blobbyblobbyblobbyblobby +006 blobbyblobbyblobbyblobby +007 blobbyblobbyblobbyblobby +008 blobbyblobbyblobbyblobby +009 blobbyblobbyblobbyblobby +010 blobbyblobbyblobbyblobby +011 blobbyblobbyblobbyblobby +012 blobbyblobbyblobbyblobby +)"; +INSTANTIATE_TEST_CASE_P(ArtifactCheckTest, CheckArtifactTest, ValuesIn>( + // description request getResponse response checkFailure downloadAttempted contentType + {{"Happy case" , valid_request , true , valid_response , false , true , "Content-Type: application/json"}, + {"HTTP GET failed" , valid_request , false , "" , true , false , "Content-Type: application/json"}, + {"GET failed w/response" , valid_request , false , valid_response , true , false , "Content-Type: application/json"}, + {"Empty response" , valid_request , true , "" , true , false , "Content-Type: application/json"}, + {"Invalid JSON" , valid_request , true , "Golden Fleece" , true , false , "Content-Type: application/json"}, + {"Valid JSON, no data" , valid_request , true , "{\"AMZN\":1917}" , true , false , "Content-Type: application/json"}, + {"Valid JSON, bad data" , valid_request , true , negative_expiry , true , false , "Content-Type: application/json"}, + {"String instead of int" , valid_request , true , int_as_string , true , false , "Content-Type: application/json"}, + {"Insecure response OK" , valid_request , true , insecure_response , false , true , "Content-Type: application/json"}, + {"Multi-part happy case" , valid_request , true , valid_multipart_full_response , false , false , "Content-Type: multipart/mixed; boundary=--90378d5d-5961-4c14-9105-d968ad9f2ba8\r\n"} +}), PrintDescription()); +// clang-format on + +static string replace_string(const string& source, const string& what, const string& replacement) { + string result = source; + size_t pos = result.find(what, 0); + result.replace(pos, what.length(), replacement); + return result; +} + +class DownloadTest : public Test { +public: + DavsServiceMock service; + + void SetUp() override { + DAVS_TEST_DIR = createTmpDir("Artifact"); + + DavsRequest::FilterMap filterMap; + filterMap["locale"] = {"en-US"}; + + checker = make_shared(); + downloader = make_shared(); + request = DavsRequest::create("wakeword", "alexa", filterMap); + handler = DavsHandler::create( + move(request), + downloader, + checker, + DAVS_TEST_DIR, + BASE_BACKOFF_VALUE, + MAX_BACKOFF_VALUE, + AuthDelegateMock::create(), + DavsEndpointHandlerV3::create("123")); + CurlWrapperMock::useDavsService = true; + } + + void TearDown() override { + filesystem::removeAll(DAVS_TEST_DIR); + CurlWrapperMock::downloadShallFail = false; + CurlWrapperMock::useDavsService = false; + } + + string DAVS_TEST_DIR; + + shared_ptr checker; + shared_ptr downloader; + shared_ptr request; + shared_ptr handler; +}; + +TEST_F(DownloadTest, fromPublishToDownloadTest) { // NOLINT + DavsServiceMock::FilterMap metadata; + metadata["locale"] = {"en-US"}; + + string artifactToDownload; + Base64Url::encode("TestContent", artifactToDownload); + service.uploadBase64Artifact("wakeword", "alexa", metadata, artifactToDownload, seconds(10)); + + checker->okToDownload = true; + handler->requestAndDownload(true); + + ASSERT_TRUE(waitUntil([this] { return downloader->started; })); + ASSERT_TRUE(waitUntil([this] { return downloader->downloaded; })); + ASSERT_EQ( + filesystem::basenameOf(downloader->path), "wakeword_alexa_" + DavsServiceMock::getId(metadata) + ".tar.gz"); + ASSERT_FALSE(downloader->failure); +} + +TEST_F(DownloadTest, downloadWithThrottlingTest) { // NOLINT + DavsServiceMock::FilterMap metadata; + metadata["locale"] = {"en-US"}; + + string artifactToDownload; + Base64Url::encode("TestContent", artifactToDownload); + service.uploadBase64Artifact("wakeword", "alexa", metadata, artifactToDownload, seconds(10)); + + checker->okToDownload = true; + handler->setThrottled(true); + handler->requestAndDownload(true); + + ASSERT_TRUE(waitUntil([this] { return downloader->started; })); + ASSERT_TRUE(waitUntil([this] { return downloader->downloaded; })); + ASSERT_EQ( + filesystem::basenameOf(downloader->path), "wakeword_alexa_" + DavsServiceMock::getId(metadata) + ".tar.gz"); + ASSERT_FALSE(downloader->failure); +} + +TEST_F(DownloadTest, downloadFailureDoesntRetryForeverTest) { // NOLINT + DavsServiceMock::FilterMap metadata; + metadata["locale"] = {"en-US"}; + + string artifactToDownload; + Base64Url::encode("TestContent", artifactToDownload); + service.uploadBase64Artifact("wakeword", "alexa", metadata, artifactToDownload, seconds(10)); + + checker->okToDownload = true; + + // After download attempts start, the first download will wait + handler->setFirstBackOff(INTIIAL_BACKOFF_VALUE); + handler->requestAndDownload(true); + + ASSERT_TRUE(waitUntil([this] { return downloader->started; })); + + // After download starts, it shall end in failure + CurlWrapperMock::downloadShallFail = true; + ASSERT_TRUE(waitUntil([this] { return downloader->failure; }, seconds(1))); + ASSERT_FALSE(downloader->downloaded); +} + +TEST_F(DownloadTest, downloadCanRecoverTest) { // NOLINT + DavsServiceMock::FilterMap metadata; + metadata["locale"] = {"en-US"}; + + string artifactToDownload; + Base64Url::encode("TestContent", artifactToDownload); + service.uploadBase64Artifact("wakeword", "alexa", metadata, artifactToDownload, seconds(10)); + + checker->okToDownload = true; + + // After download attempts start, the first download will wait + handler->setFirstBackOff(INTIIAL_BACKOFF_VALUE); + handler->requestAndDownload(true); + + ASSERT_TRUE(waitUntil([this] { return downloader->started; }, milliseconds(500))); + + // After download starts, it shall end in failure + CurlWrapperMock::downloadShallFail = true; + ASSERT_FALSE(downloader->downloaded); + this_thread::sleep_for(INTIIAL_BACKOFF_VALUE); + + // now the download shall recover and successful + CurlWrapperMock::downloadShallFail = false; + ASSERT_TRUE(waitUntil([this] { return downloader->downloaded; }, seconds(1))); + ASSERT_FALSE(downloader->failure); +} + +TEST_F(DownloadTest, downloadRetriesWithThrottlingTest) { // NOLINT + DavsServiceMock::FilterMap metadata; + metadata["locale"] = {"en-US"}; + + string artifactToDownload; + Base64Url::encode("TestContent", artifactToDownload); + service.uploadBase64Artifact("wakeword", "alexa", metadata, artifactToDownload, seconds(10)); + + checker->okToDownload = true; + + // After download attempts start, the first download will wait + handler->setFirstBackOff(INTIIAL_BACKOFF_VALUE); + handler->requestAndDownload(true); + + ASSERT_TRUE(waitUntil([this] { return downloader->started; }, milliseconds(500))); + + // After download starts, it shall end in failure + CurlWrapperMock::downloadShallFail = true; + handler->setThrottled(false); + ASSERT_FALSE(downloader->downloaded); + this_thread::sleep_for(INTIIAL_BACKOFF_VALUE); + + // now the download shall recover and successful + handler->setThrottled(true); + CurlWrapperMock::downloadShallFail = false; + ASSERT_TRUE(waitUntil([this] { return downloader->downloaded; }, seconds(1))); + ASSERT_FALSE(downloader->failure); +} + +// Tests that bad values do not extend beyond the base or max +TEST_F(DownloadTest, BackOffTimeLimitsTest) { // NOLINT + milliseconds baseTimeMs = handler->getBackOffTime(milliseconds(0)); + ASSERT_EQ(baseTimeMs.count(), BASE_BACKOFF_VALUE.count()); + + baseTimeMs = handler->getBackOffTime(milliseconds(-100)); + ASSERT_EQ(baseTimeMs.count(), BASE_BACKOFF_VALUE.count()); + + milliseconds maxTimeMs = handler->getBackOffTime(seconds(1000000)); + ASSERT_EQ(maxTimeMs.count(), MAX_BACKOFF_VALUE.count()); +} + +TEST_F(DownloadTest, BackOffIncrements) { // NOLINT + auto backoffTime = handler->getBackOffTime(milliseconds(0)); + ASSERT_EQ(backoffTime.count(), BASE_BACKOFF_VALUE.count()); + + milliseconds prevBackoffTime; + while (backoffTime < MAX_BACKOFF_VALUE) { + prevBackoffTime = backoffTime; + backoffTime = handler->getBackOffTime(backoffTime); + ASSERT_GT(backoffTime, prevBackoffTime); + } + + backoffTime = handler->getBackOffTime(backoffTime); + ASSERT_EQ(backoffTime.count(), MAX_BACKOFF_VALUE.count()); +} + +string default_value = "default_value"; +struct UrlData { + string url; + string result; +}; +class UrlParserTest + : public Test + , public WithParamInterface {}; + +TEST_P(UrlParserTest, urlParser) { // NOLINT + auto p = GetParam(); + ASSERT_EQ(DavsHandler::parseFileFromLink(p.url, default_value), p.result); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(PossibleTests, UrlParserTest, ValuesIn>( + {{"https://device-artifacts-v2.s3.amazonaws.com/file.tar.gz?X-Amz-Algorithm=AW", "file.tar.gz"}, + {"http://device-artifacts-v2.s3.amazonaws.com/file.tar.gz", "file.tar.gz"}, + {"https://device-artifacts-v2.s3.amazonaws.com/file.tar.gz?X-Amz-Algorithm=AW", "file.tar.gz"}, + {"https://s3.amazonaws.com/f", "f"}, + {"https://s3.amazonaws.com/f?", "f"}, + {"https://s3.amazonaws.com/?hi/file.tar.gz?bye", "file.tar.gz"}, + {"https://amazonaws.com/file.tar.gz?X-Amz-Algorithm=AW", "file.tar.gz"}, + {"https://azamonaws.com/file.tar.gz?X-Amz-Algorithm=AW", default_value}, + {"https://s3.amazon.com/file.tar.gz?X-Amz-Algorithm=AW", default_value}, + {"https://s3.amazonaws.com/?X-Amz", default_value}, + })); +// clang-format on \ No newline at end of file diff --git a/capabilities/DavsClient/acsdkDavsClient/test/DavsEndpointHandlerV3Test.cpp b/capabilities/DavsClient/acsdkDavsClient/test/DavsEndpointHandlerV3Test.cpp new file mode 100644 index 0000000000..5fc29ad14d --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClient/test/DavsEndpointHandlerV3Test.cpp @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "TestUtil.h" +#include "acsdkDavsClient/DavsEndpointHandlerV3.h" + +using namespace std; +using namespace testing; +using namespace alexaClientSDK::acsdkAssets::common; +using namespace alexaClientSDK::acsdkAssets::commonInterfaces; +using namespace alexaClientSDK::acsdkAssets::davs; + +struct TestDataEndpoints { + string description; + string segmentId; + string locale; + string type; + string key; + DavsRequest::FilterMap filters; + Region region; + string resultUrl; +}; + +class DavsEndpointHandlerV3Test + : public Test + , public WithParamInterface {}; + +TEST_F(DavsEndpointHandlerV3Test, InvalidCreate) { // NOLINT + ASSERT_EQ(DavsEndpointHandlerV3::create(""), nullptr); +} + +TEST_F(DavsEndpointHandlerV3Test, InvalidInputs) { // NOLINT + auto unit = DavsEndpointHandlerV3::create("123"); + ASSERT_NE(unit, nullptr); + ASSERT_EQ(unit->getDavsUrl(nullptr), ""); +} + +TEST_P(DavsEndpointHandlerV3Test, TestWithVariousEndpointCombinations) { // NOLINT + auto& p = GetParam(); + auto unit = DavsEndpointHandlerV3::create(p.segmentId, p.locale); + + auto actualUrl = unit->getDavsUrl(DavsRequest::create(p.type, p.key, p.filters, p.region)); + ASSERT_EQ(actualUrl, p.resultUrl); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(EndpointChecks, DavsEndpointHandlerV3Test, ValuesIn>( // NOLINT + // Description Segment Locale Type Key Filters Region || Result URL + {{"NA Endpoint" , "123456", "en-US", "Type1", "Key1", {{"F", {"A", "B"}}}, Region::NA, "https://api.amazonalexa.com/v3/segments/123456/artifacts/Type1/Key1?locale=en-US&encodedFilters=eyJGIjpbIkEiLCJCIl19"}, + {"EU Endpoint" , "ABCDEF", "en-US", "Type2", "Key2", {{"F", {"A", "B"}}}, Region::EU, "https://api.eu.amazonalexa.com/v3/segments/ABCDEF/artifacts/Type2/Key2?locale=en-US&encodedFilters=eyJGIjpbIkEiLCJCIl19"}, + {"FE Endpoint" , "UVWXYZ", "en-US", "Type3", "Key3", {{"F", {"A", "B"}}}, Region::FE, "https://api.fe.amazonalexa.com/v3/segments/UVWXYZ/artifacts/Type3/Key3?locale=en-US&encodedFilters=eyJGIjpbIkEiLCJCIl19"}, + {"No locale" , "123456", "" , "Type4", "Key4", {{"F", {"A", "B"}}}, Region::NA, "https://api.amazonalexa.com/v3/segments/123456/artifacts/Type4/Key4?encodedFilters=eyJGIjpbIkEiLCJCIl19"}, + {"No filters" , "123456", "en-GB", "Type5", "Key5", {} , Region::NA, "https://api.amazonalexa.com/v3/segments/123456/artifacts/Type5/Key5?locale=en-GB"}, + {"No filters/locale", "123456", "" , "Type6", "Key6", {} , Region::NA, "https://api.amazonalexa.com/v3/segments/123456/artifacts/Type6/Key6"}, +}), PrintDescription()); +// clang-format on diff --git a/capabilities/DavsClient/acsdkDavsClientInterfaces/CMakeLists.txt b/capabilities/DavsClient/acsdkDavsClientInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..ba16f8d996 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClientInterfaces/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkDavsClientInterfaces LANGUAGES CXX) + +if (DEFINED AVS_CMAKE_BUILD) + include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +endif() + +add_library(acsdkDavsClientInterfaces INTERFACE) + +target_include_directories(acsdkDavsClientInterfaces INTERFACE + "${acsdkDavsClientInterfaces_SOURCE_DIR}/include" +) + +# install interface +asdk_install_interface() diff --git a/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/ArtifactHandlerInterface.h b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/ArtifactHandlerInterface.h new file mode 100644 index 0000000000..ed15346ce5 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/ArtifactHandlerInterface.h @@ -0,0 +1,85 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKDAVSCLIENTINTERFACES_ARTIFACTHANDLERINTERFACE_H_ +#define ACSDKDAVSCLIENTINTERFACES_ARTIFACTHANDLERINTERFACE_H_ + +#include "DavsCheckCallbackInterface.h" +#include "DavsDownloadCallbackInterface.h" +#include "acsdkAssetsInterfaces/DavsRequest.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davsInterfaces { + +class ArtifactHandlerInterface { +public: + virtual ~ArtifactHandlerInterface() = default; + + /** + * Register an artifact to be checked, downloaded in requested, and maintained. This means that if an artifact is + * registered, the Artifact Handler will perform regular checks when the expiry is reached to ensure that the + * artifact is up to date. + * + * @param artifactRequest REQUIRED, a valid request containing information for the artifact to be downloaded. + * @param downloadCallback REQUIRED, a manager listener that will handle what to do with the artifact when its + * downloaded or failed. + * @param checkCallback REQUIRED, a manager listener that will handle checking if the artifact should be downloaded. + * @param downloadImmediately REQUIRED, tell the manager to download immediately or on the next update interval. + * @return uuid key for the artifact from davs client based on the given request, EMPTY string if registration + * failed. + */ + virtual std::string registerArtifact( + std::shared_ptr artifactRequest, + std::shared_ptr downloadCallback, + std::shared_ptr checkCallback, + bool downloadImmediately) = 0; + + /** + * Deregister an artifact, this cancels any download that's already been started and removes the request from the + * registration list. + * + * @param requestUUID REQUIRED, uuid of the request to be deregistered. + */ + virtual void deregisterArtifact(const std::string& requestUUID) = 0; + + /** + * Issues a single check and a download (if requested) of a given artifact which is discarded afterwards. + * + * @param artifactRequest REQUIRED, a valid request containing information for the artifact to be downloaded. + * @param downloadCallback REQUIRED, a listener that will handle what to do with the artifact when its downloaded or + * failed. + * @param checkCallback REQUIRED, a listener that will handle checking if the artifact should be downloaded. + * @return uuid key for the artifact from davs client based on the given request, EMPTY string if request failed. + */ + virtual std::string downloadOnce( + std::shared_ptr artifactRequest, + std::shared_ptr downloadCallback, + std::shared_ptr checkCallback) = 0; + /** + * Can set a downloadOnce artifact to auto update (like registerArtifact) or prevent an artifact from updating (like + * downloadOnce). + * + * @param requestUUID REQUIRED, uuid of the request to be deregistered. + * @param enable weather to enable auto update or disable it (the difference between registerArtifact and + * downloadOnce). + */ + virtual void enableAutoUpdate(const std::string& requestUUID, bool enable) = 0; +}; + +} // namespace davsInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKDAVSCLIENTINTERFACES_ARTIFACTHANDLERINTERFACE_H_ diff --git a/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/CurlProgressCallbackInterface.h b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/CurlProgressCallbackInterface.h new file mode 100644 index 0000000000..ff9d1593b4 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/CurlProgressCallbackInterface.h @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKDAVSCLIENTINTERFACES_CURLPROGRESSCALLBACKINTERFACE_H_ +#define ACSDKDAVSCLIENTINTERFACES_CURLPROGRESSCALLBACKINTERFACE_H_ + +namespace amazon { +namespace davs { + +class CurlProgressCallbackInterface { +public: + virtual ~CurlProgressCallbackInterface() = default; + + /** + * An event that is called with a frequent interval. While data is being transferred it will be + * called very frequently, and during slow periods like when nothing is being transferred it can + * slow down to about one call per second. + * + * @param dlTotal, total bytes need to be downloaded, 0 if Unknown/unused + * @param dlNow, number of bytes downloaded so far, 0 if Unknown/unused + * @param ulTotal, total bytes need to be uploaded, 0 if Unknown/unused + * @param ulNow, number of bytes uploaded so far, 0 if Unknown/unused + * @return Returning false will cause libcurl to abort the transfer and return + */ + virtual bool onProgressUpdate(long dlTotal, long dlNow, long ulTotal, long ulNow) = 0; +}; + +} // namespace davs +} // namespace amazon + +#endif // ACSDKDAVSCLIENTINTERFACES_CURLPROGRESSCALLBACKINTERFACE_H_ diff --git a/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsCheckCallbackInterface.h b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsCheckCallbackInterface.h new file mode 100644 index 0000000000..e4000ec3a2 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsCheckCallbackInterface.h @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKDAVSCLIENTINTERFACES_DAVSCHECKCALLBACKINTERFACE_H_ +#define ACSDKDAVSCLIENTINTERFACES_DAVSCHECKCALLBACKINTERFACE_H_ + +#include "acsdkAssetsInterfaces/ResultCode.h" +#include "acsdkAssetsInterfaces/VendableArtifact.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davsInterfaces { + +class DavsCheckCallbackInterface { +public: + virtual ~DavsCheckCallbackInterface() = default; + + /** + * An event that is called after check to see if the manager would like to download the given artifact. + * It is the manager's responsibility to check the exiting artifact against the one being checked. + * If it is the same then the requester should specify to not download. + * If it is different, then the requester should specify to download. + * If the insufficientSpace is true, then the requester should free the necessary space for the artifact. + * + * @param artifact ALWAYS VALID, information about the artifact including the original request. + * @param freeSpaceNeeded amount of space needed to be freed to make room for this artifact, 0 if existing space is + * sufficient. + * @return should we download the artifact? return true to download, false to not download. + */ + virtual bool checkIfOkToDownload( + std::shared_ptr artifact, + size_t freeSpaceNeeded) = 0; + + /** + * An event that is called when the check failed with a specific reason. + * + * @param errorCode reason for the failure. + */ + virtual void onCheckFailure(commonInterfaces::ResultCode errorCode) = 0; +}; + +} // namespace davsInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKDAVSCLIENTINTERFACES_DAVSCHECKCALLBACKINTERFACE_H_ diff --git a/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsDownloadCallbackInterface.h b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsDownloadCallbackInterface.h new file mode 100644 index 0000000000..4d42081d0b --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsDownloadCallbackInterface.h @@ -0,0 +1,68 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKDAVSCLIENTINTERFACES_DAVSDOWNLOADCALLBACKINTERFACE_H_ +#define ACSDKDAVSCLIENTINTERFACES_DAVSDOWNLOADCALLBACKINTERFACE_H_ + +#include "acsdkAssetsInterfaces/ResultCode.h" +#include "acsdkAssetsInterfaces/VendableArtifact.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davsInterfaces { + +class DavsDownloadCallbackInterface { +public: + virtual ~DavsDownloadCallbackInterface() = default; + + /** + * An event that is called as soon as the download has started. + */ + virtual void onStart() = 0; + + /** + * An event that is called as soon as the artifact has been downloaded successfully. The manager will receive + * metadata info about the artifact as well as the path of where to find the artifact on disk. + * + * It is the manager's responsibility to move the artifact from the specified location and maintain its lifecycle + * thereafter. If the artifact is not moved, it will be DELETED. + * + * @param artifact ALWAYS VALID, information about the artifact including the original request. + * @param path ALWAYS VALID, path of where to find the artifact on disk. + */ + virtual void onArtifactDownloaded( + std::shared_ptr artifact, + const std::string& path) = 0; + + /** + * An event that is called when the download fails, providing a reason for failure. + * + * @param errorCode reason for the failure. + */ + virtual void onDownloadFailure(commonInterfaces::ResultCode errorCode) = 0; + + /** + * An event that is called periodically to denote the progress of the download. + * + * @param progress ALWAYS VALID, between 0 and 100 + */ + virtual void onProgressUpdate(int progress) = 0; +}; + +} // namespace davsInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKDAVSCLIENTINTERFACES_DAVSDOWNLOADCALLBACKINTERFACE_H_ diff --git a/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsEndpointHandlerInterface.h b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsEndpointHandlerInterface.h new file mode 100644 index 0000000000..f498ef28e8 --- /dev/null +++ b/capabilities/DavsClient/acsdkDavsClientInterfaces/include/acsdkDavsClientInterfaces/DavsEndpointHandlerInterface.h @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKDAVSCLIENTINTERFACES_DAVSENDPOINTHANDLERINTERFACE_H_ +#define ACSDKDAVSCLIENTINTERFACES_DAVSENDPOINTHANDLERINTERFACE_H_ + +#include + +#include "acsdkAssetsInterfaces/DavsRequest.h" + +namespace alexaClientSDK { +namespace acsdkAssets { +namespace davsInterfaces { + +/** + * Interface for managing the URL generation for DAVS specific requests. + */ +class DavsEndpointHandlerInterface { +public: + /** + * Destructor. + */ + virtual ~DavsEndpointHandlerInterface() = default; + + /** + * Generates the URL given the DAVS requests, will return an empty string upon failure. + * + * @param request DAVS request containing all the necessary information needed to identify a DAVS artifact. + * @return a full valid URL string, empty upon failure. + */ + virtual std::string getDavsUrl(std::shared_ptr request) = 0; +}; + +} // namespace davsInterfaces +} // namespace acsdkAssets +} // namespace alexaClientSDK + +#endif // ACSDKDAVSCLIENTINTERFACES_DAVSENDPOINTHANDLERINTERFACE_H_ \ No newline at end of file diff --git a/capabilities/DeviceSetup/acsdkDeviceSetup/src/CMakeLists.txt b/capabilities/DeviceSetup/acsdkDeviceSetup/src/CMakeLists.txt index e5524c35cf..5039ccce26 100644 --- a/capabilities/DeviceSetup/acsdkDeviceSetup/src/CMakeLists.txt +++ b/capabilities/DeviceSetup/acsdkDeviceSetup/src/CMakeLists.txt @@ -1,7 +1,7 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkDeviceSetup") add_library( - acsdkDeviceSetup SHARED + acsdkDeviceSetup DeviceSetup.cpp DeviceSetupMessageRequest.cpp DeviceSetupComponent.cpp diff --git a/capabilities/DoNotDisturb/acsdkDoNotDisturb/src/CMakeLists.txt b/capabilities/DoNotDisturb/acsdkDoNotDisturb/src/CMakeLists.txt index 44de5ed579..6b4b7befaf 100644 --- a/capabilities/DoNotDisturb/acsdkDoNotDisturb/src/CMakeLists.txt +++ b/capabilities/DoNotDisturb/acsdkDoNotDisturb/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkDoNotDisturb") -add_library(acsdkDoNotDisturb SHARED +add_library(acsdkDoNotDisturb DNDMessageRequest.cpp DNDSettingProtocol.cpp DoNotDisturbCapabilityAgent.cpp diff --git a/capabilities/Equalizer/acsdkEqualizer/src/CMakeLists.txt b/capabilities/Equalizer/acsdkEqualizer/src/CMakeLists.txt index 01831425a9..a1d2e53011 100755 --- a/capabilities/Equalizer/acsdkEqualizer/src/CMakeLists.txt +++ b/capabilities/Equalizer/acsdkEqualizer/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=equalizer") -add_library(acsdkEqualizer SHARED +add_library(acsdkEqualizer EqualizerCapabilityAgent.cpp) target_include_directories(acsdkEqualizer PUBLIC diff --git a/capabilities/Equalizer/acsdkEqualizerImplementations/src/CMakeLists.txt b/capabilities/Equalizer/acsdkEqualizerImplementations/src/CMakeLists.txt index 8719dba2c2..d84a6b71fc 100755 --- a/capabilities/Equalizer/acsdkEqualizerImplementations/src/CMakeLists.txt +++ b/capabilities/Equalizer/acsdkEqualizerImplementations/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=equalizer") -add_library(acsdkEqualizerImplementations SHARED +add_library(acsdkEqualizerImplementations EqualizerComponent.cpp EqualizerController.cpp EqualizerUtils.cpp diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/include/acsdkExternalMediaPlayer/ExternalMediaAdapterHandler.h b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/include/acsdkExternalMediaPlayer/ExternalMediaAdapterHandler.h index 706a06163a..e0ea0bfcfd 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/include/acsdkExternalMediaPlayer/ExternalMediaAdapterHandler.h +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/include/acsdkExternalMediaPlayer/ExternalMediaAdapterHandler.h @@ -147,30 +147,10 @@ class ExternalMediaAdapterHandler /** * The handler should play the given player - * @param localPlayerId The player ID to be logged out - * @param playContextToken Play context {Track/playlist/album/artist/station/podcast} identifier. - * @param index The index of the media item in the container, if the container is indexable. - * @param offset The offset position within media item, in milliseconds. - * @param skillToken An opaque token for the domain or skill that is presently associated with this player. - * @param playbackSessionId A universally unique identifier (UUID) generated to the RFC 4122 specification. - * @param navigation Communicates desired visual display behavior for the app associated with playback. - * @param preload If true, this Play directive is intended to preload the identified content only but not begin - * playback. - * @param playRequestor The @c PlayRequestor object that is used to distinguish if it's a music alarm or not. - * @param playbackTarget Target for handling play + * @param params Play parameters to play with * @return true if successful */ - virtual bool handlePlay( - const std::string& localPlayerId, - const std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const acsdkExternalMediaPlayerInterfaces::Navigation& navigation, - bool preload, - const alexaClientSDK::avsCommon::avs::PlayRequestor& playRequestor, - const std::string& playbackTarget) = 0; + virtual bool handlePlay(const ExternalMediaAdapterHandlerInterface::PlayParams& params) = 0; /** * Method to initiate the different types of play control like PLAY/PAUSE/RESUME/NEXT/... @@ -183,6 +163,12 @@ class ExternalMediaAdapterHandler virtual bool handlePlayControl( const std::string& localPlayerId, acsdkExternalMediaPlayerInterfaces::RequestType requestType, +#ifdef MEDIA_PORTABILITY_ENABLED + /// @param mediaSessionId The optional @c mediaSessionId used to track media playback + /// @param correlationToken The optional @c correlationToken used to opaquely plumb routing info + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif const std::string& playbackTarget) = 0; /** @@ -251,20 +237,16 @@ class ExternalMediaAdapterHandler bool forceLogin, std::chrono::milliseconds tokenRefreshInterval) override; bool logout(const std::string& localPlayerId) override; - bool play( - const std::string& localPlayerId, - const std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const std::string& navigation, - bool preload, - const alexaClientSDK::avsCommon::avs::PlayRequestor& playRequestor, - const std::string& playbackTarget) override; + bool play(const PlayParams& params) override; bool playControl( const std::string& localPlayerId, acsdkExternalMediaPlayerInterfaces::RequestType requestType, +#ifdef MEDIA_PORTABILITY_ENABLED + /// @param mediaSessionId The optional @c mediaSessionId used to track media playback + /// @param correlationToken The optional @c correlationToken used to opaquely plumb routing info + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif const std::string& playbackTarget) override; bool seek(const std::string& localPlayerId, std::chrono::milliseconds offset) override; bool adjustSeek(const std::string& localPlayerId, std::chrono::milliseconds deltaOffset) override; diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/include/acsdkExternalMediaPlayer/StaticExternalMediaPlayerAdapterHandler.h b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/include/acsdkExternalMediaPlayer/StaticExternalMediaPlayerAdapterHandler.h index 835b119cd4..37c15ea207 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/include/acsdkExternalMediaPlayer/StaticExternalMediaPlayerAdapterHandler.h +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/include/acsdkExternalMediaPlayer/StaticExternalMediaPlayerAdapterHandler.h @@ -56,20 +56,14 @@ class StaticExternalMediaPlayerAdapterHandler bool forceLogin, std::chrono::milliseconds tokenRefreshInterval) override; bool logout(const std::string& localPlayerId) override; - bool play( - const std::string& localPlayerId, - const std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const std::string& navigation, - bool preload, - const alexaClientSDK::avsCommon::avs::PlayRequestor& playRequestor, - const std::string& playbackTarget) override; + bool play(const PlayParams& params) override; bool playControl( const std::string& localPlayerId, acsdkExternalMediaPlayerInterfaces::RequestType requestType, +#ifdef MEDIA_PORTABILITY_ENABLED + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif const std::string& playbackTarget) override; bool seek(const std::string& localPlayerId, std::chrono::milliseconds offset) override; bool adjustSeek(const std::string& localPlayerId, std::chrono::milliseconds deltaOffset) override; diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/CMakeLists.txt b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/CMakeLists.txt index 0ec261a52f..a7cf05af68 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/CMakeLists.txt +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=ExternalMediaPlayer") -add_library(acsdkExternalMediaPlayer SHARED +add_library(acsdkExternalMediaPlayer AuthorizedSender.cpp ExternalMediaAdapterHandler.cpp ExternalMediaPlayer.cpp diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/ExternalMediaAdapterHandler.cpp b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/ExternalMediaAdapterHandler.cpp index 8b1ea3edfb..b0ad119641 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/ExternalMediaAdapterHandler.cpp +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/ExternalMediaAdapterHandler.cpp @@ -99,59 +99,29 @@ bool ExternalMediaAdapterHandler::logout(const std::string& localPlayerId) { return handleLogout(localPlayerId); } -static const std::unordered_map NAVIGATION_ENUM_MAP = {{"DEFAULT", Navigation::DEFAULT}, - {"NONE", Navigation::NONE}, - {"FOREGROUND", Navigation::FOREGROUND}}; - -static Navigation getNavigationEnum(std::string name) { - std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) -> unsigned char { - return static_cast(std::toupper(c)); - }); - - auto it = NAVIGATION_ENUM_MAP.find(name); - - return it != NAVIGATION_ENUM_MAP.end() ? it->second : Navigation::DEFAULT; -} - -bool ExternalMediaAdapterHandler::play( - const std::string& localPlayerId, - const std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const std::string& navigation, - bool preload, - const alexaClientSDK::avsCommon::avs::PlayRequestor& playRequestor, - const std::string& playbackTarget) { - if (!validatePlayer(localPlayerId)) { +bool ExternalMediaAdapterHandler::play(const PlayParams& params) { + if (!validatePlayer(params.localPlayerId)) { ACSDK_WARN(LX("playFailed") .d("reason", "player is not configured or not authorized") - .d("localPlayerId", localPlayerId)); + .d("localPlayerId", params.localPlayerId)); return false; } - auto playerInfo = m_playerInfoMap[localPlayerId]; + auto playerInfo = m_playerInfoMap[params.localPlayerId]; + + playerInfo.skillToken = params.skillToken; + playerInfo.playbackSessionId = params.playbackSessionId; - playerInfo.skillToken = skillToken; - playerInfo.playbackSessionId = playbackSessionId; - - return handlePlay( - localPlayerId, - playContextToken, - index, - offset, - skillToken, - playbackSessionId, - getNavigationEnum(navigation), - preload, - playRequestor, - playbackTarget); + return handlePlay(params); } bool ExternalMediaAdapterHandler::playControl( const std::string& localPlayerId, acsdkExternalMediaPlayerInterfaces::RequestType requestType, +#ifdef MEDIA_PORTABILITY_ENABLED + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif const std::string& playbackTarget) { if (!validatePlayer(localPlayerId)) { ACSDK_WARN(LX("playControlFailed") @@ -160,7 +130,11 @@ bool ExternalMediaAdapterHandler::playControl( return false; } +#ifdef MEDIA_PORTABILITY_ENABLED + return handlePlayControl(localPlayerId, requestType, mediaSessionId, correlationToken, playbackTarget); +#else return handlePlayControl(localPlayerId, requestType, playbackTarget); +#endif } bool ExternalMediaAdapterHandler::seek(const std::string& localPlayerId, std::chrono::milliseconds offset) { diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/ExternalMediaPlayer.cpp b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/ExternalMediaPlayer.cpp index c2cfaa78c9..dc7680c573 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/ExternalMediaPlayer.cpp +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/ExternalMediaPlayer.cpp @@ -23,10 +23,10 @@ #include #include +#include #include #include #include -#include #include #include #include @@ -84,6 +84,12 @@ static const std::string ALEXA_INTERFACE_TYPE = "AlexaInterface"; static const std::string EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_TYPE = ALEXA_INTERFACE_TYPE; /// ExternalMediaPlayer interface name static const std::string EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_NAME = "ExternalMediaPlayer"; + +#ifdef MEDIA_PORTABILITY_ENABLED +/// ExternalMediaPlayer interface version for media portability +static const std::string EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_VERSION_FOR_MEDIA_PORTABILITY = "1.5"; +#endif + /// ExternalMediaPlayer interface version static const std::string EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_VERSION = "1.2"; @@ -94,6 +100,12 @@ static const std::string PLAYBACKSTATEREPORTER_CAPABILITY_INTERFACE_VERSION = "1 /// Alexa.PlaybackController name. static const std::string PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_NAME = PLAYBACKCONTROLLER_NAMESPACE; + +#ifdef MEDIA_PORTABILITY_ENABLED +/// Alexa.PlaybackController version for media portability +static const std::string PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_VERSION_FOR_MEDIA_PORTABILITY = "1.1"; +#endif + /// Alexa.PlaybackController version. static const std::string PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_VERSION = "1.0"; @@ -208,7 +220,7 @@ static const std::string STOP_DIRECTIVE_RECEIVED = "STOP_DIRECTIVE_RECEIVED"; */ static void submitMetric( const std::shared_ptr& metricRecorder, - const std::string metricName, + const std::string& metricName, const DataPoint& dataPoint, const std::string& msgId, const std::string& trackId, @@ -498,10 +510,18 @@ ExternalMediaPlayer::ExternalMediaPlayer( PLAYBACKSTATEREPORTER_CAPABILITY_INTERFACE_NAME, PLAYBACKSTATEREPORTER_CAPABILITY_INTERFACE_VERSION)); +#ifdef MEDIA_PORTABILITY_ENABLED + m_capabilityConfigurations.insert(generateCapabilityConfiguration( + ALEXA_INTERFACE_TYPE, + PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_NAME, + mediaPortabilityEnabled() ? PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_VERSION_FOR_MEDIA_PORTABILITY + : PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_VERSION)); +#else m_capabilityConfigurations.insert(generateCapabilityConfiguration( ALEXA_INTERFACE_TYPE, PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_NAME, PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_VERSION)); +#endif m_capabilityConfigurations.insert(generateCapabilityConfiguration( ALEXA_INTERFACE_TYPE, @@ -565,15 +585,19 @@ void ExternalMediaPlayer::createAdapters( */ auto audioPipeline = audioPipelineFactory->createApplicationMediaInterfaces(playerId + "MediaPlayer"); + std::shared_ptr mediaPlayer; + std::shared_ptr channelVolume; if (!audioPipeline) { - ACSDK_ERROR(LX("createSpotifyAdapterFailed").m("failed to create spotifyAudioPipeline")); - continue; + ACSDK_WARN(LX(__func__).d("failed to create audioPipeline for playerId", playerId)); + } else { + mediaPlayer = audioPipeline->mediaPlayer; + channelVolume = audioPipeline->channelVolume; } auto adapter = entry.second( m_metricRecorder, - audioPipeline->mediaPlayer, - audioPipeline->channelVolume, + mediaPlayer, + channelVolume, speakerManager, m_messageSender, focusManager, @@ -607,10 +631,18 @@ void ExternalMediaPlayer::createAdapters( } std::shared_ptr getExternalMediaPlayerCapabilityConfiguration() { +#ifdef MEDIA_PORTABILITY_ENABLED + return generateCapabilityConfiguration( + EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_TYPE, + EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_NAME, + mediaPortabilityEnabled() ? EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_VERSION_FOR_MEDIA_PORTABILITY + : EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_VERSION); +#else return generateCapabilityConfiguration( EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_TYPE, EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_NAME, EXTERNALMEDIAPLAYER_CAPABILITY_INTERFACE_VERSION); +#endif } void ExternalMediaPlayer::onContextAvailable(const std::string& jsonContext) { @@ -1051,12 +1083,13 @@ void ExternalMediaPlayer::handlePlay(std::shared_ptr info, Reques return; } - std::string navigation; - if (!jsonUtils::retrieveValue(payload, "navigation", &navigation)) { + std::string navigationStr; + if (!jsonUtils::retrieveValue(payload, "navigation", &navigationStr)) { ACSDK_ERROR(LX("handleDirectiveFailed").d("reason", "nullNavigation")); sendExceptionEncounteredAndReportFailed(info, "missing navigation in Play directive"); return; } + auto navigation = stringToNavigation(navigationStr); bool preload; if (!jsonUtils::retrieveValue(payload, "preload", &preload)) { @@ -1086,9 +1119,21 @@ void ExternalMediaPlayer::handlePlay(std::shared_ptr info, Reques std::string alias; if (!jsonUtils::retrieveValue(payload, "aliasName", &alias)) { - ACSDK_INFO(LX("handleDirective").m("No playback traget")); + ACSDK_INFO(LX("handleDirective").m("No playback target")); } +#ifdef MEDIA_PORTABILITY_ENABLED + std::string mediaSessionId; + std::string correlationToken; + if (mediaPortabilityEnabled()) { + if (!jsonUtils::retrieveValue(payload, "mediaSessionId", &mediaSessionId)) { + ACSDK_ERROR(LX("handleDirective").m("NoMediaSessionId")); + } + + correlationToken = info->directive->getCorrelationToken(); + } +#endif + auto messageId = info->directive->getMessageId(); m_executor.submit([this, @@ -1103,21 +1148,31 @@ void ExternalMediaPlayer::handlePlay(std::shared_ptr info, Reques preload, playRequestor, messageId, +#ifdef MEDIA_PORTABILITY_ENABLED + mediaSessionId, + correlationToken, +#endif alias]() { auto maybeHandler = getHandlerFromPlayerId(playerId); if (maybeHandler.hasValue()) { auto handler = maybeHandler.value(); - if (handler.adapterHandler->play( - handler.localPlayerId, - playbackContextToken, - index, - std::chrono::milliseconds(offset), - skillToken, - playbackSessionId, - navigation, - preload, - playRequestor, - alias)) { + ExternalMediaAdapterHandlerInterface::PlayParams params( + handler.localPlayerId, + playbackContextToken, + index, + std::chrono::milliseconds(offset), + skillToken, + playbackSessionId, + navigation, + preload, + playRequestor, +#ifdef MEDIA_PORTABILITY_ENABLED + mediaSessionId, + correlationToken, +#endif + alias); + + if (handler.adapterHandler->play(params)) { submitMetric( m_metricRecorder, PLAY_DIRECTIVE_RECEIVED, @@ -1235,16 +1290,39 @@ void ExternalMediaPlayer::handlePlayControl(std::shared_ptr info, // fall through, alias name is not required } +#ifdef MEDIA_PORTABILITY_ENABLED + std::string mediaSessionId; + std::string correlationToken; + if (mediaPortabilityEnabled()) { + if (requestTypeIncludesMediaSessionId(request)) { + if (!jsonUtils::retrieveValue(payload, "mediaSessionId", &mediaSessionId)) { + ACSDK_ERROR(LX("handleDirective").m("NoMediaSessionId")); + } + } + + if (request == RequestType::RESUME) { + correlationToken = info->directive->getCorrelationToken(); + } + } + + m_executor.submit([this, info, playerId, request, playbackSessionId, alias, mediaSessionId, correlationToken]() { +#else m_executor.submit([this, info, playerId, request, playbackSessionId, alias]() { - std::string sessId = playbackSessionId; +#endif + std::string playbackSessId = playbackSessionId; auto maybeHandler = getHandlerFromPlayerId(playerId); if (maybeHandler.hasValue()) { auto handler = maybeHandler.value(); +#ifdef MEDIA_PORTABILITY_ENABLED + if (handler.adapterHandler->playControl( + handler.localPlayerId, request, mediaSessionId, correlationToken, alias)) { +#else if (handler.adapterHandler->playControl(handler.localPlayerId, request, alias)) { +#endif if (request == RequestType::STOP || request == RequestType::PAUSE) { - if (sessId.empty()) { + if (playbackSessId.empty()) { auto state = handler.adapterHandler->getAdapterState(handler.localPlayerId); - sessId = state.sessionState.playbackSessionId; + playbackSessId = state.sessionState.playbackSessionId; } auto messageId = info->directive->getMessageId(); submitMetric( @@ -1252,7 +1330,7 @@ void ExternalMediaPlayer::handlePlayControl(std::shared_ptr info, STOP_DIRECTIVE_RECEIVED, DataPointCounterBuilder{}.setName(STOP_DIRECTIVE_RECEIVED).increment(1).build(), messageId, - sessId, + playbackSessId, playerId); } } @@ -1302,17 +1380,24 @@ bool ExternalMediaPlayer::localOperation(PlaybackOperation op) { auto localPlayerId = localPlayerIdHandler.value().localPlayerId; auto adapterHandler = localPlayerIdHandler.value().adapterHandler; + auto requestType = RequestType::STOP; switch (op) { case PlaybackOperation::STOP_PLAYBACK: - adapterHandler->playControl(localPlayerId, RequestType::STOP, ""); + requestType = RequestType::STOP; break; - case PlaybackOperation::PAUSE_PLAYBACK: - adapterHandler->playControl(localPlayerId, RequestType::PAUSE, ""); + case PlaybackOperation::RESUMABLE_STOP: + case PlaybackOperation::TRANSIENT_PAUSE: + requestType = RequestType::PAUSE; break; case PlaybackOperation::RESUME_PLAYBACK: - adapterHandler->playControl(localPlayerId, RequestType::RESUME, ""); + requestType = RequestType::RESUME; break; } +#ifdef MEDIA_PORTABILITY_ENABLED + adapterHandler->playControl(localPlayerId, requestType, "", "", ""); +#else + adapterHandler->playControl(localPlayerId, requestType, ""); +#endif return true; } return false; @@ -1482,7 +1567,11 @@ void ExternalMediaPlayer::onButtonPressed(PlaybackButton button) { auto maybeHandler = getHandlerFromPlayerId(playerInFocus); if (maybeHandler.hasValue()) { const auto& handler = maybeHandler.value(); +#ifdef MEDIA_PORTABILITY_ENABLED + handler.adapterHandler->playControl(handler.localPlayerId, buttonIt->second, "", "", ""); +#else handler.adapterHandler->playControl(handler.localPlayerId, buttonIt->second, ""); +#endif } } }); @@ -1509,9 +1598,17 @@ void ExternalMediaPlayer::onTogglePressed(PlaybackToggle toggle, bool action) { if (maybeHandler.hasValue()) { const auto& handler = maybeHandler.value(); if (action) { +#ifdef MEDIA_PORTABILITY_ENABLED + handler.adapterHandler->playControl(handler.localPlayerId, toggleStates.first, "", "", ""); +#else handler.adapterHandler->playControl(handler.localPlayerId, toggleStates.first, ""); +#endif } else { +#ifdef MEDIA_PORTABILITY_ENABLED + handler.adapterHandler->playControl(handler.localPlayerId, toggleStates.second, "", "", ""); +#else handler.adapterHandler->playControl(handler.localPlayerId, toggleStates.second, ""); +#endif } } } diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/StaticExternalMediaPlayerAdapterHandler.cpp b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/StaticExternalMediaPlayerAdapterHandler.cpp index 6b7810a8b8..69c45d770f 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/StaticExternalMediaPlayerAdapterHandler.cpp +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/src/StaticExternalMediaPlayerAdapterHandler.cpp @@ -20,6 +20,7 @@ namespace alexaClientSDK { namespace acsdkExternalMediaPlayer { using acsdkExternalMediaPlayerInterfaces::PlayerInfo; +using HandlePlayParams = acsdkExternalMediaPlayerInterfaces::ExternalMediaAdapterInterface::HandlePlayParams; /// String to identify log entries originating from this file. static const std::string TAG("StaticExternalMediaPlayerAdapterHandler"); @@ -114,40 +115,38 @@ bool StaticExternalMediaPlayerAdapterHandler::logout(const std::string& localPla return true; } -bool StaticExternalMediaPlayerAdapterHandler::play( - const std::string& localPlayerId, - const std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const std::string& navigation, - bool preload, - const alexaClientSDK::avsCommon::avs::PlayRequestor& playRequestor, - const std::string& playbackTarget) { - auto player = getAdapterByLocalPlayerId(localPlayerId); +bool StaticExternalMediaPlayerAdapterHandler::play(const PlayParams& params) { + auto player = getAdapterByLocalPlayerId(params.localPlayerId); if (!player) { return false; } - ACSDK_DEBUG5(LX(__func__).d("localPlayerId", localPlayerId)); - std::string playContextTokenCopy = playContextToken; - player->handlePlay( - playContextTokenCopy, - index, - offset, - skillToken, - playbackSessionId, - navigation, - preload, - playRequestor, - playbackTarget); + ACSDK_DEBUG5(LX(__func__).d("localPlayerId", params.localPlayerId)); + HandlePlayParams handlePlayParams( + params.playContextToken, + params.index, + params.offset, + params.skillToken, + params.playbackSessionId, + params.navigation, + params.preload, + params.playRequestor, +#ifdef MEDIA_PORTABILITY_ENABLED + params.mediaSessionId, + params.correlationToken, +#endif + params.playbackTarget); + player->handlePlay(handlePlayParams); return true; } bool StaticExternalMediaPlayerAdapterHandler::playControl( const std::string& localPlayerId, acsdkExternalMediaPlayerInterfaces::RequestType requestType, +#ifdef MEDIA_PORTABILITY_ENABLED + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif const std::string& playbackTarget) { auto player = getAdapterByLocalPlayerId(localPlayerId); if (!player) { @@ -155,7 +154,11 @@ bool StaticExternalMediaPlayerAdapterHandler::playControl( } ACSDK_DEBUG5(LX(__func__).d("localPlayerId", localPlayerId).sensitive("playbackTarget", playbackTarget)); +#ifdef MEDIA_PORTABILITY_ENABLED + player->handlePlayControl(requestType, mediaSessionId, correlationToken, playbackTarget); +#else player->handlePlayControl(requestType, playbackTarget); +#endif return true; } @@ -220,4 +223,4 @@ void StaticExternalMediaPlayerAdapterHandler::setExternalMediaPlayer( } } // namespace acsdkExternalMediaPlayer -} // namespace alexaClientSDK \ No newline at end of file +} // namespace alexaClientSDK diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/test/ExternalMediaAdapterHandlerTest.cpp b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/test/ExternalMediaAdapterHandlerTest.cpp index 43cb393230..da1ff12361 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/test/ExternalMediaAdapterHandlerTest.cpp +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/test/ExternalMediaAdapterHandlerTest.cpp @@ -20,12 +20,14 @@ #include "acsdkExternalMediaPlayer/ExternalMediaPlayer.h" #include "acsdkExternalMediaPlayer/ExternalMediaAdapterHandler.h" +#include "acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterHandlerInterface.h" #include "acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterInterface.h" namespace alexaClientSDK { namespace acsdkExternalMediaPlayer { namespace test { +using PlayParams = acsdkExternalMediaPlayerInterfaces::ExternalMediaAdapterHandlerInterface::PlayParams; using namespace avsCommon::avs; using namespace ::testing; @@ -35,9 +37,15 @@ static const std::string PLAY_CONTEXT_TOKEN = "testContextToken"; static const std::string SKILL_TOKEN = "testSkillToken"; static const std::string SESSION_ID = "testSessionId"; static const std::string NAVIGATION_NONE = "NONE"; +static const std::string PLAYBACK_TARGET = "testPlaybackTarget"; static const PlayRequestor PLAY_REQUESTOR{.type = "ALERT", .id = "123"}; static const std::chrono::milliseconds PLAY_OFFSET{100}; +#ifdef MEDIA_PORTABILITY_ENABLED +static const std::string MEDIA_SESSION_ID = "testMediaSessionId"; +static const std::string CORRELATION_TOKEN = "testCorrelationToken"; +#endif + class MockExternalMediaPlayer : public ExternalMediaPlayer::ExternalMediaPlayerInterface { public: MOCK_METHOD1(setPlayerInFocus, void(const std::string& playerInFocus)); @@ -73,24 +81,20 @@ class MockExternalMediaAdapterHandler : public ExternalMediaAdapterHandler { bool forceLogin, std::chrono::milliseconds tokenRefreshInterval)); MOCK_METHOD1(handleLogout, bool(const std::string& localPlayerId)); - MOCK_METHOD10( - handlePlay, - bool( - const std::string& localPlayerId, - const std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const acsdkExternalMediaPlayerInterfaces::Navigation& navigation, - bool preload, - const alexaClientSDK::avsCommon::avs::PlayRequestor& playRequestor, - const std::string& playbackTarget)); + MOCK_METHOD1(handlePlay, bool(const PlayParams& params)); +#ifdef MEDIA_PORTABILITY_ENABLED + MOCK_METHOD5( +#else MOCK_METHOD3( +#endif handlePlayControl, bool( const std::string& localPlayerId, acsdkExternalMediaPlayerInterfaces::RequestType requestType, +#ifdef MEDIA_PORTABILITY_ENABLED + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif const std::string& playbackTarget)); MOCK_METHOD2(handleSeek, bool(const std::string& localPlayerId, std::chrono::milliseconds offset)); MOCK_METHOD2(handleAdjustSeek, bool(const std::string& localPlayerId, std::chrono::milliseconds deltaOffset)); @@ -187,30 +191,23 @@ TEST_F(ExternalMediaPlayerTest, testHandleLogout) { */ TEST_F(ExternalMediaPlayerTest, testHandlePlay) { authorizePlayer(); - EXPECT_CALL( - *m_externalMediaPlayerAdapterHandler, - handlePlay( - PLAYER_ID, - PLAY_CONTEXT_TOKEN, - 0, - PLAY_OFFSET, - SKILL_TOKEN, - SESSION_ID, - acsdkExternalMediaPlayerInterfaces::Navigation::NONE, - false, - PLAY_REQUESTOR, - "")); - m_externalMediaPlayerAdapterHandler->play( + const PlayParams params( PLAYER_ID, PLAY_CONTEXT_TOKEN, 0, PLAY_OFFSET, SKILL_TOKEN, SESSION_ID, - NAVIGATION_NONE, + acsdkExternalMediaPlayerInterfaces::Navigation::NONE, false, PLAY_REQUESTOR, +#ifdef MEDIA_PORTABILITY_ENABLED + MEDIA_SESSION_ID, + CORRELATION_TOKEN, +#endif ""); + EXPECT_CALL(*m_externalMediaPlayerAdapterHandler, handlePlay(_)); + m_externalMediaPlayerAdapterHandler->play(params); } /** @@ -220,9 +217,22 @@ TEST_F(ExternalMediaPlayerTest, testHandlePlayControl) { authorizePlayer(); EXPECT_CALL( *m_externalMediaPlayerAdapterHandler, - handlePlayControl(PLAYER_ID, acsdkExternalMediaPlayerInterfaces::RequestType::NONE, _)); + handlePlayControl( + PLAYER_ID, + acsdkExternalMediaPlayerInterfaces::RequestType::NONE, +#ifdef MEDIA_PORTABILITY_ENABLED + MEDIA_SESSION_ID, + CORRELATION_TOKEN, +#endif + PLAYBACK_TARGET)); m_externalMediaPlayerAdapterHandler->playControl( - PLAYER_ID, acsdkExternalMediaPlayerInterfaces::RequestType::NONE, ""); + PLAYER_ID, + acsdkExternalMediaPlayerInterfaces::RequestType::NONE, +#ifdef MEDIA_PORTABILITY_ENABLED + MEDIA_SESSION_ID, + CORRELATION_TOKEN, +#endif + PLAYBACK_TARGET); } /** diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp index b9370da891..3c6efbef3f 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp @@ -323,19 +323,18 @@ class MockExternalMediaPlayerAdapter : public ExternalMediaAdapterInterface { bool forceLogin, std::chrono::milliseconds tokenRefreshInterval)); MOCK_METHOD0(handleLogout, void()); - MOCK_METHOD9( - handlePlay, + MOCK_METHOD1(handlePlay, void(const HandlePlayParams& params)); +#ifdef MEDIA_PORTABILITY_ENABLED + MOCK_METHOD4( + handlePlayControl, void( - std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const std::string& navigation, - bool preload, - const avsCommon::avs::PlayRequestor& playRequestor, + RequestType requestType, + const std::string& mediaSessionId, + const std::string& correlationToken, const std::string& playbackTarget)); +#else MOCK_METHOD2(handlePlayControl, void(RequestType requestType, const std::string& playbackTarget)); +#endif MOCK_METHOD1(handleSeek, void(std::chrono::milliseconds offset)); MOCK_METHOD1(handleAdjustSeek, void(std::chrono::milliseconds deltaOffset)); MOCK_METHOD3( @@ -411,24 +410,20 @@ class MockExternalMediaAdapterHandler : public ExternalMediaAdapterHandler { bool forceLogin, std::chrono::milliseconds tokenRefreshInterval)); MOCK_METHOD1(handleLogout, bool(const std::string& localPlayerId)); - MOCK_METHOD10( - handlePlay, - bool( - const std::string& localPlayerId, - const std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const Navigation& navigation, - bool preload, - const alexaClientSDK::avsCommon::avs::PlayRequestor& playRequestor, - const std::string& playbackTarget)); + MOCK_METHOD1(handlePlay, bool(const PlayParams& params)); +#ifdef MEDIA_PORTABILITY_ENABLED + MOCK_METHOD5( +#else MOCK_METHOD3( +#endif handlePlayControl, bool( const std::string& localPlayerId, acsdkExternalMediaPlayerInterfaces::RequestType requestType, +#ifdef MEDIA_PORTABILITY_ENABLED + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif const std::string& playbackTarget)); MOCK_METHOD2(handleSeek, bool(const std::string& localPlayerId, std::chrono::milliseconds offset)); MOCK_METHOD2(handleAdjustSeek, bool(const std::string& localPlayerId, std::chrono::milliseconds deltaOffset)); @@ -459,6 +454,12 @@ class MockStartupNotifier : public acsdkStartupManagerInterfaces::StartupNotifie MOCK_METHOD1( removeObserver, void(const std::shared_ptr& observer)); + MOCK_METHOD1( + addWeakPtrObserver, + void(const std::weak_ptr& observer)); + MOCK_METHOD1( + removeWeakPtrObserver, + void(const std::weak_ptr& observer)); MOCK_METHOD1( notifyObservers, void(std::function&)>)); @@ -1654,9 +1655,7 @@ TEST_F(ExternalMediaPlayerTest, test_playNoOffset) { m_attachmentManager, ""); - EXPECT_CALL( - *(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), - handlePlay(_, _, _, _, _, _, _, PlayRequestor{}, _)); + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlay(_)); EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -1682,9 +1681,7 @@ TEST_F(ExternalMediaPlayerTest, testPlaywithPlayRequestor) { m_attachmentManager, ""); - EXPECT_CALL( - *(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), - handlePlay(_, _, _, _, _, _, _, testPlayRequestor, _)); + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlay(_)); EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -1709,8 +1706,7 @@ TEST_F(ExternalMediaPlayerTest, test_playNoIndex) { m_attachmentManager, ""); - EXPECT_CALL( - *(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlay(_, _, _, _, _, _, _, _, _)); + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlay(_)); EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -1885,7 +1881,11 @@ TEST_F(ExternalMediaPlayerTest, test_play) { EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif m_externalMediaPlayer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); m_externalMediaPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST); @@ -1903,7 +1903,11 @@ TEST_F(ExternalMediaPlayerTest, test_pause) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -1924,7 +1928,11 @@ TEST_F(ExternalMediaPlayerTest, testStop) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -1945,7 +1953,11 @@ TEST_F(ExternalMediaPlayerTest, test_stop) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -1966,7 +1978,11 @@ TEST_F(ExternalMediaPlayerTest, test_next) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -1987,7 +2003,11 @@ TEST_F(ExternalMediaPlayerTest, test_previous) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2008,7 +2028,11 @@ TEST_F(ExternalMediaPlayerTest, test_startOver) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2029,7 +2053,11 @@ TEST_F(ExternalMediaPlayerTest, test_rewind) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2050,7 +2078,11 @@ TEST_F(ExternalMediaPlayerTest, test_fastForward) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2071,7 +2103,11 @@ TEST_F(ExternalMediaPlayerTest, test_enableRepeatOne) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2092,7 +2128,11 @@ TEST_F(ExternalMediaPlayerTest, test_enableRepeat) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2113,7 +2153,11 @@ TEST_F(ExternalMediaPlayerTest, test_disableRepeat) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2134,7 +2178,11 @@ TEST_F(ExternalMediaPlayerTest, test_enableShuffle) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2155,7 +2203,11 @@ TEST_F(ExternalMediaPlayerTest, test_disableShuffle) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2176,7 +2228,11 @@ TEST_F(ExternalMediaPlayerTest, test_favorite) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); @@ -2197,7 +2253,11 @@ TEST_F(ExternalMediaPlayerTest, test_unfavorite) { std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, createPayloadWithPlayerId(MSP1_PLAYER_ID), m_attachmentManager, ""); +#ifdef MEDIA_PORTABILITY_ENABLED + EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _, _, _)); +#else EXPECT_CALL(*(MockExternalMediaPlayerAdapter::m_currentActiveMediaPlayerAdapter), handlePlayControl(_, _)); +#endif EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(1) .WillOnce(InvokeWithoutArgs(this, &ExternalMediaPlayerTest::wakeOnSetComplete)); diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/AdapterUtils.h b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/AdapterUtils.h index 59ec00fc70..a25844a606 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/AdapterUtils.h +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/AdapterUtils.h @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ACSDKEXTERNALMEDIAPLAYERINTERFACES_ADAPTERUTILS_H_ -#define ACSDKEXTERNALMEDIAPLAYERINTERFACES_ADAPTERUTILS_H_ +#ifndef ALEXA_CLIENT_SDK_ACSDKEXTERNALMEDIAPLAYERINTERFACES_INCLUDE_ACSDKEXTERNALMEDIAPLAYERINTERFACES_ADAPTERUTILS_H_ +#define ALEXA_CLIENT_SDK_ACSDKEXTERNALMEDIAPLAYERINTERFACES_INCLUDE_ACSDKEXTERNALMEDIAPLAYERINTERFACES_ADAPTERUTILS_H_ #include #include @@ -51,6 +51,41 @@ enum class AdapterEvent { PLAYER_ERROR_EVENT }; +/** + * Convert an AdapterEvent to string + * @param event Event to convert + * @return Event in string format + */ +inline std::string adapterEventToString(AdapterEvent event) { + switch (event) { + case AdapterEvent::CHANGE_REPORT: + return "CHANGE_REPORT"; + case AdapterEvent::REQUEST_TOKEN: + return "REQUEST_TOKEN"; + case AdapterEvent::LOGIN: + return "LOGIN"; + case AdapterEvent::LOGOUT: + return "LOGOUT"; + case AdapterEvent::PLAYER_EVENT: + return "PLAYER_EVENT"; + case AdapterEvent::PLAYER_ERROR_EVENT: + return "PLAYER_ERROR_EVENT"; + } + // To satisfy errant compiler warnings, returning empty string + return ""; +} + +/** + * Write a @c AdapterEvent to an @c ostream. + * + * @param stream The stream to write the value to. + * @param event The @c AdapterEvent 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 AdapterEvent& event) { + return stream << adapterEventToString(event); +} + /// Table with the retry times on subsequent retries for session management /// (token fetch/changeReport send). extern const std::vector SESSION_RETRY_TABLE; @@ -141,8 +176,45 @@ bool buildDefaultPlayerState(rapidjson::Value* document, rapidjson::Document::Al */ std::string getEmpContextString(acsdkExternalMediaPlayerInterfaces::AdapterState adapterState); +#ifdef MEDIA_PORTABILITY_ENABLED +/** + * Media portability mode + */ +enum class MpMode { + /// Legacy mode indicates that no media portability or media convergence is being used. + LEGACY, + /// Media convergence mode means all media playback is through one type of media player, + /// and media portability is not turned on + MEDIA_CONVERGENCE, + /// Media portability mode means both media convergence and media portability are turned on. + MEDIA_PORTABILITY +}; + +/** + * Method to check if the given request type includes a mediaSessionId. + * + * @param type RequestType to check + * @return Whether request type includes a mediaSessionId + */ +bool requestTypeIncludesMediaSessionId(RequestType type); + +/** + * Get the media portability mode. + * @return Media portability mode + */ +MpMode getMediaPortabilityMode(); + +/** + * Whether media portability is enabled + * + * @return True if media portability is enabled, false otherwise. + */ +bool mediaPortabilityEnabled(); + +#endif + } // namespace acsdkExternalMediaPlayerInterfaces } // namespace alexaClientSDK #endif // end -// ACSDKEXTERNALMEDIAPLAYERINTERFACES_ADAPTERUTILS_H_ +// ALEXA_CLIENT_SDK_ACSDKEXTERNALMEDIAPLAYERINTERFACES_INCLUDE_ACSDKEXTERNALMEDIAPLAYERINTERFACES_ADAPTERUTILS_H_ diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterConstants.h b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterConstants.h index 5279ed6f3e..2f24817bf9 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterConstants.h +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterConstants.h @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ACSDKEXTERNALMEDIAPLAYERINTERFACES_EXTERNALMEDIAADAPTERCONSTANTS_H_ -#define ACSDKEXTERNALMEDIAPLAYERINTERFACES_EXTERNALMEDIAADAPTERCONSTANTS_H_ +#ifndef ALEXA_CLIENT_SDK_ACSDKEXTERNALMEDIAPLAYERINTERFACES_INCLUDE_ACSDKEXTERNALMEDIAPLAYERINTERFACES_EXTERNALMEDIAADAPTERCONSTANTS_H_ +#define ALEXA_CLIENT_SDK_ACSDKEXTERNALMEDIAPLAYERINTERFACES_INCLUDE_ACSDKEXTERNALMEDIAPLAYERINTERFACES_EXTERNALMEDIAADAPTERCONSTANTS_H_ #include @@ -45,6 +45,10 @@ const char SPI_VERSION[] = "spiVersion"; const char PLAYER_COOKIE[] = "playerCookie"; const char SKILL_TOKEN[] = "skillToken"; const char PLAYBACK_SESSION_ID[] = "playbackSessionId"; +#ifdef MEDIA_PORTABILITY_ENABLED +const char MEDIA_SESSION_ID[] = "mediaSessionId"; +static const std::string CORRELATION_TOKEN = "correlationToken"; +#endif // The key values used in the context payload from External Media Player to AVS. const char STATE[] = "state"; @@ -79,4 +83,4 @@ const char VALUE[] = "value"; } // namespace acsdkExternalMediaPlayerInterfaces } // namespace alexaClientSDK -#endif // ACSDKEXTERNALMEDIAPLAYERINTERFACES_EXTERNALMEDIAADAPTERCONSTANTS_H_ +#endif // ALEXA_CLIENT_SDK_ACSDKEXTERNALMEDIAPLAYERINTERFACES_INCLUDE_ACSDKEXTERNALMEDIAPLAYERINTERFACES_EXTERNALMEDIAADAPTERCONSTANTS_H_ diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterHandlerInterface.h b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterHandlerInterface.h index 017ec6d5d8..4ed275803b 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterHandlerInterface.h +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterHandlerInterface.h @@ -20,6 +20,7 @@ #include #include +#include #include #include "acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterInterface.h" @@ -28,26 +29,11 @@ namespace alexaClientSDK { namespace acsdkExternalMediaPlayerInterfaces { +/// DEE-267369: Avoid cyclic dependency by having ExternalMediaPlayerInterface and +/// ExternalMediaAdapeExternalMediaAdapterHandlerInterface depend on a separate interface, +/// and limit the scope of adapters to change EMP. class ExternalMediaPlayerInterface; -/** - * Type of navigation when external media player app is first invoked via AVS - */ -enum class Navigation { - /** - * Source dependant behavior - */ - DEFAULT, - /** - * No navigation should occur - */ - NONE, - /** - * External app should take foreground - */ - FOREGROUND -}; - /** * The ExternalMediaAdapterHandlerInterface specifies the interface of adapter handler objects which interact with third * party music service providers. The adapter handler may handle multiple players distinguished by a different player ID @@ -71,6 +57,82 @@ class ExternalMediaAdapterHandlerInterface : public alexaClientSDK::avsCommon::u */ virtual ~ExternalMediaAdapterHandlerInterface() = default; + /// PlayParams is a struct that contains the parameters for the play method + struct PlayParams { + /// Local player id to play with + std::string localPlayerId; + /// Play context token for specifying what to play + std::string playContextToken; + /// Index for track + int64_t index; + /// Offset to play from + std::chrono::milliseconds offset; + /// Associated skillToken + std::string skillToken; + /// Playback session id for identifying the session + std::string playbackSessionId; + /// Navigation for indicating foreground or not + Navigation navigation; + /// Whether or not to preload first + bool preload; + /// PlayRequestor for indicating who requested playback + alexaClientSDK::avsCommon::avs::PlayRequestor playRequestor; +#ifdef MEDIA_PORTABILITY_ENABLED + /// mediaSessionId used to track media playback + std::string mediaSessionId; + /// correlationToken used to opaquely plumb routing info + std::string correlationToken; +#endif + /// Playback target to play on + std::string playbackTarget; + + /** + * Construct PlayParams + * + * @param localPlayerId The localPlayerId that this play control is targeted at + * @param playContextToken Play context {Track/playlist/album/artist/station/podcast} identifier. + * @param index The index of the media item in the container, if the container is indexable. + * @param offset The offset position within media item, in milliseconds. + * @param skillToken An opaque token for the domain or skill that is presently associated with this player. + * @param playbackSessionId A universally unique identifier (UUID) generated to the RFC 4122 specification. + * @param navigation Communicates desired visual display behavior for the app associated with playback. + * @param preload If true, this Play directive is intended to preload the identified content only but not begin + * playback. + * @param playRequestor The @c PlayRequestor object that is used to distinguish if it's a music alarm or not. + * @param playbackTarget Playback target to play + */ + PlayParams( + const std::string& localPlayerId, + const std::string& playContextToken, + int64_t index, + std::chrono::milliseconds offset, + const std::string& skillToken, + const std::string& playbackSessionId, + Navigation navigation, + bool preload, + const alexaClientSDK::avsCommon::avs::PlayRequestor& playRequestor, +#ifdef MEDIA_PORTABILITY_ENABLED + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif + const std::string& playbackTarget) : + localPlayerId{localPlayerId}, + playContextToken{playContextToken}, + index{index}, + offset{offset}, + skillToken{skillToken}, + playbackSessionId{playbackSessionId}, + navigation{navigation}, + preload{preload}, + playRequestor(playRequestor), +#ifdef MEDIA_PORTABILITY_ENABLED + mediaSessionId{mediaSessionId}, + correlationToken{correlationToken}, +#endif + playbackTarget{playbackTarget} { + } + }; + /** * Method to notify the handler that the cloud status of given players has been updated. * This method also provides the playerId and skillToken as identified by the cloud. The cloud support for a player @@ -109,30 +171,10 @@ class ExternalMediaAdapterHandlerInterface : public alexaClientSDK::avsCommon::u /** * Method to allow a user to initiate play from a third party music service provider based on a play context. * - * @param localPlayerId The localPlayerId that this play control is targeted at - * @param playContextToken Play context {Track/playlist/album/artist/station/podcast} identifier. - * @param index The index of the media item in the container, if the container is indexable. - * @param offset The offset position within media item, in milliseconds. - * @param skillToken An opaque token for the domain or skill that is presently associated with this player. - * @param playbackSessionId A universally unique identifier (UUID) generated to the RFC 4122 specification. - * @param navigation Communicates desired visual display behavior for the app associated with playback. - * @param preload If true, this Play directive is intended to preload the identified content only but not begin - * playback. - * @param playRequestor The @c PlayRequestor object that is used to distinguish if it's a music alarm or not. - * @param playbackTarget Playback target to play + * @param params Play parameters required for playback * @return True if the call was handled */ - virtual bool play( - const std::string& localPlayerId, - const std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const std::string& navigation, - bool preload, - const alexaClientSDK::avsCommon::avs::PlayRequestor& playRequestor, - const std::string& playbackTarget) = 0; + virtual bool play(const PlayParams& params) = 0; /** * Method to initiate the different types of play control like PLAY/PAUSE/RESUME/NEXT/... @@ -145,6 +187,12 @@ class ExternalMediaAdapterHandlerInterface : public alexaClientSDK::avsCommon::u virtual bool playControl( const std::string& localPlayerId, acsdkExternalMediaPlayerInterfaces::RequestType requestType, +#ifdef MEDIA_PORTABILITY_ENABLED + /// @param mediaSessionId The optional @c mediaSessionId used to track media playback + /// @param correlationToken The optional @c correlationToken used to opaquely plumb routing info + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif const std::string& playbackTarget) = 0; /** diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterInterface.h b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterInterface.h index 130403e725..35655aee71 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterInterface.h +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/include/acsdkExternalMediaPlayerInterfaces/ExternalMediaAdapterInterface.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -249,6 +250,72 @@ enum class MediaType { OTHER }; +/** + * Type of navigation when external media player app is first invoked via AVS + */ +enum class Navigation { + /** + * Source dependant behavior + */ + DEFAULT, + /** + * No navigation should occur + */ + NONE, + /** + * External app should take foreground + */ + FOREGROUND +}; + +/** + * Convert navigation enum to a string + * + * @param navigation Navigation to convert + * @return Navigation in string form + */ +inline std::string navigationToString(Navigation navigation) { + switch (navigation) { + case Navigation::DEFAULT: + return "DEFAULT"; + case Navigation::NONE: + return "NONE"; + case Navigation::FOREGROUND: + return "FOREGROUND"; + } + return ""; +} + +/** + * Write a @c Navigation to an @c ostream. + * + * @param stream The stream to write the value to. + * @param event The @c Navigation 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 Navigation& navigation) { + return stream << navigationToString(navigation); +} + +/** + * Convert the given string to a Navigation enum + * + * @param str String to convert + * @return Navigation enum + */ +inline Navigation stringToNavigation(const std::string& str) { + if (str == "DEFAULT") { + return Navigation::DEFAULT; + } else if (str == "NONE") { + return Navigation::NONE; + } else if (str == "FOREGROUND") { + return Navigation::FOREGROUND; + } else { + // default to DEFAULT + return Navigation::DEFAULT; + } +} + /** * struct that represents the session state of an adapter. */ @@ -311,6 +378,15 @@ struct AdapterSessionState { /// The validity period of the token in milliseconds. std::chrono::milliseconds tokenRefreshInterval; + +#ifdef MEDIA_PORTABILITY_ENABLED + /// A universally unique identifier (UUID) generated to the RFC 4122 + /// specification used to track media playback + std::string mediaSessionId; + + /// A identifier token used to opaquely plumb routing info from directives to events + std::string correlationToken; +#endif }; /** @@ -455,6 +531,86 @@ class ExternalMediaAdapterInterface : public virtual avsCommon::utils::RequiresS */ virtual ~ExternalMediaAdapterInterface() = default; + /** + * HandlePlayParams is a struct that contains the parameters for the handlePlay method + */ + struct HandlePlayParams { + /// Play context token for specifying what to play + std::string playContextToken; + /// Index for track + int64_t index; + /// Offset to play from + std::chrono::milliseconds offset; + /// Associated skillToken + std::string skillToken; + /// Playback session id for identifying the session + std::string playbackSessionId; + /// Navigation for indicating foreground or not + Navigation navigation; + /// Whether or not to preload first + bool preload; + /// PlayRequestor for indicating who requested playback + avsCommon::avs::PlayRequestor playRequestor; +#ifdef MEDIA_PORTABILITY_ENABLED + /// mediaSessionId used to track media playback + std::string mediaSessionId; + /// correlationToken used to opaquely plumb routing info + std::string correlationToken; +#endif + /// Playback target to play on + std::string playbackTarget; + + /** + * Construct HandlePlayParams + * + * @param playContextToken Play context + * {Track/playlist/album/artist/station/podcast} identifier. + * @param index The index of the media item in the container, if the container + * is indexable. + * @param offset The offset position within media item, in milliseconds. + * @param skillToken An opaque token for the domain or skill that is presently + * associated with this player. + * @param playbackSessionId A universally unique identifier (UUID) generated + * to the RFC 4122 specification. + * @param navigation Communicates desired visual display behavior for the app + * associated with playback. + * @param preload If true, this Play directive is intended to preload the + * identified content only but not begin playback. + * @param playRequestor The @c PlayRequestor object that is used to + * distinguish if it's a music alarm or not. + * @param playbackTarget The @c PlaybackTarget is used to specify the targeted device + * that will handle the playback action. + */ + HandlePlayParams( + const std::string& playContextToken, + int64_t index, + std::chrono::milliseconds offset, + const std::string& skillToken, + const std::string& playbackSessionId, + Navigation navigation, + bool preload, + const avsCommon::avs::PlayRequestor& playRequestor, +#ifdef MEDIA_PORTABILITY_ENABLED + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif + const std::string& playbackTarget) : + playContextToken{playContextToken}, + index{index}, + offset{offset}, + skillToken{skillToken}, + playbackSessionId{playbackSessionId}, + navigation{navigation}, + preload{preload}, + playRequestor(playRequestor), +#ifdef MEDIA_PORTABILITY_ENABLED + mediaSessionId{mediaSessionId}, + correlationToken{correlationToken}, +#endif + playbackTarget{playbackTarget} { + } + }; + /// Method to initialize a third party library. virtual void init() = 0; @@ -486,45 +642,28 @@ class ExternalMediaAdapterInterface : public virtual avsCommon::utils::RequiresS * Method to allow a user to initiate play from a third party music service * provider based on a play context. * - * @param playContextToken Play context - * {Track/playlist/album/artist/station/podcast} identifier. - * @param index The index of the media item in the container, if the container - * is indexable. - * @param offset The offset position within media item, in milliseconds. - * @param skillToken An opaque token for the domain or skill that is presently - * associated with this player. - * @param playbackSessionId A universally unique identifier (UUID) generated - * to the RFC 4122 specification. - * @param navigation Communicates desired visual display behavior for the app - * associated with playback. - * @param preload If true, this Play directive is intended to preload the - * identified content only but not begin playback. - * @param playRequestor The @c PlayRequestor object that is used to - * distinguish if it's a music alarm or not. - * @param playbackTarget The @c PlaybackTarget is used to specify the targeted device - * that will handle the playback action. + * @param params Handle play parameters required for playback */ - virtual void handlePlay( - std::string& playContextToken, - int64_t index, - std::chrono::milliseconds offset, - const std::string& skillToken, - const std::string& playbackSessionId, - const std::string& navigation, - bool preload, - const avsCommon::avs::PlayRequestor& playRequestor, - const std::string& playbackTarget) = 0; + virtual void handlePlay(const HandlePlayParams& params) = 0; /** * Method to initiate the different types of play control like * PLAY/PAUSE/RESUME/NEXT/... * - * @param requestType The type of REQUEST. Will always be - * PLAY/PAUSE/RESUME/NEXT... + * @param requestType The type of REQUEST. Will always be PLAY/PAUSE/RESUME/NEXT... * @param playbackTarget The @c PlaybackTarget is used to specify the targeted device * that will handle the play control. + * @param isLocal Whether play control is locally triggered on device */ - virtual void handlePlayControl(RequestType requestType, const std::string& playbackTarget) = 0; + virtual void handlePlayControl( + RequestType requestType, +#ifdef MEDIA_PORTABILITY_ENABLED + /// @param mediaSessionId The optional @c mediaSessionId used to track media playback + /// @param correlationToken The optional @c correlationToken used to opaquely plumb routing info + const std::string& mediaSessionId, + const std::string& correlationToken, +#endif + const std::string& playbackTarget) = 0; /** * Method to seek to the given offset. diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/src/AdapterUtils.cpp b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/src/AdapterUtils.cpp index fcc257d9a4..ff545697a2 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/src/AdapterUtils.cpp +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/src/AdapterUtils.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include @@ -259,5 +258,31 @@ std::string getEmpContextString(AdapterState adapterState) { return buffer.GetString(); } +#ifdef MEDIA_PORTABILITY_ENABLED + +bool requestTypeIncludesMediaSessionId(RequestType type) { + static const std::set requestTypesWithMediaSessionId{RequestType::RESUME, + RequestType::START_OVER, + RequestType::PREVIOUS, + RequestType::NEXT, + RequestType::REWIND, + RequestType::FAST_FORWARD, + RequestType::STOP, + RequestType::PAUSE}; + + return requestTypesWithMediaSessionId.count(type) > 0; +} + +MpMode getMediaPortabilityMode() { + // TODO: Update MP mode based on system property + return MpMode::LEGACY; +} + +bool mediaPortabilityEnabled() { + return getMediaPortabilityMode() == MpMode::MEDIA_PORTABILITY; +} + +#endif + } // namespace acsdkExternalMediaPlayerInterfaces } // namespace alexaClientSDK diff --git a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/src/CMakeLists.txt b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/src/CMakeLists.txt index 585663155b..d5562e3a29 100644 --- a/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/src/CMakeLists.txt +++ b/capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/src/CMakeLists.txt @@ -1,6 +1,6 @@ include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) -add_library(acsdkExternalMediaPlayerInterfaces SHARED +add_library(acsdkExternalMediaPlayerInterfaces AdapterUtils.cpp) target_include_directories(acsdkExternalMediaPlayerInterfaces PUBLIC diff --git a/capabilities/InputController/.clang-format b/capabilities/InputController/.clang-format new file mode 100644 index 0000000000..3d1ec24482 --- /dev/null +++ b/capabilities/InputController/.clang-format @@ -0,0 +1,102 @@ +--- +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +#AlignConsecutiveAssignments: false +#AlignConsecutiveDeclarations: false +#AlignEscapedNewlines: Left +#AlignOperands: true +#AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +#AllowShortBlocksOnASingleLine: false +#AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +#AllowShortIfStatementsOnASingleLine: true +#AllowShortLoopsOnASingleLine: true +#AlwaysBreakAfterDefinitionReturnType: None +#AlwaysBreakAfterReturnType: None +#AlwaysBreakBeforeMultilineStrings: true +#AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +#BraceWrapping: None +# AfterClass: false +# AfterControlStatement: false +# AfterEnum: false +# AfterFunction: false +# AfterNamespace: false +# AfterObjCDeclaration: false +# AfterStruct: false +# AfterUnion: false +# BeforeCatch: false +# BeforeElse: false +# IndentBraces: false +# SplitEmptyFunctionBody: true +#BreakBeforeBinaryOperators: None +#BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: true +#BreakBeforeTernaryOperators: true +#BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +#BreakAfterJavaFieldAnnotations: false +#BreakStringLiterals: true +ColumnLimit: 120 +#CommentPragmas: '^ IWYU pragma:' +#CompactNamespaces: false +#ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 8 +#ContinuationIndentWidth: 4 +#Cpp11BracedListStyle: true +DerivePointerAlignment: false +#DisableFormat: false +#ExperimentalAutoDetectBinPacking: false +#FixNamespaceComments: true +#ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +#IncludeCategories: +# - Regex: '^<.*\.h>' +# Priority: 1 +# - Regex: '^<.*' +# Priority: 2 +# - Regex: '.*' +# Priority: 3 +#IncludeIsMainRegex: '([-_](test|unittest))?$' +#IndentCaseLabels: true +IndentWidth: 4 +#IndentWrappedFunctionNames: false +#JavaScriptQuotes: Leave +#JavaScriptWrapImports: true +#KeepEmptyLinesAtTheStartOfBlocks: false +#MacroBlockBegin: '' +#MacroBlockEnd: '' +#MaxEmptyLinesToKeep: 1 +#NamespaceIndentation: None +#ObjCBlockIndentWidth: 2 +#ObjCSpaceAfterProperty: false +#ObjCSpaceBeforeProtocolList: false +#PenaltyBreakAssignment: 2 +#PenaltyBreakBeforeFirstCallParameter: 1 +#PenaltyBreakComment: 300 +#PenaltyBreakFirstLessLess: 120 +#PenaltyBreakString: 1000 +#PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 20000 +#PointerAlignment: Left +#ReflowComments: true +SortIncludes: false +#SpaceAfterCStyleCast: false +#SpaceAfterTemplateKeyword: true +#SpaceBeforeAssignmentOperators: true +#SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +#SpacesBeforeTrailingComments: 2 +#SpacesInAngles: false +#SpacesInContainerLiterals: true +#SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +#Standard: Auto +#TabWidth: 8 +#UseTab: Never +... + diff --git a/capabilities/InputController/CMakeLists.txt b/capabilities/InputController/CMakeLists.txt new file mode 100644 index 0000000000..0f220330bf --- /dev/null +++ b/capabilities/InputController/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +add_subdirectory("acsdkInputController") +add_subdirectory("acsdkInputControllerInterfaces") diff --git a/capabilities/InputController/acsdkInputController/CMakeLists.txt b/capabilities/InputController/acsdkInputController/CMakeLists.txt new file mode 100644 index 0000000000..e2c26f87da --- /dev/null +++ b/capabilities/InputController/acsdkInputController/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.0) +if(INPUT_CONTROLLER) + project(acsdkInputController LANGUAGES CXX) + include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + add_subdirectory("src") + add_subdirectory("test") +endif() \ No newline at end of file diff --git a/capabilities/InputController/acsdkInputController/include/acsdkInputController/InputControllerFactory.h b/capabilities/InputController/acsdkInputController/include/acsdkInputController/InputControllerFactory.h new file mode 100644 index 0000000000..5fc479951d --- /dev/null +++ b/capabilities/InputController/acsdkInputController/include/acsdkInputController/InputControllerFactory.h @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKINPUTCONTROLLER_INPUTCONTROLLERFACTORY_H_ +#define ACSDKINPUTCONTROLLER_INPUTCONTROLLERFACTORY_H_ + +#include + +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkInputController { + +/// This structure contains the interfaces to interact with the InputController Capability Agent. +struct InputControllerFactoryInterfaces { + /// Interface for handling @c AVSDirectives. + std::shared_ptr directiveHandler; + + /// Interface providing CapabilitiesDelegate access to the version and configurations of the capabilities. + std::shared_ptr + capabilityConfigurationInterface; +}; + +/** + * Creates a new InputController capability agent + * + * @param handler The handler for InputController input updates. + * @param exceptionSender The object to use for sending AVS Exception messages. + * @return An @c Optional @c InputControllerFactoryInterfaces object. + */ +avsCommon::utils::Optional create( + const std::shared_ptr& handler, + const std::shared_ptr& exceptionSender); + +} // namespace acsdkInputController +} // namespace alexaClientSDK + +#endif // ACSDKINPUTCONTROLLER_INPUTCONTROLLERFACTORY_H_ diff --git a/capabilities/InputController/acsdkInputController/src/CMakeLists.txt b/capabilities/InputController/acsdkInputController/src/CMakeLists.txt new file mode 100644 index 0000000000..bbf883def1 --- /dev/null +++ b/capabilities/InputController/acsdkInputController/src/CMakeLists.txt @@ -0,0 +1,19 @@ +add_definitions("-DACSDK_LOG_MODULE=acsdkInputController") + +set(InputController_SOURCES) +list(APPEND InputController_SOURCES + InputControllerCapabilityAgent.cpp + InputControllerFactory.cpp) + +add_library(acsdkInputController + "${InputController_SOURCES}") + +target_include_directories(acsdkInputController PUBLIC + "${acsdkInputController_SOURCE_DIR}/include") + +target_link_libraries(acsdkInputController + acsdkInputControllerInterfaces + AVSCommon) + +# install target +asdk_install() diff --git a/capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.cpp b/capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.cpp new file mode 100644 index 0000000000..0acfb4ae71 --- /dev/null +++ b/capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.cpp @@ -0,0 +1,296 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "InputControllerCapabilityAgent.h" + +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkInputController { + +using namespace rapidjson; +using namespace acsdkInputControllerInterfaces; +using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils; +using namespace json::jsonUtils; + +/// String to identify log entries originating from this file. +static const std::string TAG{"InputController"}; + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) logger::LogEntry(TAG, event) + +/// The namespace for this capability agent. +static const std::string NAMESPACE = "Alexa.InputController"; + +/// The SelectInput directive signature. +static const NamespaceAndName SELECT_INPUT{NAMESPACE, "SelectInput"}; + +/// InputController capability constants +/// The AlexaInterface constant type. +static const std::string ALEXA_INTERFACE_TYPE = "AlexaInterface"; + +/// Interface name +static const std::string INPUT_CONTROLLER_CAPABILITY_INTERFACE_NAME = "Alexa.InputController"; + +/// Interface version. +static const std::string INPUT_CONTROLLER_CAPABILITY_INTERFACE_VERSION = "3.0"; + +/// The configuration key +static const std::string CAPABILITY_CONFIGURATION_KEY{"configurations"}; + +/// Payload input key +static const std::string INPUT_CONTROLLER_INPUT_KEY = "input"; + +/// Payload inputs key +static const std::string INPUT_CONTROLLER_CONFIGURATION_KEY = "inputs"; + +/// Payload name key +static const std::string INPUT_CONTROLLER_CONFIGURATION_NAME_KEY = "name"; + +/// Payload friendlyNames key +static const std::string INPUT_CONTROLLER_CONFIGURATION_FRIENDLY_NAMES_KEY = "friendlyNames"; + +/** + * A helper function to check if the input configurations from the handler is valid. + * + * @param inputConfigurations The @c inputs and its friendly names. + * @return true if valid, false otherwise. + */ +static bool checkInputs(const InputControllerCapabilityAgent::InputFriendlyNameConfigurations& inputConfigurations) { + std::unordered_map friendlyNamesCheck; + if (inputConfigurations.empty()) { + ACSDK_ERROR(LX("checkInputsFailed").d("reason", "emptyInputConfig")); + return false; + } + for (const auto& input : inputConfigurations) { + for (const auto& friendlyName : input.second) { + if (0 == friendlyNamesCheck.count(friendlyName)) { + friendlyNamesCheck[friendlyName] = input.first; + } else { + ACSDK_ERROR(LX("checkInputsFailed") + .d("reason", "friendlyNameExistsInTwoInputs") + .d("friendlyName", friendlyName) + .d("input1", friendlyNamesCheck[friendlyName]) + .d("input2", input.first)); + return false; + } + } + } + return true; +} + +/** + * A helper function to generate the @c CapabilityConfiguration based on the @c InputConfigurations. + * + * @param inputConfigurations The @c InputConfigurations to read from. + * @return A @c std::shared_ptr to a @c CapabilityConfiguration. + */ +static std::shared_ptr getInputControllerCapabilityConfiguration( + const InputControllerCapabilityAgent::InputFriendlyNameConfigurations& inputConfigurations) { + avsCommon::utils::json::JsonGenerator jsonGenerator; + + jsonGenerator.startArray(INPUT_CONTROLLER_CONFIGURATION_KEY); + for (const auto& input : inputConfigurations) { + jsonGenerator.startArrayElement(); + jsonGenerator.addMember(INPUT_CONTROLLER_CONFIGURATION_NAME_KEY, input.first); + jsonGenerator.addStringArray(INPUT_CONTROLLER_CONFIGURATION_FRIENDLY_NAMES_KEY, input.second); + jsonGenerator.finishArrayElement(); + } + jsonGenerator.finishArray(); + + auto additionalConfigurations = CapabilityConfiguration::AdditionalConfigurations(); + additionalConfigurations[CAPABILITY_CONFIGURATION_KEY] = jsonGenerator.toString(); + + auto configuration = std::make_shared( + ALEXA_INTERFACE_TYPE, + INPUT_CONTROLLER_CAPABILITY_INTERFACE_NAME, + INPUT_CONTROLLER_CAPABILITY_INTERFACE_VERSION, + avsCommon::utils::Optional(), + avsCommon::utils::Optional(), + additionalConfigurations); + + return configuration; +} + +std::shared_ptr InputControllerCapabilityAgent::create( + const std::shared_ptr& handler, + const std::shared_ptr& exceptionSender) { + if (!handler) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullHandler")); + return nullptr; + } + if (!exceptionSender) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullExceptionSender")); + return nullptr; + } + if (!checkInputs(handler->getConfiguration().inputs)) { + ACSDK_ERROR(LX("createFailed").d("reason", "invalidInputs")); + return nullptr; + } + auto inputControllerCapabilityAgent = + std::shared_ptr(new InputControllerCapabilityAgent(handler, exceptionSender)); + return inputControllerCapabilityAgent; +} + +InputControllerCapabilityAgent::InputControllerCapabilityAgent( + const std::shared_ptr& handler, + const std::shared_ptr& exceptionSender) : + CapabilityAgent{NAMESPACE, exceptionSender}, + m_inputControllerHandler{handler} { + ACSDK_DEBUG5(LX(__func__)); + m_inputConfigurations = handler->getConfiguration().inputs; + m_capabilityConfigurations.insert(getInputControllerCapabilityConfiguration(m_inputConfigurations)); +} + +DirectiveHandlerConfiguration InputControllerCapabilityAgent::getConfiguration() const { + DirectiveHandlerConfiguration configuration; + configuration[SELECT_INPUT] = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); + return configuration; +} + +void InputControllerCapabilityAgent::handleDirectiveImmediately(std::shared_ptr directive) { + ACSDK_DEBUG5(LX(__func__)); + handleDirective(std::make_shared(directive, nullptr)); +} + +void InputControllerCapabilityAgent::preHandleDirective(std::shared_ptr info) { + // No-op +} + +bool InputControllerCapabilityAgent::executeHandleDirectiveHelper( + std::shared_ptr info, + std::string* errMessage, + ExceptionErrorType* type) { + ACSDK_DEBUG5(LX(__func__)); + if (!errMessage) { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", "nullErrMessage")); + return false; + } + if (!type) { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", "nullType")); + return false; + } + if (!info) { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", "nullInfo")); + return false; + } + if (!info->directive) { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", "nullDirective")); + return false; + } + + const std::string directiveName = info->directive->getName(); + + Document payload(kObjectType); + ParseResult result = payload.Parse(info->directive->getPayload()); + if (!result) { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", "directiveParseFailed")); + *errMessage = "Parse failure"; + *type = ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED; + return false; + } + + if (SELECT_INPUT.name == directiveName) { + rapidjson::Value::ConstMemberIterator it; + std::string input; + if (!retrieveValue(payload, INPUT_CONTROLLER_INPUT_KEY, &input)) { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", "missingInputField")); + *errMessage = "Input field is not accessible"; + *type = ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED; + return false; + } + if (input.empty()) { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", "inputIsEmptyString")); + *errMessage = "Input is an Empty String"; + *type = ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED; + return false; + } + if (0 == m_inputConfigurations.count(input)) { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", "InvalidInputReceived")); + *errMessage = "Input is invalid"; + *type = ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED; + return false; + } + + ACSDK_INFO(LX("inputControllerNotifier").d("input", input)); + if (!m_inputControllerHandler->onInputChange(input)) { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", "onInputChangeFailed")); + *errMessage = "Input change failed"; + *type = ExceptionErrorType::INTERNAL_ERROR; + return false; + } + } else { + *errMessage = directiveName + " not supported"; + *type = ExceptionErrorType::UNSUPPORTED_OPERATION; + return false; + } + + return true; +} + +void InputControllerCapabilityAgent::handleDirective(std::shared_ptr info) { + ACSDK_DEBUG5(LX(__func__)); + if (!info) { + ACSDK_ERROR(LX("handleDirectiveFailed").d("reason", "nullInfo")); + return; + } + + if (!info->directive) { + ACSDK_ERROR(LX("handleDirectiveFailed").d("reason", "nullDirective")); + return; + } + + m_executor.submit([this, info] { + std::string errMessage; + ExceptionErrorType errType; + + if (executeHandleDirectiveHelper(info, &errMessage, &errType)) { + if (info->result) { + info->result->setCompleted(); + } + } else { + ACSDK_ERROR(LX("processDirectiveFailed").d("reason", errMessage)); + m_exceptionEncounteredSender->sendExceptionEncountered( + info->directive->getUnparsedDirective(), errType, errMessage); + if (info->result) { + info->result->setFailed(errMessage); + } + } + removeDirective(info->directive->getMessageId()); + }); +} + +void InputControllerCapabilityAgent::cancelDirective(std::shared_ptr info) { + // No-op +} + +std::unordered_set> InputControllerCapabilityAgent:: + getCapabilityConfigurations() { + return m_capabilityConfigurations; +} + +} // namespace acsdkInputController +} // namespace alexaClientSDK diff --git a/capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.h b/capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.h new file mode 100644 index 0000000000..e33c6a31b6 --- /dev/null +++ b/capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.h @@ -0,0 +1,124 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKINPUTCONTROLLER_SRC_INPUTCONTROLLERCAPABILITYAGENT_H_ +#define ACSDKINPUTCONTROLLER_SRC_INPUTCONTROLLERCAPABILITYAGENT_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkInputController { + +/** + * The Input Controller Capability Agent provides an implementation for a client to interface with the + * Alexa.InputController API. + * + * @see https://developer.amazon.com/en-US/docs/alexa/alexa-voice-service/inputcontroller.html + * + * AVS sends SelectInput directives to the device to switch the input. + * + */ +class InputControllerCapabilityAgent + : public avsCommon::avs::CapabilityAgent + , public avsCommon::sdkInterfaces::CapabilityConfigurationInterface { +public: + /// Alias for brevity + using InputFriendlyNameConfigurations = + acsdkInputControllerInterfaces::InputControllerHandlerInterface::InputFriendlyNameType; + + /** + * Destructor. + */ + virtual ~InputControllerCapabilityAgent() = default; + + /** + * Creates an instance of the @c InputControllerCapabilityAgent. + * + * @param handler The handler for InputController input updates. + * @param exceptionSender The object to use for sending AVS Exception messages. + * @return A @c std::shared_ptr to the new @c InputControllerCapabilityAgent instance, a nullptr if failed. + */ + static std::shared_ptr create( + const std::shared_ptr& handler, + const std::shared_ptr& exceptionSender); + + /// @name CapabilityAgent Functions + /// @{ + avsCommon::avs::DirectiveHandlerConfiguration getConfiguration() const override; + void handleDirectiveImmediately(std::shared_ptr directive) override; + void preHandleDirective(std::shared_ptr info) override; + void handleDirective(std::shared_ptr info) override; + void cancelDirective(std::shared_ptr info) override; + /// @} + + /// @name CapabilityConfigurationInterface Functions + /// @{ + std::unordered_set> getCapabilityConfigurations() override; + /// @} + +private: + /** + * Constructor. + * + * @param handler The handler for InputController input updates. + * @param exceptionSender The object to use for sending AVS Exception messages. + */ + InputControllerCapabilityAgent( + const std::shared_ptr& handler, + const std::shared_ptr& exceptionSender); + + /** + * Helper function to process the incoming directive + * + * @param info Directive to be processed. + * @param[out] errMessage Error message associated to failure to process the directive + * @param[out] type Error type associated to failure to process the directive + * @return A bool indicating the success of processing the directive + */ + bool executeHandleDirectiveHelper( + std::shared_ptr info, + std::string* errMessage, + avsCommon::avs::ExceptionErrorType* type); + + /// Set of capability configurations that will get published using Capabilities API + std::unordered_set> m_capabilityConfigurations; + + /// The object to handle input change events. + const std::shared_ptr m_inputControllerHandler; + + /// The configuration of the inputs that is read from the configuration file. + InputFriendlyNameConfigurations m_inputConfigurations; + + /** + * @c Executor which queues up operations from asynchronous API calls. + * + * @note This declaration needs to come *after* the Executor Thread Variables above so that the thread shuts down + * before the Executor Thread Variables are destroyed. + */ + avsCommon::utils::threading::Executor m_executor; +}; + +} // namespace acsdkInputController +} // namespace alexaClientSDK + +#endif // ACSDKINPUTCONTROLLER_SRC_INPUTCONTROLLERCAPABILITYAGENT_H_ diff --git a/capabilities/InputController/acsdkInputController/src/InputControllerFactory.cpp b/capabilities/InputController/acsdkInputController/src/InputControllerFactory.cpp new file mode 100644 index 0000000000..5b207f9097 --- /dev/null +++ b/capabilities/InputController/acsdkInputController/src/InputControllerFactory.cpp @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "InputControllerCapabilityAgent.h" +#include "acsdkInputController/InputControllerFactory.h" + +namespace alexaClientSDK { +namespace acsdkInputController { + +using namespace avsCommon::utils; + +Optional create( + const std::shared_ptr& handler, + const std::shared_ptr& exceptionSender) { + auto inputControllerCA = InputControllerCapabilityAgent::create(handler, exceptionSender); + if (!inputControllerCA) { + return Optional(); + } + InputControllerFactoryInterfaces interfaces; + interfaces.capabilityConfigurationInterface = inputControllerCA; + interfaces.directiveHandler = inputControllerCA; + return Optional(interfaces); +} + +} // namespace acsdkInputController +} // namespace alexaClientSDK diff --git a/capabilities/InputController/acsdkInputController/test/CMakeLists.txt b/capabilities/InputController/acsdkInputController/test/CMakeLists.txt new file mode 100644 index 0000000000..a78cdae127 --- /dev/null +++ b/capabilities/InputController/acsdkInputController/test/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +set(INCLUDE_PATH + "${acsdkInputController_SOURCE_DIR}/include") + +set(LIBS + "acsdkInputController" + "SDKInterfacesTests") + +discover_unit_tests("${INCLUDE_PATH}" "${LIBS}") diff --git a/capabilities/InputController/acsdkInputController/test/InputControllerCapabilityAgentTest.cpp b/capabilities/InputController/acsdkInputController/test/InputControllerCapabilityAgentTest.cpp new file mode 100644 index 0000000000..228ed7723b --- /dev/null +++ b/capabilities/InputController/acsdkInputController/test/InputControllerCapabilityAgentTest.cpp @@ -0,0 +1,272 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkInputController { +namespace test { + +using namespace ::testing; +using namespace acsdkInputControllerInterfaces; +using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils; +using namespace avsCommon::utils::configuration; + +/// A simple SelectInput directive JSON string. +static const std::string SELECT_INPUT_DIRECTIVE_JSON_STRING = R"delim( + { + "directive": { + "header": { + "namespace": "Alexa.InputController", + "name": "SelectInput", + "messageId": "12345", + "dialogRequestId": "2" + }, + "payload": { + "input": "HDMI1" + } + } + } +)delim"; + +/// A SelectInput directive with invalid input (i.e. not part of the configuration as specified in @c +/// INPUT_CONTROLLER_CONFIG_JSON) +static const std::string SELECT_INPUT_DIRECTIVE_INVALID_INPUT_JSON_STRING = R"delim( + { + "directive": { + "header": { + "namespace": "Alexa.InputController", + "name": "SelectInput", + "messageId": "12345", + "dialogRequestId": "2" + }, + "payload": { + "input": "RCA" + } + } + } +)delim"; + +/// A SelectInput directive without input field +static const std::string SELECT_INPUT_MISSING_TAG_DIRECTIVE_JSON_STRING = R"delim( + { + "directive": { + "header": { + "namespace": "Alexa.InputController", + "name": "SelectInput", + "messageId": "12345", + "dialogRequestId": "2" + }, + "payload": { + } + } + } +)delim"; + +/// A SelectInput directive with an empty input +static const std::string SELECT_INPUT_EMPTY_INPUT_DIRECTIVE_JSON_STRING = R"delim( + { + "directive": { + "header": { + "namespace": "Alexa.InputController", + "name": "SelectInput", + "messageId": "12345", + "dialogRequestId": "2" + }, + "payload": { + "input": "" + } + } + } +)delim"; + +/// The valid input configuration used for testing +static const InputControllerHandlerInterface::InputFriendlyNameType INPUT_CONTROLLER_GOOD_CONFIG{ + {"HDMI1", {"TV", "Television"}}, + {"HDMI2", {"Game Console"}}}; + +/// An invalid input configuration with duplicated friendly names across inputs +static const InputControllerHandlerInterface::InputFriendlyNameType INPUT_CONTROLLER_DUPLICATE_FRIENDLY_NAMES_CONFIG{ + {"HDMI1", {"TV", "Television"}}, + {"HDMI2", {"TV"}}}; + +/// An invalid input configuration with no inputs +static const InputControllerHandlerInterface::InputFriendlyNameType INPUT_CONTROLLER_EMPTY_CONFIG{}; + +// Timeout to wait before indicating a test failed. +static const std::chrono::milliseconds TIMEOUT{500}; + +/// A Mock for @c InputControllerHandlerInterface +class MockHandler : public InputControllerHandlerInterface { +public: + /// @name InputControllerHandlerInterface Functions + /// @{ + InputConfigurations getConfiguration() override; + bool onInputChange(const std::string& input) override; + /// @} + + /** + * Waits for the onInputChange() function to be called. + * + * @return An @c Optional of input. If callback is before timeout, the Optional will be filled with input, + * otherwise and Empty optional if there was no callback. + */ + Optional waitOnInputChange(); + + /// Ctor + MockHandler(InputControllerHandlerInterface::InputFriendlyNameType inputConfigs) : m_inputsConfig{inputConfigs} {}; + +protected: + std::mutex m_mutex; + std::condition_variable m_cond; + bool m_inputCalled = false; + std::string m_input; + InputControllerHandlerInterface::InputFriendlyNameType m_inputsConfig; +}; + +bool MockHandler::onInputChange(const std::string& input) { + std::lock_guard lock(m_mutex); + m_input = input; + m_inputCalled = true; + m_cond.notify_all(); + return true; +} + +InputControllerHandlerInterface::InputConfigurations MockHandler::getConfiguration() { + return {m_inputsConfig}; +} + +Optional MockHandler::waitOnInputChange() { + std::unique_lock lock(m_mutex); + m_cond.wait_for(lock, TIMEOUT, [this] { return m_inputCalled; }); + if (m_inputCalled) { + return Optional(m_input); + } + return Optional(); +} + +/// Test harness for @c InputControllerCapabilityAgentTest class. +class InputControllerCapabilityAgentTest : public Test { +public: + /// Set up the test harness for running a test. + void SetUp() override; + +protected: + /// The InputControllerCapabilityAgent instance to be tested + InputControllerFactoryInterfaces m_inputControllerCA; + + /// The mock handler for testing + std::shared_ptr m_mockHandler; + + /// The mock @c ExceptionEncounteredSenderInterface. + std::shared_ptr m_mockExceptionEncounteredSender; +}; + +void InputControllerCapabilityAgentTest::SetUp() { + m_mockExceptionEncounteredSender = + std::make_shared(); + + m_mockHandler = std::make_shared(INPUT_CONTROLLER_GOOD_CONFIG); + auto inputControllerCA = create(m_mockHandler, m_mockExceptionEncounteredSender); + + ASSERT_TRUE(inputControllerCA.hasValue()); + m_inputControllerCA = inputControllerCA.value(); +} + +/** + * Test to verify the @c InputControllerCapabilityAgent cannot be created if handler param is null. + */ +TEST_F(InputControllerCapabilityAgentTest, test_createNoHandlerFail) { + auto inputControllerCA = create(nullptr, m_mockExceptionEncounteredSender); + ASSERT_FALSE(inputControllerCA.hasValue()); +} + +/** + * Test to verify the @c InputControllerCapabilityAgent cannot be created if exceptionHandler param is null. + */ +TEST_F(InputControllerCapabilityAgentTest, test_createNoExceptionHandlerFail) { + auto inputControllerCA = create(m_mockHandler, nullptr); + ASSERT_FALSE(inputControllerCA.hasValue()); +} + +/** + * Test to verify the @c InputControllerCapabilityAgent cannot be created if config param is empty. + */ +TEST_F(InputControllerCapabilityAgentTest, test_createEmptyConfigFail) { + auto mockHandler = std::make_shared(INPUT_CONTROLLER_EMPTY_CONFIG); + auto inputControllerCA = create(mockHandler, m_mockExceptionEncounteredSender); + ASSERT_FALSE(inputControllerCA.hasValue()); +} + +/** + * Test to verify the @c InputControllerCapabilityAgent cannot be created if the configuration is not correct. + */ +TEST_F(InputControllerCapabilityAgentTest, test_createWithDuplicateFriendlyNamesFail) { + auto mockHandler = std::make_shared(INPUT_CONTROLLER_DUPLICATE_FRIENDLY_NAMES_CONFIG); + auto inputControllerCA = create(mockHandler, m_mockExceptionEncounteredSender); + ASSERT_FALSE(inputControllerCA.hasValue()); +} + +/** + * Test to verify if a valid SelectInput directive will set the input successfully. + */ +TEST_F(InputControllerCapabilityAgentTest, test_selectInputDirectiveSuccess) { + // Create a SelectInput AVSDirective. + auto directivePair = AVSDirective::create(SELECT_INPUT_DIRECTIVE_JSON_STRING, nullptr, ""); + std::shared_ptr directive = std::move(directivePair.first); + + m_inputControllerCA.directiveHandler->handleDirectiveImmediately(directive); + auto observerReturned = m_mockHandler->waitOnInputChange(); + EXPECT_TRUE(observerReturned.hasValue()); + EXPECT_EQ("HDMI1", observerReturned.value()); +} + +/** + * Test to verify if interface will send exceptions when the directive received is invalid + */ +TEST_F(InputControllerCapabilityAgentTest, test_processInvalidDirectiveFail) { + std::shared_ptr directive1 = + AVSDirective::create(SELECT_INPUT_DIRECTIVE_INVALID_INPUT_JSON_STRING, nullptr, "").first; + std::shared_ptr directive2 = + AVSDirective::create(SELECT_INPUT_MISSING_TAG_DIRECTIVE_JSON_STRING, nullptr, "").first; + std::shared_ptr directive3 = + AVSDirective::create(SELECT_INPUT_EMPTY_INPUT_DIRECTIVE_JSON_STRING, nullptr, "").first; + + EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _)).Times(3); + m_inputControllerCA.directiveHandler->handleDirectiveImmediately(directive1); + m_inputControllerCA.directiveHandler->handleDirectiveImmediately(directive2); + m_inputControllerCA.directiveHandler->handleDirectiveImmediately(directive3); + + auto observerReturned = m_mockHandler->waitOnInputChange(); + EXPECT_FALSE(observerReturned.hasValue()); +} + +} // namespace test +} // namespace acsdkInputController +} // namespace alexaClientSDK diff --git a/capabilities/InputController/acsdkInputControllerInterfaces/CMakeLists.txt b/capabilities/InputController/acsdkInputControllerInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..ce9fde3472 --- /dev/null +++ b/capabilities/InputController/acsdkInputControllerInterfaces/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.0) +project(acsdkInputControllerInterfaces LANGUAGES CXX) + +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +add_library(acsdkInputControllerInterfaces INTERFACE) + +target_include_directories(acsdkInputControllerInterfaces INTERFACE + "${acsdkInputControllerInterfaces_SOURCE_DIR}/include") + +# install interface +asdk_install_interface() \ No newline at end of file diff --git a/capabilities/InputController/acsdkInputControllerInterfaces/include/acsdkInputControllerInterfaces/InputControllerHandlerInterface.h b/capabilities/InputController/acsdkInputControllerInterfaces/include/acsdkInputControllerInterfaces/InputControllerHandlerInterface.h new file mode 100644 index 0000000000..ad7b7a2397 --- /dev/null +++ b/capabilities/InputController/acsdkInputControllerInterfaces/include/acsdkInputControllerInterfaces/InputControllerHandlerInterface.h @@ -0,0 +1,74 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKINPUTCONTROLLERINTERFACES_INPUTCONTROLLERHANDLERINTERFACE_H_ +#define ACSDKINPUTCONTROLLERINTERFACES_INPUTCONTROLLERHANDLERINTERFACE_H_ + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkInputControllerInterfaces { + +/** + * An interface to handle input changes from InputController. + */ +class InputControllerHandlerInterface { +public: + /** + * Destructor. + */ + virtual ~InputControllerHandlerInterface() = default; + + /** + * Alias to a type used for defining the inputs. The key of the map is the input, and the set is the + * friendlyNames associated with the input. For more information, please refer to the Alexa.InputController API. + * + * @see https://developer.amazon.com/en-US/docs/alexa/alexa-voice-service/inputcontroller.html + */ + using InputFriendlyNameType = std::unordered_map>; + + /// The configuration of the inputs on the device. + struct InputConfigurations { + /// Inputs and its friendly names of the device. + InputFriendlyNameType inputs; + }; + + /** + * A function to get the input configuration of the device. + * + * @return The @c InputConfigurations of the device. + */ + virtual InputConfigurations getConfiguration() = 0; + + /** + * A callback function to request the change of the input on the device. The @c InputController does not remember + * the previous input, so this callback will be called whenever AVS notifies a change in input. Also, during + * initialization, the application is responsible for remembering the previous input, as the @c InputController + * does not notify the application of the previous input with this callback. + * + * @param input The selected input on the product. The input is guaranteed to be one of the inputs as specified in + * the @c InputConfigurations from @c getConfiguration(). + * @return A bool indicate if the change in input is successful or not. + * + */ + virtual bool onInputChange(const std::string& input) = 0; +}; + +} // namespace acsdkInputControllerInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKINPUTCONTROLLERINTERFACES_INPUTCONTROLLERHANDLERINTERFACE_H_ diff --git a/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/include/acsdkMultiRoomMusic/MRMCapabilityAgent.h b/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/include/acsdkMultiRoomMusic/MRMCapabilityAgent.h index 8a75f465b6..8a89eb53a2 100644 --- a/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/include/acsdkMultiRoomMusic/MRMCapabilityAgent.h +++ b/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/include/acsdkMultiRoomMusic/MRMCapabilityAgent.h @@ -34,6 +34,7 @@ #include #include #include +#include #include "MRMHandlerInterface.h" @@ -228,6 +229,10 @@ class MRMCapabilityAgent bool m_wasPreviouslyActive; /// The @c Executor which queues up operations from asynchronous API calls. avsCommon::utils::threading::Executor m_executor; + + /// A timer to defer reacting to changes in Alexa dialog state, in an effort to improve WakeWordToBar + /// performance through freeing up resources on the critical path. + alexaClientSDK::avsCommon::utils::timing::MultiTimer m_delayedTaskTimer; }; } // namespace mrm diff --git a/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/src/CMakeLists.txt b/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/src/CMakeLists.txt index f028667c03..170882f158 100644 --- a/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/src/CMakeLists.txt +++ b/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkMultiRoomMusic") -add_library(acsdkMultiRoomMusic SHARED +add_library(acsdkMultiRoomMusic MRMCapabilityAgent.cpp) target_include_directories(acsdkMultiRoomMusic PUBLIC diff --git a/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/src/MRMCapabilityAgent.cpp b/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/src/MRMCapabilityAgent.cpp index 5cd329df87..ee148b6cc7 100644 --- a/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/src/MRMCapabilityAgent.cpp +++ b/capabilities/MultiRoomMusic/acsdkMultiRoomMusic/src/MRMCapabilityAgent.cpp @@ -13,6 +13,7 @@ * permissions and limitations under the License. */ +#include #include #include @@ -57,6 +58,10 @@ static const std::string MRM_CONFIGURATION_ROOT_KEY = "mrm"; /// The key in our config file to find the MRM capabilities. static const std::string MRM_CAPABILITIES_KEY = "capabilities"; +/// The amount of time to delay the processing of alexa dialog state changes in an effort to improve +/// WakeWordToBar performance, by freeing up resources during the critical time just after a wake word. +static const std::chrono::milliseconds DIALOG_STATE_UPDATE_DELAY{200}; + static std::unordered_set> readCapabilities() { std::unordered_set> capabilitiesSet; auto configRoot = configuration::ConfigurationNode::getRoot(); @@ -191,12 +196,11 @@ MRMCapabilityAgent::~MRMCapabilityAgent() { } void MRMCapabilityAgent::preHandleDirective(std::shared_ptr info) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("preHandleDirective")); // intentional no-op. } - void MRMCapabilityAgent::handleDirective(std::shared_ptr info) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("handleDirective")); if (!info) { ACSDK_ERROR(LX("handleDirectiveFailed").d("reason", "info is nullptr.")); return; @@ -205,12 +209,12 @@ void MRMCapabilityAgent::handleDirective(std::shared_ptr info) { } void MRMCapabilityAgent::cancelDirective(std::shared_ptr info) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("cancelDirective")); // intentional no-op. } void MRMCapabilityAgent::handleDirectiveImmediately(std::shared_ptr directive) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("handleDirectiveImmediately")); if (!directive) { ACSDK_ERROR(LX("handleDirectiveImmediatelyFailed").d("reason", "directive is nullptr.")); return; @@ -248,7 +252,7 @@ void MRMCapabilityAgent::onCallStateChange(avsCommon::sdkInterfaces::CallStateOb void MRMCapabilityAgent::onDialogUXStateChanged( avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState state) { ACSDK_DEBUG5(LX(__func__).d("state", state)); - m_executor.submit([this, state]() { executeOnDialogUXStateChanged(state); }); + m_delayedTaskTimer.submitTask(DIALOG_STATE_UPDATE_DELAY, [this, state]() { executeOnDialogUXStateChanged(state); }); } std::string MRMCapabilityAgent::getVersionString() const { diff --git a/capabilities/Notifications/acsdkNotifications/include/acsdkNotifications/NotificationsNotifier.h b/capabilities/Notifications/acsdkNotifications/include/acsdkNotifications/NotificationsNotifier.h index de817c2a2a..d3e37f96b5 100644 --- a/capabilities/Notifications/acsdkNotifications/include/acsdkNotifications/NotificationsNotifier.h +++ b/capabilities/Notifications/acsdkNotifications/include/acsdkNotifications/NotificationsNotifier.h @@ -20,7 +20,7 @@ #include #include -#include +#include namespace alexaClientSDK { namespace acsdkNotifications { diff --git a/capabilities/Notifications/acsdkNotifications/src/CMakeLists.txt b/capabilities/Notifications/acsdkNotifications/src/CMakeLists.txt index 02e533329f..790a72c0ec 100755 --- a/capabilities/Notifications/acsdkNotifications/src/CMakeLists.txt +++ b/capabilities/Notifications/acsdkNotifications/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=Notifications") -add_library(acsdkNotifications SHARED +add_library(acsdkNotifications NotificationsComponent.cpp NotificationIndicator.cpp NotificationsNotifier.cpp diff --git a/capabilities/Notifications/acsdkNotifications/src/NotificationsCapabilityAgent.cpp b/capabilities/Notifications/acsdkNotifications/src/NotificationsCapabilityAgent.cpp index 547a64e960..dabde16f3f 100644 --- a/capabilities/Notifications/acsdkNotifications/src/NotificationsCapabilityAgent.cpp +++ b/capabilities/Notifications/acsdkNotifications/src/NotificationsCapabilityAgent.cpp @@ -544,7 +544,7 @@ void NotificationsCapabilityAgent::executeProvideState(bool sendToken, unsigned .d("sendToken", sendToken) .d("stateRequestToken", stateRequestToken) .d("isEnabled", m_isEnabled)); - auto policy = StateRefreshPolicy::ALWAYS; + auto policy = StateRefreshPolicy::NEVER; rapidjson::Document state(rapidjson::kObjectType); state.AddMember(IS_ENABLED_KEY, m_isEnabled, state.GetAllocator()); @@ -831,6 +831,7 @@ void NotificationsCapabilityAgent::clearData() { auto result = m_executor.submit([this]() { m_notificationsStorage->clearNotificationIndicators(); m_notificationsStorage->setIndicatorState(IndicatorState::OFF); + executeProvideState(); notifyObserversOfIndicatorState(IndicatorState::OFF); }); diff --git a/capabilities/Notifications/acsdkNotifications/src/SQLiteNotificationsStorage.cpp b/capabilities/Notifications/acsdkNotifications/src/SQLiteNotificationsStorage.cpp index 620c30acf5..7062c088f9 100644 --- a/capabilities/Notifications/acsdkNotifications/src/SQLiteNotificationsStorage.cpp +++ b/capabilities/Notifications/acsdkNotifications/src/SQLiteNotificationsStorage.cpp @@ -18,7 +18,6 @@ #include #include - #include namespace alexaClientSDK { @@ -317,6 +316,17 @@ bool SQLiteNotificationsStorage::getIndicatorState(IndicatorState* state) { ACSDK_ERROR(LX("getIndicatorStateFailed").m("State parameter was nullptr")); return false; } + std::lock_guard lock{m_databaseMutex}; + if (!m_database.isDatabaseReady()) { + ACSDK_ERROR(LX("getIndicatorStateFailed").m("Database not ready")); + return false; + } + + if (!m_database.tableExists(INDICATOR_STATE_NAME)) { + ACSDK_ERROR( + LX("getIndicatorStateFailed").m("Table does not exist").d("table name", INDICATOR_STATE_NAME.c_str())); + return false; + } std::string sqlString = "SELECT * FROM " + INDICATOR_STATE_NAME; diff --git a/capabilities/Notifications/acsdkNotificationsInterfaces/include/acsdkNotificationsInterfaces/NotificationsNotifierInterface.h b/capabilities/Notifications/acsdkNotificationsInterfaces/include/acsdkNotificationsInterfaces/NotificationsNotifierInterface.h index cdc60061d2..2d8e362f81 100644 --- a/capabilities/Notifications/acsdkNotificationsInterfaces/include/acsdkNotificationsInterfaces/NotificationsNotifierInterface.h +++ b/capabilities/Notifications/acsdkNotificationsInterfaces/include/acsdkNotificationsInterfaces/NotificationsNotifierInterface.h @@ -18,7 +18,7 @@ #include -#include +#include #include "acsdkNotificationsInterfaces/NotificationsObserverInterface.h" diff --git a/cmakeBuild/BuildDefaults.cmake b/cmakeBuild/BuildDefaults.cmake index 4e4375a777..742297e5bb 100644 --- a/cmakeBuild/BuildDefaults.cmake +++ b/cmakeBuild/BuildDefaults.cmake @@ -28,6 +28,9 @@ include_once(DisallowOutOfSourceBuilds) # Setup default build options, like compiler flags and build type. include_once(BuildOptions) +# Setup platform dependant variables. +include_once(Platforms) + # Setup code coverage environment. This must be called after BuildOptions since it uses the variables defined there. include_once(CodeCoverage/CodeCoverage) @@ -52,6 +55,9 @@ include_once(MediaPlayer) # Setup PortAudio variables. include_once(PortAudio) +# Setup PKCS11 variables. +include_once(PKCS11) + # Setup Curl variables. include_once(Curl) @@ -64,9 +70,6 @@ include_once(Crypto) # Setup Test Options variables. include_once(TestOptions) -# Setup platform dependant variables. -include_once(Platforms) - # Setup Comms variables. include_once(Comms) @@ -79,6 +82,9 @@ include_once(MC) # Setup MCC variables. include_once(MCC) +# Setup RTCSC variables. +include_once(RTCSC) + # Setup android variables. include_once(Android) @@ -135,3 +141,15 @@ include_once(LocalDucking) # Setup AuthorizationManager include_once(AuthorizationManager) + +# Setup FileSystemUtils options. +include_once(FileSystemUtils) + +# Setup InputController options. +include_once(InputController) + +# Setup LibArchive options. +include_once(LibArchive) + +# Setup AssetManager options. +include_once(AssetManager) diff --git a/cmakeBuild/cmake/AssetManager.cmake b/cmakeBuild/cmake/AssetManager.cmake new file mode 100644 index 0000000000..59e2d4a2ed --- /dev/null +++ b/cmakeBuild/cmake/AssetManager.cmake @@ -0,0 +1,27 @@ +# +# Set up and enable Asset Manager and DAVS Client functionalities. +# +# Asset Manager is disabed by default, to build with it, run the following command, +# cmake +# -DASSET_MANAGER=ON +# +# Asset Manager will only be enabled if both FileSystemUtils and LibArchive are found and enabled. +# + +option(ASSET_MANAGER "Enable Asset Manager & DAVS Client functionality." OFF) + +if(ASSET_MANAGER) + if (NOT FILE_SYSTEM_UTILS) + message("FileSystemUtils is not enabled, cannot enable Asset Manager functionalities") + set(ASSET_MANAGER OFF) + elseif (NOT LibArchive_FOUND) + message("LibArchive is not found, cannot enable Asset Manager functionalities") + set(ASSET_MANAGER OFF) + elseif (NOT CRYPTO_FOUND) + message("Crypto is not found, cannot enable Asset Manager functionalities") + set(ASSET_MANAGER OFF) + else () + message("Enabling Asset Manager functionalities") + add_definitions("-DASSET_MANAGER") + endif() +endif() diff --git a/cmakeBuild/cmake/BuildOptions.cmake b/cmakeBuild/cmake/BuildOptions.cmake index 28faebe133..245ec1acc7 100644 --- a/cmakeBuild/cmake/BuildOptions.cmake +++ b/cmakeBuild/cmake/BuildOptions.cmake @@ -5,6 +5,10 @@ # cmake -DCMAKE_BUILD_TYPE= # +# CMake option to build shared libraries instead of static ones. For legacy +# reasons, we set the default to build shared. +option(BUILD_SHARED_LIBS "Build shared libraries instead of static." ON) + # If no build type is specified by specifying it on the command line, default to debug. if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: DEBUG, RELEASE, or MINSIZEREL." FORCE) @@ -51,6 +55,9 @@ set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +# Build position-independent code even when building static libraries +set(POSITION_INDEPENDENT_CODE ON CACHE BOOL "Build position-independent code") + # Determine the platform and compiler dependent flags. if (NOT MSVC) set(CXX_PLATFORM_DEPENDENT_FLAGS_DEBUG "-DDEBUG -DACSDK_LOG_ENABLED -DACSDK_DEBUG_LOG_ENABLED -Wall -Werror -Wsign-compare -g") @@ -102,3 +109,11 @@ set(CMAKE_C_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} CACHE INTERNAL "Flags used # Minimum sized release build. set(CMAKE_CXX_FLAGS_MINSIZEREL "${CXX_PLATFORM_DEPENDENT_FLAGS_MINSIZEREL} -DRAPIDJSON_HAS_STDSTRING" CACHE INTERNAL "Flags used for minimum sized RELEASE builds" FORCE) set(CMAKE_C_FLAGS_MINSIZEREL ${CMAKE_CXX_FLAGS_RELEASE} CACHE INTERNAL "Flags used for minimum sized RELEASE builds" FORCE) + +if(BUILD_SHARED_LIBS) + set(ACSDK_CONFIG_STATIC_LIBS OFF CACHE INTERNAL "Flag for SDKConfig.h.in" FORCE) + set(ACSDK_CONFIG_SHARED_LIBS ON CACHE INTERNAL "Flag for SDKConfig.h.in" FORCE) +else() + set(ACSDK_CONFIG_STATIC_LIBS ON CACHE INTERNAL "Flag for SDKConfig.h.in" FORCE) + set(ACSDK_CONFIG_SHARED_LIBS OFF CACHE INTERNAL "Flag for SDKConfig.h.in" FORCE) +endif() diff --git a/cmakeBuild/cmake/ExtensionPath.cmake b/cmakeBuild/cmake/ExtensionPath.cmake index b29015358c..9c21b82b8c 100644 --- a/cmakeBuild/cmake/ExtensionPath.cmake +++ b/cmakeBuild/cmake/ExtensionPath.cmake @@ -15,34 +15,38 @@ # option(EXTENSION_PATH -"A semi-colon separated list of paths to search for CMake projects.\ - DEPRECATED: Use EXTENSION_PATHS instead.") + "A semi-colon separated list of paths to search for CMake projects.\ + DEPRECATED: Use EXTENSION_PATHS instead.") - set(EXTENSION_PATHS "${AVS_CORE}/capabilities" CACHE STRING +# Default add the core capabilities directory to the extension paths list. +set(EXTENSION_PATHS_LIST "${AVS_CORE}/capabilities" CACHE STRING "A semi-colon separated list of PATHSs to search for CMake projects") macro(add_extension_projects) if(EXTENSION_PATH) - list(APPEND EXTENSION_PATHS ${EXTENSION_PATH}) + list(APPEND EXTENSION_PATHS_LIST ${EXTENSION_PATH}) endif() if(EXTENSION_PATHS) - foreach(EXTENSION IN LISTS EXTENSION_PATHS) - if(IS_DIRECTORY "${EXTENSION}") - if(EXISTS "${EXTENSION}/CMakeLists.txt") - get_filename_component(BUILD_BASENAME "${EXTENSION}" NAME) - add_subdirectory("${EXTENSION}" "EXTENSION/${BUILD_BASENAME}") - else() - file(GLOB EXTENSION_ENTIRES "${EXTENSION}/*/") - foreach(ENTRY IN LISTS EXTENSION_ENTIRES) - if(IS_DIRECTORY "${ENTRY}" AND EXISTS "${ENTRY}/CMakeLists.txt") - get_filename_component(BUILD_BASENAME "${ENTRY}" NAME) - add_subdirectory("${ENTRY}" "EXTENSION/${BUILD_BASENAME}") - endif() - endforeach() - endif() + list(APPEND EXTENSION_PATHS_LIST ${EXTENSION_PATHS}) + endif() + list(REMOVE_DUPLICATES EXTENSION_PATHS_LIST) + + foreach(EXTENSION IN LISTS EXTENSION_PATHS_LIST) + if(IS_DIRECTORY "${EXTENSION}") + if(EXISTS "${EXTENSION}/CMakeLists.txt") + get_filename_component(BUILD_BASENAME "${EXTENSION}" NAME) + add_subdirectory("${EXTENSION}" "EXTENSION/${BUILD_BASENAME}") else() - message(WARNING "Could not find extension ${EXTENSION}") + file(GLOB EXTENSION_ENTRIES "${EXTENSION}/*/") + foreach(ENTRY IN LISTS EXTENSION_ENTRIES) + if(IS_DIRECTORY "${ENTRY}" AND EXISTS "${ENTRY}/CMakeLists.txt") + get_filename_component(BUILD_BASENAME "${ENTRY}" NAME) + add_subdirectory("${ENTRY}" "EXTENSION/${BUILD_BASENAME}") + endif() + endforeach() endif() - endforeach() - endif() + else() + message(WARNING "Could not find extension ${EXTENSION}") + endif() + endforeach() endmacro() diff --git a/cmakeBuild/cmake/FileSystemUtils.cmake b/cmakeBuild/cmake/FileSystemUtils.cmake new file mode 100644 index 0000000000..33442e5dbd --- /dev/null +++ b/cmakeBuild/cmake/FileSystemUtils.cmake @@ -0,0 +1,32 @@ +# +# Set up and enable FileSystemUtils in AVSCommon. +# +# FileSystemUtils is enabled by default, to build without FileSystemUtils, run the following command, +# cmake +# -DFILE_SYSTEM_UTILS=OFF +# +# If the existing default implementation does not work for your platform and wish to provide your own custom implementation +# of the FileSystemUtils APIs, you can provide the cpp file with the following command, +# cmake +# -DFILE_SYSTEM_UTILS_CPP_PATH= +# + +option(FILE_SYSTEM_UTILS "Enable FileSystemUtils functionality." ON) +set(FILE_SYSTEM_UTILS_CPP_PATH "" CACHE FILEPATH "Optional: Custom FileSystemUtils implementation.") +mark_as_dependent(FILE_SYSTEM_UTILS_CPP_PATH FILE_SYSTEM_UTILS) + +if(FILE_SYSTEM_UTILS) + if (FILE_SYSTEM_UTILS_CPP_PATH) + set(FileSystemUtils_SOURCE ${FILE_SYSTEM_UTILS_CPP_PATH}) + elseif((${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR (${CMAKE_SYSTEM_NAME} MATCHES "Android") OR (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")) + set(FileSystemUtils_SOURCE Utils/src/FileSystem/FileSystemUtilsLinux.cpp) + elseif(WIN32) + set(FileSystemUtils_SOURCE Utils/src/FileSystem/FileSystemUtilsWindows.cpp) + endif() + + if ("${FileSystemUtils_SOURCE}" STREQUAL "") + message("FileSystemUtils is not supported on this platform") + else() + add_definitions("-DFILE_SYSTEM_UTILS_ENABLED") + endif() +endif() \ No newline at end of file diff --git a/cmakeBuild/cmake/InputController.cmake b/cmakeBuild/cmake/InputController.cmake new file mode 100644 index 0000000000..f6dc915325 --- /dev/null +++ b/cmakeBuild/cmake/InputController.cmake @@ -0,0 +1,12 @@ +# +# Setup the InputController compiler options. +# +# To build with InputController capabilities, specify: +# cmake -DINPUT_CONTROLLER=ON + +option(INPUT_CONTROLLER "Enable the Input Controller functionality" OFF) + +if(INPUT_CONTROLLER) + message("Enabling InputController CapabilityAgent") + add_definitions(-DENABLE_INPUT_CONTROLLER) +endif() diff --git a/cmakeBuild/cmake/KeywordDetector.cmake b/cmakeBuild/cmake/KeywordDetector.cmake index 3404df9e96..bbb7ee540b 100644 --- a/cmakeBuild/cmake/KeywordDetector.cmake +++ b/cmakeBuild/cmake/KeywordDetector.cmake @@ -12,7 +12,7 @@ # -DAMAZONLITE_KEY_WORD_DETECTOR_INCLUDE_DIR= # -DAMAZONLITE_KEY_WORD_DETECTOR_DYNAMIC_MODEL_LOADING= # -DAMAZONLITE_KEY_WORD_DETECTOR_MODEL_CPP_PATH= -# -DSENSORY_KEY_WORD_DETECTOR=ON +# -DSENSORY_KEY_WORD_DETECTOR=ON # -DSENSORY_KEY_WORD_DETECTOR_LIB_PATH= # -DSENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR= # @@ -65,13 +65,6 @@ if(AMAZONLITE_KEY_WORD_DETECTOR) if(NOT AMAZONLITE_KEY_WORD_DETECTOR_INCLUDE_DIR) message(FATAL_ERROR "Must pass include dir path of AmazonLite KeywordDetector!") endif() - if(NOT AMAZONLITE_KEY_WORD_DETECTOR_DYNAMIC_MODEL_LOADING) - if(NOT AMAZONLITE_KEY_WORD_DETECTOR_MODEL_CPP_PATH) - message(FATAL_ERROR "Must pass the path of the desired model .cpp file for the AmazonLite Keyword Detector if dynamic loading of model is disabled!") - endif() - else() - add_definitions(-DKWD_AMAZONLITE_DYNAMIC_MODEL_LOADING) - endif() add_definitions(-DKWD) add_definitions(-DKWD_AMAZONLITE) set(KWD ON) @@ -88,4 +81,8 @@ if(SENSORY_KEY_WORD_DETECTOR) add_definitions(-DKWD) add_definitions(-DKWD_SENSORY) set(KWD ON) + + # If Sensory KWD Enabled Add SensoryAdapter to extension paths to include with project. + set(EXTENSION_PATHS "${PROJECT_SOURCE_DIR}/applications/acsdkSensoryAdapter;${EXTENSION_PATHS}" CACHE STRING + "Adding SensoryAdapter to the ExtensionPaths" FORCE) endif() diff --git a/cmakeBuild/cmake/LibArchive.cmake b/cmakeBuild/cmake/LibArchive.cmake new file mode 100644 index 0000000000..17fefe5919 --- /dev/null +++ b/cmakeBuild/cmake/LibArchive.cmake @@ -0,0 +1,19 @@ +# +# Custom LibArchive library usage. +# +# To build with a customized version of LibArchive, run the following command, +# cmake +# -DLibArchive_LIBRARIES= +# -DLibArchive_INCLUDE_DIRS= +# + +set(LibArchive_LIBRARIES "" CACHE FILEPATH "LibArchive library path.") +set(LibArchive_INCLUDE_DIRS "" CACHE PATH "LibArchive include directory.") + +mark_as_advanced(LibArchive_INCLUDE_DIRS LibArchive_LIBRARIES) + +if (("${LibArchive_LIBRARIES}" STREQUAL "") OR ("${LibArchive_INCLUDE_DIRS}" STREQUAL "")) + find_package(LibArchive) +else() + set(LibArchive_FOUND true) +endif() diff --git a/cmakeBuild/cmake/PKCS11.cmake b/cmakeBuild/cmake/PKCS11.cmake new file mode 100644 index 0000000000..dafe20d241 --- /dev/null +++ b/cmakeBuild/cmake/PKCS11.cmake @@ -0,0 +1,53 @@ +# +# This module contains variables for PKCS11 integration and tests. +# +# To build with PKCS11 support, include the following option on the cmake command line. +# cmake -DPKCS11=ON +# +# To support unit tests with non-default parameters, include the following arguments in cmake command line: +# cmake \ +# -DPKCS11_TEST_LIBRARY= \ +# -DPKCS11_TEST_USER_PIN= \ +# -DPKCS11_TEST_MAIN_KEY_ALIAS= \ +# -DPKCS11_TEST_TOKEN_NAME= +# + +if(CMAKE_SYSTEM_NAME MATCHES "Android") + option(PKCS11 "Enable PKCS11" OFF) +else() + option(PKCS11 "Enable PKCS11" ON) +endif() + +# acsdkPkcs11 requires an absolute path to PKCS11 library. We use stubs in unit tests by default, and the default +# library location corresponds to project directory, but the location can be overridden. +set(PKCS11_TEST_LIBRARY_DIR "${PROJECT_BINARY_DIR}/core/Crypto/acsdkPkcs11/testStubs/") +if (WIN32 AND NOT "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" STREQUAL "") + set(PKCS11_TEST_LIBRARY_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +elseif(NOT WIN32 AND NOT "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}" STREQUAL "") + set(PKCS11_TEST_LIBRARY_DIR "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +endif() +get_filename_component(PKCS11_TEST_LIBRARY_DIR "${PKCS11_TEST_LIBRARY_DIR}" ABSOLUTE) + +set(PKCS11_TEST_LIBRARY + "${PKCS11_TEST_LIBRARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}acsdkPkcs11Stubs${CMAKE_SHARED_LIBRARY_SUFFIX}" + CACHE + FILEPATH + "PKCS11 Module Path for Unit Tests") +set(PKCS11_TEST_USER_PIN "1234" CACHE STRING "PKCS11 User Pin for Unit Tests" ) +set(PKCS11_TEST_MAIN_KEY_ALIAS "TEST_KEY" CACHE STRING "PKCS11 Main Key Alias for Unit Tests" ) +set(PKCS11_TEST_TOKEN_NAME "ACSDK" CACHE STRING "PKCS11 Token Name for Unit Tests") + +mark_as_dependent(PKCS11_TEST_LIBRARY PKCS11) +mark_as_dependent(PKCS11_TEST_USER_PIN PKCS11) +mark_as_dependent(PKCS11_TEST_MAIN_KEY_ALIAS PKCS11) +mark_as_dependent(PKCS11_TEST_TOKEN_NAME PKCS11) + +if (PKCS11) + message(STATUS "PKCS11 Support is enabled") + message(STATUS "\tModule Path: ${PKCS11_TEST_LIBRARY}") + message(STATUS "\tToken Name: ${PKCS11_TEST_TOKEN_NAME}") + message(STATUS "\tPIN: ${PKCS11_TEST_USER_PIN}") + message(STATUS "\tMain Key: ${PKCS11_TEST_MAIN_KEY_ALIAS}") +else() + message(STATUS "PKCS11 Support is disabled") +endif() diff --git a/cmakeBuild/cmake/RTCSC.cmake b/cmakeBuild/cmake/RTCSC.cmake new file mode 100644 index 0000000000..c2b71d412b --- /dev/null +++ b/cmakeBuild/cmake/RTCSC.cmake @@ -0,0 +1,20 @@ +# +# Setup the RTCSessionController compiler options. +# +# To build with RTCSessionController capabilities, specify: +# cmake -DRTCSC=ON \ +# -DRTCSC_LIB_PATH= \ +# -DRTCSC_INCLUDE_DIR= + +option(RTCSC "Enable the RTCSessionController functionality" OFF) + +if(RTCSC) + if(NOT RTCSC_LIB_PATH) + message(FATAL_ERROR "Must pass library path for RTCSC to enable RTCSC.") + endif() + if(NOT RTCSC_INCLUDE_DIR) + message(FATAL_ERROR "Must pass include dir path for RTCSC to enable RTCSC.") + endif() + message("Creating ${PROJECT_NAME} with RTCSessionController") + add_definitions(-DENABLE_RTCSC) +endif() diff --git a/cmakeBuild/cmake/Rapidjson.cmake b/cmakeBuild/cmake/Rapidjson.cmake index a30bdf437e..45344a671e 100644 --- a/cmakeBuild/cmake/Rapidjson.cmake +++ b/cmakeBuild/cmake/Rapidjson.cmake @@ -1,6 +1,12 @@ # # Custom Rapidjson usage. # +# To build without the rapidjson provided by the SDK +# cmake +# -DUSE_DEFAULT_RAPIDJSON=OFF +# +# If using the rapidjson provided by SDK the below options are available. +# # To build without the memory optimization using the CrtAllocator run with the option, # cmake # -DRAPIDJSON_MEM_OPTIMIZATION=OFF @@ -13,33 +19,36 @@ # -DRAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY= # -DRAPIDJSON_DEFAULT_STACK_ALLOCATOR= +set(USE_DEFAULT_RAPIDJSON ON CACHE BOOL "Use rapidjson packaged within the SDK") -if(RAPIDJSON_MEM_OPTIMIZATION STREQUAL "OFF") - # Do Nothing and let defaults take over - message(STATUS "rapidjson upstream defaults used") -elseif(RAPIDJSON_MEM_OPTIMIZATION STREQUAL "CUSTOM") - # Use Custom values if set to custom - message(STATUS "rapidjson custom values used") - if(RAPIDJSON_DEFAULT_ALLOCATOR) - add_definitions(-DRAPIDJSON_DEFAULT_ALLOCATOR=${RAPIDJSON_DEFAULT_ALLOCATOR}) - endif() +if(USE_DEFAULT_RAPIDJSON) + if(RAPIDJSON_MEM_OPTIMIZATION STREQUAL "OFF") + # Do Nothing and let defaults take over + message(STATUS "rapidjson upstream defaults used") + elseif(RAPIDJSON_MEM_OPTIMIZATION STREQUAL "CUSTOM") + # Use Custom values if set to custom + message(STATUS "rapidjson custom values used") + if(RAPIDJSON_DEFAULT_ALLOCATOR) + add_definitions(-DRAPIDJSON_DEFAULT_ALLOCATOR=${RAPIDJSON_DEFAULT_ALLOCATOR}) + endif() - if(RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY) - add_definitions(-DRAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY=${RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY}) - endif() + if(RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY) + add_definitions(-DRAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY=${RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY}) + endif() - if(RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY) - add_definitions(-DRAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY=${RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY}) - endif() + if(RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY) + add_definitions(-DRAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY=${RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY}) + endif() - if(RAPIDJSON_DEFAULT_STACK_ALLOCATOR) - add_definitions(-DRAPIDJSON_DEFAULT_STACK_ALLOCATOR=${RAPIDJSON_DEFAULT_STACK_ALLOCATOR}) + if(RAPIDJSON_DEFAULT_STACK_ALLOCATOR) + add_definitions(-DRAPIDJSON_DEFAULT_STACK_ALLOCATOR=${RAPIDJSON_DEFAULT_STACK_ALLOCATOR}) + endif() + else() + # Use Memory Optimization + message(STATUS "rapidjson memory optimization used") + add_definitions(-DRAPIDJSON_DEFAULT_ALLOCATOR=CrtAllocator) + add_definitions(-DRAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY=1) + add_definitions(-DRAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY=1) + add_definitions(-DRAPIDJSON_DEFAULT_STACK_ALLOCATOR=CrtAllocator) endif() -else() - # Use Memory Optimization - message(STATUS "rapidjson memory optimization used") - add_definitions(-DRAPIDJSON_DEFAULT_ALLOCATOR=CrtAllocator) - add_definitions(-DRAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY=1) - add_definitions(-DRAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY=1) - add_definitions(-DRAPIDJSON_DEFAULT_STACK_ALLOCATOR=CrtAllocator) endif() diff --git a/core/Authorization/acsdkAuthorization/CMakeLists.txt b/core/Authorization/acsdkAuthorization/CMakeLists.txt index 50845612f7..88fea4c267 100644 --- a/core/Authorization/acsdkAuthorization/CMakeLists.txt +++ b/core/Authorization/acsdkAuthorization/CMakeLists.txt @@ -4,3 +4,4 @@ project(acsdkAuthorization LANGUAGES CXX) include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) add_subdirectory("src") +add_subdirectory("test") diff --git a/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/AuthorizationManagerStorage.h b/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/AuthorizationManagerStorage.h index 1b309d9a82..5ec2f812d4 100644 --- a/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/AuthorizationManagerStorage.h +++ b/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/AuthorizationManagerStorage.h @@ -45,21 +45,20 @@ class AuthorizationManagerStorage { * Upon failure, the database may not be in * a consistent state. Clearing is recommended. * - * @param The adapterId. This is required. - * @param The userId. This can be empty. + * @param adapterId The adapterId. This is required. + * @param userId The userId. This can be empty. * @return Whether the data was successfully stored. */ bool store(const std::string& adapterId, const std::string& userId); /** - * Stores the information into the database. This will fail if there - * are existing entries. + * Loads information from the database. * - * @param The adapterId. This is required. - * @param The userId. This can be empty. - * @return Whether the data was successfully stored. + * @param[out] adapterId The adapterId. + * @param[out] userId The userId. + * @return Whether the data was successfully loaded. */ - bool load(std::string* adapterId, std::string* userId); + bool load(std::string& adapterId, std::string& userId); /** * Clears the table. This will not delete the database. diff --git a/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/LWA/LWAAuthorizationStorage.h b/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/LWA/LWAAuthorizationStorage.h new file mode 100644 index 0000000000..4ae48fe52d --- /dev/null +++ b/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/LWA/LWAAuthorizationStorage.h @@ -0,0 +1,153 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKAUTHORIZATION_LWA_LWAAUTHORIZATIONSTORAGE_H_ +#define ACSDKAUTHORIZATION_LWA_LWAAUTHORIZATIONSTORAGE_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace lwa { + +/// A SQLite based version of @c LWAAuthorizationStorageInterface. + +/** + * @brief Storage implementation based on Properties API. + * + * This implementation class adapts properties interface to domain-specific authorization storage interface. Depending + * on + * + * @sa PropertiesAPI + */ +class LWAAuthorizationStorage : public acsdkAuthorizationInterfaces::lwa::LWAAuthorizationStorageInterface { +public: + /** + * @brief Create storage interface. + * + * Factory method for creating a storage interface using properties API. For certification it is important + * to use encrypted properties factory. + * + * @param[in] propertiesFactory Properties factory interface. + * + * @return Pointer to the LWAAuthorizationStorage object, nullptr if there's an error creating it. + */ + static std::shared_ptr createStorage( + const std::shared_ptr& propertiesFactory); + + /** + * @brief Create storage interface backed by SQLite. + * + * Factory method for creating a storage object for creating a storage interface based on an SQLite database. If + * platform configuration has both cryptography and hardware security module support, all the stored values will + * be encrypted. If there is no cryptography module and/or HSM support, all values will be stored in unencrypted + * form. + * + * @param[in] configurationRoot The global config object. + * @param[in] storageRootKey The key to use to find the parent node. + * @param[in] cryptoFactory Crypto factory interface. This interface is required if HSM integration is enabled. + * @param[in] keyStore Key store interface. This interface is required if HSM integration is enabled. + * + * @return Pointer to the LWAAuthorizationStorage object, nullptr if there's an error creating it. + * + * @sa CryptoIMPL + * @sa CryptoPKCS11 + */ + static std::shared_ptr + createLWAAuthorizationStorageInterface( + const std::shared_ptr& configurationRoot, + const std::string& storageRootKey, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore); + + /** + * Destructor. + */ + ~LWAAuthorizationStorage(); + + /// @name LWAAuthorizationStorageInterface method overrides. + /// @{ + bool createDatabase() override; + bool open() override; + bool openOrCreate() override; + bool setRefreshToken(const std::string& refreshToken) override; + bool clearRefreshToken() override; + bool getRefreshToken(std::string* refreshToken) override; + bool setUserId(const std::string& userId) override; + bool getUserId(std::string* userId) override; + bool clear() override; + /// @} + +private: + /** + * @brief Create SQLite interface. + * + * Method initialized SQLite database object and returns reference to it. The database path is taken from + * configuration. + * + * @param[in] configurationRoot The global config object. + * @param[in] storageRootKey The key to use to find the parent node. + * + * @return Reference to database object or nullptr on error. + */ + static std::shared_ptr createSQLiteStorage( + const std::shared_ptr& configurationRoot, + const std::string& storageRootKey); + + /** + * @brief Create database file. + * + * This method creates an empty database file and sets file permissions to owner-only read write if and only if + * file doesn't exist. + * + * If file already exists, this method does nothing. + * + * @param[in] filepath Database file path. + * + * @return True if file already exists, or a new empty file created and permissions were set. + */ + static bool createStorageFileAndSetPermissions(const std::string& filepath) noexcept; + + /** + * Constructor. + */ + LWAAuthorizationStorage( + const std::shared_ptr& + propertiesFactory); + + /// The underlying properties factory class; + std::shared_ptr m_propertiesFactory; + + /// The underlying properties class; + std::shared_ptr m_properties; + + /// Friend class for member access. + friend class LWAAuthorizationStorageTestHelper; +}; + +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK + +#endif // ACSDKAUTHORIZATION_LWA_LWAAUTHORIZATIONSTORAGE_H_ diff --git a/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/LWA/SQLiteLWAAuthorizationStorage.h b/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/LWA/SQLiteLWAAuthorizationStorage.h deleted file mode 100644 index 1097ba09f7..0000000000 --- a/core/Authorization/acsdkAuthorization/include/acsdkAuthorization/LWA/SQLiteLWAAuthorizationStorage.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKAUTHORIZATION_LWA_SQLITELWAAUTHORIZATIONSTORAGE_H_ -#define ACSDKAUTHORIZATION_LWA_SQLITELWAAUTHORIZATIONSTORAGE_H_ - -#include -#include -#include - -#include - -#include -#include -#include - -namespace alexaClientSDK { -namespace acsdkAuthorization { -namespace lwa { - -/// A SQLite based version of @c LWAAuthorizationStorageInterface. -class SQLiteLWAAuthorizationStorage : public acsdkAuthorizationInterfaces::lwa::LWAAuthorizationStorageInterface { -public: - /** - * Factory method for creating a storage object for SQLiteLWAAuthorizationStorage based on an SQLite database. - * - * @param configurationRoot The global config object. - * @param storageRootKey The key to use to find the parent node. - * @return Pointer to the SQLiteLWAAuthorizationStorage object, nullptr if there's an error creating it. - */ - static std::shared_ptr - createLWAAuthorizationStorageInterface( - const std::shared_ptr& configurationRoot, - const std::string& storageRootKey = ""); - - /** - * Destructor. - */ - ~SQLiteLWAAuthorizationStorage(); - - /// @name LWAAuthorizationStorageInterface method overrides. - /// @{ - bool createDatabase() override; - bool open() override; - bool openOrCreate() override; - bool setRefreshToken(const std::string& refreshToken) override; - bool clearRefreshToken() override; - bool getRefreshToken(std::string* refreshToken) override; - bool setUserId(const std::string& userId) override; - bool getUserId(std::string* userId) override; - bool clear() override; - /// @} - -private: - /** - * Clears the specified table. The std::m_mutex must be held. - * - * @param tableName The table. - * @return Bool indicating success. - */ - bool clearTableLocked(const std::string& tableName); - - /** - * Constructor. - */ - SQLiteLWAAuthorizationStorage(const std::string& databaseFilePath); - - /** - * Close the database. - */ - void close(); - - /// Mutex with which to serialize database operations. - std::mutex m_mutex; - - /// The underlying database class. - alexaClientSDK::storage::sqliteStorage::SQLiteDatabase m_database; -}; - -} // namespace lwa -} // namespace acsdkAuthorization -} // namespace alexaClientSDK - -#endif // ACSDKAUTHORIZATION_LWA_SQLITELWAAUTHORIZATIONSTORAGE_H_ diff --git a/core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/LWA/LWAStorageConstants.h b/core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/LWA/LWAStorageConstants.h new file mode 100644 index 0000000000..296c163f74 --- /dev/null +++ b/core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/LWA/LWAStorageConstants.h @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKAUTHORIZATION_PRIVATE_LWA_LWASTORAGECONSTANTS_H_ +#define ACSDKAUTHORIZATION_PRIVATE_LWA_LWASTORAGECONSTANTS_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace lwa { + +/// The name of the refreshToken entry. +extern const std::string REFRESH_TOKEN_PROPERTY_NAME; + +/// The name of the userId table. +extern const std::string USER_ID_PROPERTY_NAME; + +/// The configuration URI. +extern const std::string CONFIG_URI; + +/// The name of the refreshToken table. +extern const std::string REFRESH_TOKEN_TABLE_NAME; + +/// The name of the refreshToken column. +extern const std::string REFRESH_TOKEN_COLUMN_NAME; + +/// The name of the userId table. +extern const std::string USER_ID_TABLE_NAME; + +/// The name of the userId column. +extern const std::string USER_ID_COLUMN_NAME; + +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK + +#endif // ACSDKAUTHORIZATION_PRIVATE_LWA_LWASTORAGECONSTANTS_H_ diff --git a/core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/LWA/LWAStorageDataMigration.h b/core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/LWA/LWAStorageDataMigration.h new file mode 100644 index 0000000000..957d97d5e8 --- /dev/null +++ b/core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/LWA/LWAStorageDataMigration.h @@ -0,0 +1,83 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKAUTHORIZATION_PRIVATE_LWA_LWASTORAGEDATAMIGRATION_H_ +#define ACSDKAUTHORIZATION_PRIVATE_LWA_LWASTORAGEDATAMIGRATION_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace lwa { + +/** + * @brief Class to migrate storage database format to support @ref SQLiteMiscStorage. + * + * This class provides method to migrate data from existing instances of LWAAuthorizationStorage from a format before + * 1.26 release into a new one. + * + * Migration allows end-users to continue using Alexa device without need to reauthorize it. + */ +class LWAStorageDataMigration { +public: + LWAStorageDataMigration( + const std::shared_ptr& storage, + const std::shared_ptr& propertiesFactory) noexcept; + + /** + * @brief Upgrades storage structure if required. + * + * This method checks if \a storage contains tables with data from previous SDK releases, and if so, the data + * is loaded and stored into \a properties, and tables are dropped. + */ + void upgradeStorage() noexcept; + +private: + /** + * @brief Migrate single property from old table into properties. + * + * This method checks if the old table \a tableName exists, and has a value in \a columnName column. If there is a + * value, it is stored into \a properties with a \a propertyName name. + * + * Table \a tableName is deleted before operation completes, and if there is an error, property value may be lost. + * + * @param[in] storage Storage to check for data to migrate. + * @param[in] tableName Table name with data to migrate. + * @param[in] columnName Column name with property value to migrate. + * @param[in] properties Destination properties interface. + * @param[in] propertyName New property name to keep the value. + * + * @return True on success, False on error. + */ + bool migrateSinglePropertyTable( + const std::string& tableName, + const std::string& columnName, + const std::shared_ptr& properties, + const std::string& propertyName) noexcept; + +private: + const std::shared_ptr m_storage; + const std::shared_ptr m_propertiesFactory; +}; + +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK + +#endif // ACSDKAUTHORIZATION_PRIVATE_LWA_LWASTORAGEDATAMIGRATION_H_ diff --git a/core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/Logging.h b/core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/Logging.h new file mode 100644 index 0000000000..bbe2c4d6fb --- /dev/null +++ b/core/Authorization/acsdkAuthorization/privateInclude/acsdkAuthorization/private/Logging.h @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKAUTHORIZATION_PRIVATE_LOGGING_H_ +#define ACSDKAUTHORIZATION_PRIVATE_LOGGING_H_ + +#include + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + * @private + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +#endif // ACSDKAUTHORIZATION_PRIVATE_LOGGING_H_ diff --git a/core/Authorization/acsdkAuthorization/src/AuthorizationManager.cpp b/core/Authorization/acsdkAuthorization/src/AuthorizationManager.cpp index 978e455bc4..12deeb4139 100644 --- a/core/Authorization/acsdkAuthorization/src/AuthorizationManager.cpp +++ b/core/Authorization/acsdkAuthorization/src/AuthorizationManager.cpp @@ -14,18 +14,11 @@ */ #include -#include +#include /// String to identify log entries originating from this file. static const std::string TAG{"AuthorizationManager"}; -/** - * 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 acsdkAuthorization { @@ -34,10 +27,10 @@ using namespace avsCommon::sdkInterfaces; void AuthorizationManager::setRegistrationManager( const std::shared_ptr regManager) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("setRegistrationManager")); if (!regManager) { - ACSDK_ERROR(LX(__func__).d("reason", "nullRegManager")); + ACSDK_ERROR(LX("setRegistrationManagerFailed").d("reason", "nullRegManager")); } else { m_registrationManager = regManager; } @@ -46,7 +39,7 @@ void AuthorizationManager::setRegistrationManager( std::shared_ptr AuthorizationManager::create( const std::shared_ptr& storage, const std::shared_ptr& customerDataManager) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("create")); if (!storage || !customerDataManager) { ACSDK_ERROR( @@ -61,6 +54,7 @@ std::shared_ptr AuthorizationManager:: auto authMgr = std::shared_ptr(new AuthorizationManager(authMgrStorage, customerDataManager)); if (!authMgr->init()) { + ACSDK_ERROR(LX("createFailed").d("reason", "authMgrInitFailed")); return nullptr; } @@ -73,24 +67,24 @@ AuthorizationManager::AuthorizationManager( RequiresShutdown{"AuthorizationManager"}, CustomerDataHandler{customerDataManager}, m_storage{storage} { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("AuthorizationManager")); } bool AuthorizationManager::init() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("init")); - if (!m_storage->load(&m_activeAdapterId, &m_activeUserId)) { - ACSDK_ERROR(LX(__func__)); + if (!m_storage->load(m_activeAdapterId, m_activeUserId)) { + ACSDK_ERROR(LX("initFailed").d("reason", "")); return false; } - ACSDK_INFO(LX(__func__).d("activeAuthAdapter", m_activeAdapterId).sensitive("activeUserId", m_activeUserId)); + ACSDK_INFO(LX("init").d("activeAuthAdapter", m_activeAdapterId).sensitive("activeUserId", m_activeUserId)); return true; } void AuthorizationManager::clearDataLocked() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("clearDataLocked")); if (m_activeAdapter) { m_activeAdapter->reset(); @@ -104,7 +98,7 @@ void AuthorizationManager::clearDataLocked() { } void AuthorizationManager::clearData() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("clearData")); std::lock_guard lock(m_mutex); clearDataLocked(); @@ -114,10 +108,10 @@ void AuthorizationManager::reportStateChange( const avsCommon::sdkInterfaces::AuthObserverInterface::FullState& state, const std::string& authId, const std::string& userId) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("reportStateChange")); if (authId.empty()) { - ACSDK_ERROR(LX(__func__).d("reason", "emptyAuthId")); + ACSDK_ERROR(LX("reportStateChangeFailed").d("reason", "emptyAuthId")); return; } @@ -126,11 +120,12 @@ void AuthorizationManager::reportStateChange( void AuthorizationManager::setStateLocked(const avsCommon::sdkInterfaces::AuthObserverInterface::FullState& state) { if (state.state == m_authState.state) { - ACSDK_DEBUG5(LX(__func__).d("reason", "sameState").d("state", state.state).d("action", "skipping")); + ACSDK_DEBUG5( + LX("setStateLockedFailed").d("reason", "sameState").d("state", state.state).d("action", "skipping")); return; } - ACSDK_DEBUG5(LX(__func__) + ACSDK_DEBUG5(LX("setStateLocked") .d("fromState", m_authState.state) .d("toState", state.state) .d("fromError", m_authState.error) @@ -145,11 +140,11 @@ void AuthorizationManager::setStateLocked(const avsCommon::sdkInterfaces::AuthOb } void AuthorizationManager::setActiveLocked(const std::string& adapterId, const std::string& userId) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("setActiveLocked")); auto it = m_adapters.find(adapterId); if (it == m_adapters.end()) { - ACSDK_ERROR(LX(__func__).d("reason", "adapterNotRegistered").d("adapterId", adapterId)); + ACSDK_ERROR(LX("setActiveLockedFailed").d("reason", "adapterNotRegistered").d("adapterId", adapterId)); return; } @@ -160,7 +155,7 @@ void AuthorizationManager::setActiveLocked(const std::string& adapterId, const s void AuthorizationManager::persist(const std::string& adapterId, const std::string& userId) { if (!m_storage->store(adapterId, userId)) { - ACSDK_CRITICAL(LX(__func__) + ACSDK_CRITICAL(LX("persist") .d("reason", "failedToStoreAuthIdentifiers") .d("adapter", m_activeAdapterId) .sensitive("userId", m_activeUserId)); @@ -171,13 +166,13 @@ void AuthorizationManager::handleTransition( const avsCommon::sdkInterfaces::AuthObserverInterface::FullState& newState, const std::string& authId, const std::string& userId) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("handleTransition")); std::unique_lock lock(m_mutex); auto it = m_adapters.find(authId); if (m_adapters.end() == it) { - ACSDK_ERROR(LX(__func__).d("reason", "unrecognizedAdapter").d("authId", authId)); + ACSDK_ERROR(LX("handleTransitionFailed").d("reason", "unrecognizedAdapter").d("authId", authId)); return; } @@ -186,7 +181,7 @@ void AuthorizationManager::handleTransition( bool interruptingAuthorization = false; if ((!m_activeAdapterId.empty() && m_activeAdapterId != authId) || (!m_activeUserId.empty() && m_activeUserId != userId)) { - ACSDK_INFO(LX(__func__) + ACSDK_INFO(LX("handleTransitionInterrupted") .d("reason", "interruptingAuthorizationDetected") .d("activeAdapterId", m_activeAdapterId) .sensitive("activeUserId", m_activeUserId) @@ -207,7 +202,7 @@ void AuthorizationManager::handleTransition( * an inconsistent state in authorization. Force a logout to protect * customer data. */ - ACSDK_ERROR(LX(__func__) + ACSDK_ERROR(LX("handleTransitionFailed") .d("reason", "mismatchingAdapter") .d("activeAdapterId", m_activeAdapterId) .d("incomingAdapterId", authId)); @@ -216,7 +211,7 @@ void AuthorizationManager::handleTransition( logoutHelper(lock); return; } else { - ACSDK_WARN(LX(__func__) + ACSDK_WARN(LX("handleTransitionFailed") .d("reason", "invalidStateNewAuth") .d("authId", authId) .sensitive("userId", userId) @@ -228,7 +223,10 @@ void AuthorizationManager::handleTransition( // From this point on the authorization interruption has been handled. if (newState.state == m_authState.state) { - ACSDK_DEBUG0(LX(__func__).d("reason", "sameState").d("authId", m_activeAdapter).d("state", newState.state)); + ACSDK_DEBUG0(LX("handleTransitionFailed") + .d("reason", "sameState") + .d("authId", m_activeAdapter) + .d("state", newState.state)); return; } @@ -265,7 +263,7 @@ void AuthorizationManager::handleTransition( }; if (!valid) { - ACSDK_ERROR(LX(__func__) + ACSDK_ERROR(LX("handleTransitionFailed") .d("reason", "invalidTransition") .d("adapterId", authId) .d("from", m_authState.state) @@ -277,10 +275,10 @@ void AuthorizationManager::handleTransition( void AuthorizationManager::add( const std::shared_ptr& adapter) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("add")); if (!adapter) { - ACSDK_ERROR(LX(__func__).d("reason", "nullAdapter")); + ACSDK_ERROR(LX("addFailed").d("reason", "nullAdapter")); return; } @@ -290,14 +288,14 @@ void AuthorizationManager::add( } if (adapterId.empty()) { - ACSDK_ERROR(LX(__func__).d("reason", "emptyAuthAdapterId")); + ACSDK_ERROR(LX("addFailed").d("reason", "emptyAuthAdapterId")); return; } std::unique_lock lock(m_mutex); if (m_adapters.count(adapterId) != 0) { - ACSDK_ERROR(LX(__func__).d("reason", "alreadyAdded").d("adapterId", adapterId)); + ACSDK_ERROR(LX("addFailed").d("reason", "alreadyAdded").d("adapterId", adapterId)); return; } @@ -318,17 +316,17 @@ void AuthorizationManager::add( } void AuthorizationManager::logout() { - ACSDK_INFO(LX(__func__)); + ACSDK_INFO(LX("logout")); if (m_registrationManager) { m_registrationManager->logout(); } else { - ACSDK_CRITICAL(LX(__func__).d("reason", "nullRegistrationManager").m("Unable to Complete Logout")); + ACSDK_CRITICAL(LX("logoutFailed").d("reason", "nullRegistrationManager").m("Unable to Complete Logout")); } } void AuthorizationManager::logoutHelper(std::unique_lock& lock) { - ACSDK_INFO(LX(__func__)); + ACSDK_INFO(LX("logoutHelper")); lock.unlock(); logout(); @@ -336,15 +334,15 @@ void AuthorizationManager::logoutHelper(std::unique_lock& lock) { } avsCommon::sdkInterfaces::AuthObserverInterface::State AuthorizationManager::getState() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getState")); std::lock_guard lock(m_mutex); - ACSDK_DEBUG5(LX(__func__).d("state", m_authState.state)); + ACSDK_DEBUG5(LX("getState").d("state", m_authState.state)); return m_authState.state; } std::string AuthorizationManager::getActiveAuthorization() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getActiveAuthorization")); std::lock_guard lock(m_mutex); std::string activeAdapterId; @@ -356,10 +354,10 @@ std::string AuthorizationManager::getActiveAuthorization() { } void AuthorizationManager::addAuthObserver(std::shared_ptr observer) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("addAuthObserver")); if (!observer) { - ACSDK_ERROR(LX(__func__).d("reason", "nullObserver")); + ACSDK_ERROR(LX("addAuthObserverFailed").d("reason", "nullObserver")); return; } @@ -378,10 +376,10 @@ void AuthorizationManager::addAuthObserver(std::shared_ptr observer) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("removeAuthObserver")); if (!observer) { - ACSDK_ERROR(LX(__func__).d("reason", "nullObserver")); + ACSDK_ERROR(LX("removeAuthObserverFailed").d("reason", "nullObserver")); return; } @@ -390,7 +388,7 @@ void AuthorizationManager::removeAuthObserver( } std::string AuthorizationManager::getAuthToken() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getAuthToken")); std::lock_guard lock(m_mutex); std::string authToken; @@ -398,16 +396,16 @@ std::string AuthorizationManager::getAuthToken() { if (m_activeAdapter && AuthObserverInterface::State::REFRESHED == m_authState.state) { authToken = m_activeAdapter->getAuthToken(); } else { - ACSDK_WARN(LX(__func__).d("reason", "noActiveAdapter")); + ACSDK_WARN(LX("getAuthTokenFailed").d("reason", "noActiveAdapter")); } - ACSDK_DEBUG0(LX(__func__).sensitive("token", authToken)); + ACSDK_DEBUG0(LX("getAuthToken").sensitive("token", authToken)); return authToken; } void AuthorizationManager::onAuthFailure(const std::string& token) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("onAuthFailure")); std::lock_guard lock(m_mutex); if (m_activeAdapter) { @@ -416,7 +414,7 @@ void AuthorizationManager::onAuthFailure(const std::string& token) { } void AuthorizationManager::doShutdown() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("doShutdown")); m_executor.shutdown(); diff --git a/core/Authorization/acsdkAuthorization/src/AuthorizationManagerStorage.cpp b/core/Authorization/acsdkAuthorization/src/AuthorizationManagerStorage.cpp index fed9b67339..edf9705073 100644 --- a/core/Authorization/acsdkAuthorization/src/AuthorizationManagerStorage.cpp +++ b/core/Authorization/acsdkAuthorization/src/AuthorizationManagerStorage.cpp @@ -14,18 +14,11 @@ */ #include -#include +#include /// String to identify log entries originating from this file. static const std::string TAG{"AuthorizationManagerStorage"}; -/** - * 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 acsdkAuthorization { @@ -46,10 +39,10 @@ static const std::string USER_ID_KEY = "userId"; std::shared_ptr AuthorizationManagerStorage::create( const std::shared_ptr& storage) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("create")); if (!storage) { - ACSDK_ERROR(LX(__func__).d("isStorageNull", !storage)); + ACSDK_ERROR(LX("createFailed").d("isStorageNull", !storage)); return nullptr; } @@ -68,12 +61,12 @@ AuthorizationManagerStorage::AuthorizationManagerStorage( } bool AuthorizationManagerStorage::initializeDatabase() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("initializeDatabase")); std::lock_guard lock(m_mutex); if (!openLocked()) { if (!m_storage->createDatabase() || !openLocked()) { - ACSDK_ERROR(LX(__func__).d("reason", "createDatabaseFailed")); + ACSDK_ERROR(LX("initializeDatabaseFailed").d("reason", "createDatabaseFailed")); return false; } } @@ -81,7 +74,7 @@ bool AuthorizationManagerStorage::initializeDatabase() { bool exists = false; if (!m_storage->tableExists(COMPONENT_NAME, AUTH_STATE_TABLE, &exists)) { - ACSDK_ERROR(LX(__func__).d("reason", "checkTableExistenceFailed")); + ACSDK_ERROR(LX("initializeDatabaseFailed").d("reason", "checkTableExistenceFailed")); return false; } @@ -91,7 +84,7 @@ bool AuthorizationManagerStorage::initializeDatabase() { AUTH_STATE_TABLE, MiscStorageInterface::KeyType::STRING_KEY, MiscStorageInterface::ValueType::STRING_VALUE)) { - ACSDK_ERROR(LX(__func__).d("reason", "createTableFailed")); + ACSDK_ERROR(LX("initializeDatabaseFailed").d("reason", "createTableFailed")); return false; } } @@ -100,13 +93,13 @@ bool AuthorizationManagerStorage::initializeDatabase() { } bool AuthorizationManagerStorage::openLocked() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("openLocked")); return m_storage->isOpened() || m_storage->open(); } bool AuthorizationManagerStorage::store(const std::string& adapterId, const std::string& userId) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("store")); std::lock_guard lock(m_mutex); std::unordered_map valueContainer; @@ -114,63 +107,57 @@ bool AuthorizationManagerStorage::store(const std::string& adapterId, const std: m_storage->load(COMPONENT_NAME, AUTH_STATE_TABLE, &valueContainer); if (valueContainer.size() != 0) { - ACSDK_WARN(LX(__func__).d("reason", "tableNotEmpty")); + ACSDK_WARN(LX("storeFailed").d("reason", "tableNotEmpty")); } if (!m_storage->put(COMPONENT_NAME, AUTH_STATE_TABLE, AUTH_ADAPTER_ID_KEY, adapterId)) { - ACSDK_ERROR(LX(__func__).d("reason", "storeAdapterIdFailed").d("adapterId", adapterId)); + ACSDK_ERROR(LX("storeFailed").d("reason", "storeAdapterIdFailed").d("adapterId", adapterId)); return false; } if (!m_storage->put(COMPONENT_NAME, AUTH_STATE_TABLE, USER_ID_KEY, userId)) { - ACSDK_ERROR(LX(__func__).d("reason", "storeUserIdFailed").d("userId", userId)); + ACSDK_ERROR(LX("storeFailed").d("reason", "storeUserIdFailed").d("userId", userId)); return false; } return true; } -bool AuthorizationManagerStorage::load(std::string* adapterId, std::string* userId) { - ACSDK_DEBUG5(LX(__func__)); - - if (!adapterId || !userId) { - ACSDK_ERROR(LX(__func__).d("reason", "nullptr").d("isAdapterIdNull", !adapterId).d("isUserIdNull", !userId)); - - return false; - } +bool AuthorizationManagerStorage::load(std::string& adapterId, std::string& userId) { + ACSDK_DEBUG5(LX("load")); std::lock_guard lock(m_mutex); std::unordered_map valueContainer; if (!m_storage->load(COMPONENT_NAME, AUTH_STATE_TABLE, &valueContainer)) { - ACSDK_ERROR(LX(__func__)); + ACSDK_ERROR(LX("loadFailed").d("reason", "storageLoadError")); return false; } auto it = valueContainer.find(AUTH_ADAPTER_ID_KEY); if (it == valueContainer.end()) { - ACSDK_DEBUG0(LX(__func__).d("reason", "missingAuthAdapterId")); - *adapterId = ""; + ACSDK_DEBUG0(LX("loadFailed").d("reason", "missingAuthAdapterId")); + adapterId.clear(); } else { - *adapterId = it->second; + adapterId = it->second; } it = valueContainer.find(USER_ID_KEY); if (it == valueContainer.end()) { - ACSDK_DEBUG0(LX(__func__).d("reason", "missingUserId")); - *userId = ""; + ACSDK_DEBUG0(LX("loadFailed").d("reason", "missingUserId")); + userId.clear(); } else { - *userId = it->second; + userId = it->second; } - ACSDK_DEBUG5(LX(__func__).d("authAdapterId", *adapterId).d("userId", *userId)); + ACSDK_DEBUG5(LX("loadFailed").d("authAdapterId", adapterId).d("userId", userId)); return true; } void AuthorizationManagerStorage::clear() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("clear")); std::lock_guard lock(m_mutex); m_storage->clearTable(COMPONENT_NAME, AUTH_STATE_TABLE); } diff --git a/core/Authorization/acsdkAuthorization/src/CMakeLists.txt b/core/Authorization/acsdkAuthorization/src/CMakeLists.txt index 35a680abdc..bc2ec2ba26 100644 --- a/core/Authorization/acsdkAuthorization/src/CMakeLists.txt +++ b/core/Authorization/acsdkAuthorization/src/CMakeLists.txt @@ -1,26 +1,30 @@ add_definitions("-DACSDK_LOG_MODULE=authorizationManager") -add_library( - acsdkAuthorization SHARED - AuthorizationManager.cpp - AuthorizationManagerStorage.cpp - LWA/LWAAuthorizationAdapter.cpp - LWA/LWAAuthorizationConfiguration.cpp - LWA/SQLiteLWAAuthorizationStorage.cpp -) +set(acsdkAuthorization_SOURCES + AuthorizationManager.cpp + AuthorizationManagerStorage.cpp + LWA/LWAAuthorizationAdapter.cpp + LWA/LWAAuthorizationConfiguration.cpp + LWA/LWAAuthorizationStorage.cpp + LWA/LWAStorageConstants.cpp + LWA/LWAStorageDataMigration.cpp + ) -target_include_directories(acsdkAuthorization PUBLIC - "${acsdkAuthorization_SOURCE_DIR}/include" -) +set(acsdkAuthorization_LIBRARIES + acsdkAuthorizationInterfaces + acsdkCryptoInterfaces + acsdkManufactory + acsdkProperties + AVSCommon + RegistrationManager + RegistrationManagerInterfaces + SQLiteStorage + ) -target_link_libraries(acsdkAuthorization - acsdkAuthorizationInterfaces - acsdkManufactory - AVSCommon - RegistrationManager - RegistrationManagerInterfaces - SQLiteStorage -) +add_library(acsdkAuthorization ${acsdkAuthorization_SOURCES}) +target_include_directories(acsdkAuthorization PUBLIC "${acsdkAuthorization_SOURCE_DIR}/include") +target_include_directories(acsdkAuthorization PRIVATE "${acsdkAuthorization_SOURCE_DIR}/privateInclude") +target_link_libraries(acsdkAuthorization PUBLIC ${acsdkAuthorization_LIBRARIES}) # install target asdk_install() diff --git a/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationAdapter.cpp b/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationAdapter.cpp index 620fb35182..2a3fe9bf3f 100644 --- a/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationAdapter.cpp +++ b/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationAdapter.cpp @@ -15,23 +15,20 @@ #include #include -#include #include #include #include #include #include -#include -#include #include #include #include #include -#include -#include "acsdkAuthorization/LWA/LWAAuthorizationAdapter.h" +#include +#include namespace alexaClientSDK { namespace acsdkAuthorization { @@ -49,13 +46,6 @@ using namespace rapidjson; /// String to identify log entries originating from this file. static const std::string TAG("LWAAuthorizationAdapter"); -/** - * 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) - /// Key for user_code values in JSON returned by @c LWA static const char JSON_KEY_USER_CODE[] = "user_code"; @@ -366,7 +356,7 @@ std::shared_ptr LWAAuthorizationAdapter::create( std::unique_ptr httpGet, const std::string& adapterId) { if (!configuration || !httpPost || !deviceInfo || !storage) { - ACSDK_ERROR(LX(__func__) + ACSDK_ERROR(LX("createFailed") .d("reason", "nullptr") .d("configurationNull", !configuration) .d("httpPostNull", !httpPost) @@ -406,48 +396,44 @@ LWAAuthorizationAdapter::LWAAuthorizationAdapter( } LWAAuthorizationAdapter::~LWAAuthorizationAdapter() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("~LWAAuthorizationAdapter")); stop(); } bool LWAAuthorizationAdapter::shouldStopRetrying() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("shouldStopRetrying")); std::lock_guard lock(m_mutex); return shouldStopRetryingLocked(); } bool LWAAuthorizationAdapter::shouldStopRetryingLocked() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("shouldStopRetryingLocked")); return m_isClearingData || m_isShuttingDown; } LWAAuthorizationAdapter::FlowState LWAAuthorizationAdapter::retrievePersistedData() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("retrievePersistedData")); std::unique_lock lock(m_mutex); RefreshTokenResponse refreshTokenResponse; bool storageTokenSucceeded = m_storage->getRefreshToken(&refreshTokenResponse.refreshToken); bool userIdSucceeded = m_storage->getUserId(&m_userId); + if (!userIdSucceeded) { + ACSDK_INFO(LX("retrievePersistedData").m("noUserId")); + m_userId.clear(); + } - if (!storageTokenSucceeded && !userIdSucceeded) { + if (!storageTokenSucceeded) { // Not authorized, wait for authorization request. + ACSDK_INFO(LX("retrievePersistedData").m("noRefreshToken")); return FlowState::IDLE; - } else if (storageTokenSucceeded && userIdSucceeded) { + } else { setRefreshTokenResponseLocked(refreshTokenResponse); m_authState = AuthObserverInterface::State::AUTHORIZING; m_authError = AuthObserverInterface::Error::SUCCESS; return FlowState::REFRESHING_TOKEN; - } else { - ACSDK_ERROR(LX(__func__) - .d("reason", "retrievePersistedDataFailed") - .d("retrieveStorageTokenSucceeded", storageTokenSucceeded) - .d("retrieveUserIdSucceeded", userIdSucceeded)); - - m_authState = AuthObserverInterface::State::UNRECOVERABLE_ERROR; - m_authError = AuthObserverInterface::Error::INTERNAL_ERROR; - return FlowState::IDLE; } } @@ -526,7 +512,7 @@ AuthObserverInterface::Error LWAAuthorizationAdapter::receiveCodePairResponse(co } HTTPResponse LWAAuthorizationAdapter::sendCodePairRequest() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("sendCodePairRequest")); std::string scope = SCOPE_ALEXA_ALL; if (m_requestCustomerProfile) { @@ -550,7 +536,7 @@ HTTPResponse LWAAuthorizationAdapter::sendCodePairRequest() { avsCommon::utils::Optional LWAAuthorizationAdapter:: requestCodePair() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("requestCodePair")); int retryCount = 0; while (!shouldStopRetrying()) { @@ -596,7 +582,7 @@ avsCommon::utils::libcurlUtils::HTTPResponse LWAAuthorizationAdapter::sendTokenR if (requestTime) { *requestTime = std::chrono::steady_clock::now(); } else { - ACSDK_ERROR(LX(__func__).d("reason", "nullRequestTime")); + ACSDK_ERROR(LX("sendTokenRequestFailed").d("reason", "nullRequestTime")); } return m_httpPost->doPost( @@ -610,7 +596,7 @@ AuthObserverInterface::Error LWAAuthorizationAdapter::receiveTokenResponse( ACSDK_DEBUG5(LX("receiveTokenResponse").d("code", response.code)); if (!tokenResponse) { - ACSDK_ERROR(LX(__func__).d("reason", "nullTokenResponse")); + ACSDK_ERROR(LX("receiveTokenResponseFailed").d("reason", "nullTokenResponse")); return AuthObserverInterface::Error::UNKNOWN_ERROR; } @@ -668,10 +654,10 @@ AuthObserverInterface::Error LWAAuthorizationAdapter::receiveTokenResponse( } Optional LWAAuthorizationAdapter::exchangeToken(RefreshTokenResponse* tokenResponse) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("exchangeToken")); if (!tokenResponse) { - ACSDK_ERROR(LX(__func__).d("reason", "nullTokenResponse")); + ACSDK_ERROR(LX("exchangeTokenFailed").d("reason", "nullTokenResponse")); return Optional(AuthObserverInterface::Error::INTERNAL_ERROR); } @@ -724,7 +710,7 @@ Optional LWAAuthorizationAdapter::exchangeToken(Re } LWAAuthorizationAdapter::FlowState LWAAuthorizationAdapter::handleRequestingToken() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("handleRequestingToken")); updateStateAndNotifyManager(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS); @@ -758,26 +744,26 @@ LWAAuthorizationAdapter::FlowState LWAAuthorizationAdapter::handleRequestingToke return FlowState::IDLE; } case TokenExchangeMethod::NONE: - ACSDK_ERROR(LX(__func__).d("reason", "noAuthMethod")); + ACSDK_ERROR(LX("handleRequestingTokenFailed").d("reason", "noAuthMethod")); return FlowState::IDLE; } - ACSDK_ERROR(LX(__func__).d("reason", "missingEnumHandler")); + ACSDK_ERROR(LX("handleRequestingTokenFailed").d("reason", "missingEnumHandler")); return FlowState::IDLE; } bool LWAAuthorizationAdapter::getCustomerProfile(const std::string& accessToken) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getCustomerProfile")); std::string url = m_configuration->getCustomerProfileUrl() + "?access_token=" + accessToken; HTTPResponse response; if (m_httpGet) { response = m_httpGet->doGet(url, {}); } else { - ACSDK_DEBUG0(LX(__func__).d("reason", "usingFallbackGetLogic")); + ACSDK_DEBUG0(LX("getCustomerProfileFailed").d("reason", "usingFallbackGetLogic")); response = doGet(url); } - ACSDK_INFO(LX(__func__).sensitive("code", response.code).sensitive("body", response.body)); + ACSDK_INFO(LX("getCustomerProfile").sensitive("code", response.code).sensitive("body", response.body)); Document document; auto result = parseLWAResponse(response, &document); @@ -822,7 +808,7 @@ bool LWAAuthorizationAdapter::getCustomerProfile(const std::string& accessToken) avsCommon::utils::libcurlUtils::HTTPResponse LWAAuthorizationAdapter::requestRefresh( std::chrono::steady_clock::time_point* requestTime) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("requestRefresh")); const std::vector> postData = { {POST_KEY_GRANT_TYPE, POST_VALUE_REFRESH_TOKEN}, @@ -835,7 +821,7 @@ avsCommon::utils::libcurlUtils::HTTPResponse LWAAuthorizationAdapter::requestRef if (requestTime) { *requestTime = now; } else { - ACSDK_ERROR(LX(__func__).d("reason", "nullRequestTime")); + ACSDK_ERROR(LX("requestRefreshFailed").d("reason", "nullRequestTime")); } auto timeout = m_configuration->getRequestTimeout(); @@ -851,7 +837,7 @@ avsCommon::utils::libcurlUtils::HTTPResponse LWAAuthorizationAdapter::requestRef } LWAAuthorizationAdapter::FlowState LWAAuthorizationAdapter::handleRefreshingToken() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("handleRefreshingToken")); int retryCount = 0; std::chrono::steady_clock::time_point nextRefresh = @@ -885,7 +871,7 @@ LWAAuthorizationAdapter::FlowState LWAAuthorizationAdapter::handleRefreshingToke } if (isAboutToExpire) { - ACSDK_DEBUG0(LX(__func__).d("reason", "aboutToExpire")); + ACSDK_DEBUG0(LX("handleRefreshingTokenFailed").d("reason", "aboutToExpire")); m_refreshTokenResponse.accessToken.clear(); lock.unlock(); nextState = AuthObserverInterface::State::EXPIRED; @@ -939,7 +925,7 @@ LWAAuthorizationAdapter::FlowState LWAAuthorizationAdapter::handleRefreshingToke } void LWAAuthorizationAdapter::stop() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("stop")); { std::lock_guard lock(m_mutex); m_isShuttingDown = true; @@ -956,14 +942,14 @@ void LWAAuthorizationAdapter::stop() { } LWAAuthorizationAdapter::FlowState LWAAuthorizationAdapter::handleStopping() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("handleStopping")); return FlowState::STOPPING; } void LWAAuthorizationAdapter::updateStateAndNotifyManager( avsCommon::sdkInterfaces::AuthObserverInterface::State state, avsCommon::sdkInterfaces::AuthObserverInterface::Error error) { - ACSDK_DEBUG5(LX(__func__).d("state", state).d("error", error)); + ACSDK_DEBUG5(LX("updateStateAndNotifyManager").d("state", state).d("error", error)); std::string userId; std::shared_ptr manager; @@ -974,7 +960,7 @@ void LWAAuthorizationAdapter::updateStateAndNotifyManager( m_authState = state; m_authError = error; } else { - ACSDK_DEBUG5(LX(__func__).d("reason", "sameState").d("state", state)); + ACSDK_DEBUG5(LX("updateStateAndNotifyManagerFailed").d("reason", "sameState").d("state", state)); return; } @@ -985,18 +971,18 @@ void LWAAuthorizationAdapter::updateStateAndNotifyManager( if (manager) { manager->reportStateChange({state, error}, m_adapterId, userId); } else { - ACSDK_WARN(LX(__func__).d("reason", "nullManager")); + ACSDK_WARN(LX("updateStateAndNotifyManagerFailed").d("reason", "nullManager")); } } void LWAAuthorizationAdapter::setRefreshTokenResponse(const RefreshTokenResponse& response, bool persist) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("setRefreshTokenResponse")); std::lock_guard lock(m_mutex); setRefreshTokenResponseLocked(response, persist); } void LWAAuthorizationAdapter::setRefreshTokenResponseLocked(const RefreshTokenResponse& response, bool persist) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("setRefreshTokenResponseLocked")); m_refreshTokenResponse = response; @@ -1012,7 +998,7 @@ void LWAAuthorizationAdapter::setRefreshTokenResponseLocked(const RefreshTokenRe } LWAAuthorizationAdapter::FlowState LWAAuthorizationAdapter::handleClearingData() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("handleClearingData")); updateStateAndNotifyManager( avsCommon::sdkInterfaces::AuthObserverInterface::State::UNINITIALIZED, @@ -1025,12 +1011,12 @@ LWAAuthorizationAdapter::FlowState LWAAuthorizationAdapter::handleClearingData() bool LWAAuthorizationAdapter::isShuttingDown() { std::lock_guard lock(m_mutex); - ACSDK_DEBUG5(LX(__func__).d("shuttingDown", m_isShuttingDown)); + ACSDK_DEBUG5(LX("isShuttingDown").d("shuttingDown", m_isShuttingDown)); return m_isShuttingDown; } void LWAAuthorizationAdapter::handleAuthorizationFlow(FlowState state) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("handleAuthorizationFlow")); while (!isShuttingDown()) { auto nextFlowState = FlowState::STOPPING; @@ -1064,7 +1050,7 @@ void LWAAuthorizationAdapter::handleAuthorizationFlow(FlowState state) { bool LWAAuthorizationAdapter::init( const avsCommon::utils::configuration::ConfigurationNode& configuration, const std::shared_ptr& deviceInfo) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("init")); m_configuration = LWAAuthorizationConfiguration::create(configuration, deviceInfo); if (!m_configuration) { @@ -1087,19 +1073,19 @@ bool LWAAuthorizationAdapter::authorizeUsingCBLHelper( const std::shared_ptr& observer, bool requestCustomerProfile) { if (!observer) { - ACSDK_ERROR(LX(__func__).d("reason", "nullObserver")); + ACSDK_ERROR(LX("authorizeUsingCBLHelperFailed").d("reason", "nullObserver")); return false; } std::lock_guard lock(m_mutex); if (!m_manager) { - ACSDK_ERROR(LX(__func__).d("reason", "nullManager")); + ACSDK_ERROR(LX("authorizeUsingCBLHelperFailed").d("reason", "nullManager")); return false; } if (TokenExchangeMethod::NONE != m_authMethod) { - ACSDK_INFO(LX(__func__).d("reason", "authorizationInProgress")); + ACSDK_INFO(LX("authorizeUsingCBLHelperFailed").d("reason", "authorizationInProgress")); return false; } @@ -1110,7 +1096,7 @@ bool LWAAuthorizationAdapter::authorizeUsingCBLHelper( m_requestCustomerProfile = requestCustomerProfile; m_wake.notify_one(); } else { - ACSDK_INFO(LX(__func__).d("reason", "invalidState").d("m_authState", m_authState)); + ACSDK_INFO(LX("authorizeUsingCBLHelperFailed").d("reason", "invalidState").d("m_authState", m_authState)); return false; } @@ -1128,19 +1114,19 @@ bool LWAAuthorizationAdapter::authorizeUsingCBLWithCustomerProfile( std::string LWAAuthorizationAdapter::getId() { // m_adapterId is const, no need to lock. - ACSDK_DEBUG5(LX(__func__).d("id", m_adapterId)); + ACSDK_DEBUG5(LX("getId").d("id", m_adapterId)); return m_adapterId; } std::string LWAAuthorizationAdapter::getAuthToken() { std::lock_guard lock(m_mutex); - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getAuthToken")); return m_refreshTokenResponse.accessToken; } void LWAAuthorizationAdapter::reset() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("reset")); std::lock_guard lock(m_mutex); m_storage->clear(); @@ -1157,7 +1143,7 @@ void LWAAuthorizationAdapter::reset() { } void LWAAuthorizationAdapter::onAuthFailure(const std::string& authToken) { - ACSDK_DEBUG0(LX(__func__)); + ACSDK_DEBUG0(LX("onAuthFailure")); std::lock_guard lock(m_mutex); if (authToken.empty() || authToken == m_refreshTokenResponse.accessToken) { @@ -1168,24 +1154,24 @@ void LWAAuthorizationAdapter::onAuthFailure(const std::string& authToken) { } AuthObserverInterface::FullState LWAAuthorizationAdapter::getState() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getState")); std::lock_guard lock(m_mutex); return {m_authState, m_authError}; } std::shared_ptr LWAAuthorizationAdapter::getAuthorizationInterface() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("getAuthorizationInterface")); return shared_from_this(); } AuthObserverInterface::FullState LWAAuthorizationAdapter::onAuthorizationManagerReady( const std::shared_ptr& manager) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG5(LX("onAuthorizationManagerReady")); std::lock_guard lock(m_mutex); if (!manager) { - ACSDK_ERROR(LX(__func__).d("reason", "nullManager")); + ACSDK_ERROR(LX("onAuthorizationManagerReadyFailed").d("reason", "nullManager")); } else { m_manager = manager; } diff --git a/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationConfiguration.cpp b/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationConfiguration.cpp index 35e981e3ad..f3a9cdbad9 100644 --- a/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationConfiguration.cpp +++ b/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationConfiguration.cpp @@ -19,26 +19,22 @@ #include #include +#include #include -#include "acsdkAuthorization/LWA/LWAAuthorizationConfiguration.h" +#include +#include namespace alexaClientSDK { namespace acsdkAuthorization { namespace lwa { +using namespace avsCommon::utils::json::jsonUtils; using namespace rapidjson; /// String to identify log entries originating from this file. static const std::string TAG("LWAAuthorizationConfiguration"); -/** - * 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 LWAAuthorization static const std::string CONFIG_KEY_LWA_AUTHORIZATION = "lwaAuthorization"; @@ -63,6 +59,9 @@ static const std::string CONFIG_KEY_DEFAULT_LOCALE = "defaultLocale"; /// Default value for settings.locale. static const std::string CONFIG_VALUE_DEFAULT_LOCALE = "en-US"; +/// Index for primary locale in a multilingual locales vector. +static const int PRIMARY_LOCALE_INDEX = 0; + /// Key for alexa:all values in JSON sent to @c LWA static const char JSON_KEY_ALEXA_ALL[] = "alexa:all"; @@ -136,8 +135,21 @@ bool LWAAuthorizationConfiguration::init( &m_accessTokenRefreshHeadStart, DEFAULT_ACCESS_TOKEN_REFRESH_HEAD_START); - configurationRoot[CONFIG_KEY_DEVICE_SETTINGS].getString( - CONFIG_KEY_DEFAULT_LOCALE, &m_locale, CONFIG_VALUE_DEFAULT_LOCALE); + /// Check if default locale is multilingual. + auto localesArray = configurationRoot[CONFIG_KEY_DEVICE_SETTINGS].getArray(CONFIG_KEY_DEFAULT_LOCALE); + if (localesArray) { + /// The first value in a multilingual locale denotes the primary locale. + auto localesStringArray = retrieveStringArray>(localesArray.serialize()); + if (PRIMARY_LOCALE_INDEX < localesStringArray.size()) { + m_locale = localesStringArray[PRIMARY_LOCALE_INDEX]; + } + } + + /// Fallback if the default locale is not multilingual. + if (m_locale.empty()) { + configurationRoot[CONFIG_KEY_DEVICE_SETTINGS].getString( + CONFIG_KEY_DEFAULT_LOCALE, &m_locale, CONFIG_VALUE_DEFAULT_LOCALE); + } if (!initScopeData()) { ACSDK_ERROR(LX("initFailed").d("reason", "initScopeDataFailed")); diff --git a/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationStorage.cpp b/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationStorage.cpp new file mode 100644 index 0000000000..28b9648f5f --- /dev/null +++ b/core/Authorization/acsdkAuthorization/src/LWA/LWAAuthorizationStorage.cpp @@ -0,0 +1,328 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace lwa { + +using namespace ::alexaClientSDK::acsdkAuthorizationInterfaces; +using namespace ::alexaClientSDK::acsdkAuthorizationInterfaces::lwa; +using namespace ::alexaClientSDK::acsdkProperties; +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces; +using namespace ::alexaClientSDK::avsCommon::utils::configuration; +using namespace ::alexaClientSDK::avsCommon::utils::logger; +using namespace ::alexaClientSDK::storage; + +/// String to identify log entries originating from this file. +static const std::string TAG("LWAAuthorizationStorage"); + +/// Name of default @c ConfigurationNode for LWA +static const std::string CONFIG_KEY_LWA_AUTHORIZATION = "lwaAuthorization"; + +/// Name of @c databaseFilePath value in the @c ConfigurationNode. +static const std::string CONFIG_KEY_DB_FILE_PATH_KEY = "databaseFilePath"; + +std::shared_ptr LWAAuthorizationStorage::createStorage( + const std::shared_ptr& propertiesFactory) { + if (!propertiesFactory) { + ACSDK_ERROR(LX("createStorageFailed").d("reason", "propertiesFactoryNull")); + return nullptr; + } + + return std::shared_ptr(new LWAAuthorizationStorage(propertiesFactory)); +} + +bool LWAAuthorizationStorage::createStorageFileAndSetPermissions(const std::string& filepath) noexcept { + using namespace avsCommon::utils::filesystem; + + if (exists(filepath)) { + ACSDK_DEBUG9( + LX("createStorageFileAndSetPermissionsSuccess").d("reason", "fileExists").sensitive("path", filepath)); + return true; + } + + std::ofstream file{filepath, std::ofstream::out | std::ofstream::binary}; + if (!file.is_open()) { + ACSDK_DEBUG9( + LX("createStorageFileAndSetPermissionsFailed").d("reason", "createError").sensitive("path", filepath)); + return false; + } + file.close(); + + if (!changePermissions(filepath, OWNER_READ | OWNER_WRITE)) { + ACSDK_DEBUG9(LX("createStorageFileAndSetPermissionsFailed").sensitive("path", filepath)); + return false; + } + + ACSDK_DEBUG9(LX("createStorageFileAndSetPermissionsSuccess").sensitive("path", filepath)); + + return true; +} + +std::shared_ptr LWAAuthorizationStorage::createSQLiteStorage( + const std::shared_ptr& configurationRoot, + const std::string& storageRootKey) { + ACSDK_DEBUG5(LX("createSQLiteStorage")); + + if (!configurationRoot) { + ACSDK_ERROR(LX("createSQLiteStorageFailed").d("reason", "nullConfigurationRoot")); + return nullptr; + } + + std::string key = storageRootKey.empty() ? CONFIG_KEY_LWA_AUTHORIZATION : storageRootKey; + + auto configNode = (*configurationRoot)[key]; + if (!configNode) { + ACSDK_ERROR(LX("createSQLiteStorageFailed").d("reason", "missingConfigurationValue").d("key", key)); + return nullptr; + } + + std::string databaseFilePath; + if (!configNode.getString(CONFIG_KEY_DB_FILE_PATH_KEY, &databaseFilePath) || databaseFilePath.empty()) { + ACSDK_ERROR(LX("createSQLiteStorageFailed") + .d("reason", "missingConfigurationValue") + .d("key", CONFIG_KEY_DB_FILE_PATH_KEY)); + return nullptr; + } + + if (!createStorageFileAndSetPermissions(databaseFilePath)) { + ACSDK_ERROR( + LX("createSQLiteStorageFailed").d("reason", "failedToCreateDBFile").sensitive("path", databaseFilePath)); + return nullptr; + } + + auto storage = storage::sqliteStorage::SQLiteMiscStorage::create(databaseFilePath); + if (!storage || !storage->open()) { + ACSDK_ERROR(LX("createSQLiteStorageFailed").d("reason", "openFailed").sensitive("path", databaseFilePath)); + return nullptr; + } + + return std::move(storage); +} + +std::shared_ptr LWAAuthorizationStorage::createLWAAuthorizationStorageInterface( + const std::shared_ptr& configurationRoot, + const std::string& storageRootKey, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) { + ACSDK_DEBUG0(LX("createLWAAuthorizationStorageInterface")); + + bool useEncryptionAtRest = true; + if (!cryptoFactory) { + ACSDK_WARN(LX("createLWAAuthorizationStorageInterface") + .m("encryptionAtRestDisabled") + .d("reason", "cryptoFactoryNull")); + useEncryptionAtRest = false; + } + if (!keyStore) { + ACSDK_WARN( + LX("createLWAAuthorizationStorageInterface").m("encryptionAtRestDisabled").d("reason", "keyStoreNull")); + useEncryptionAtRest = false; + } + auto storage = createSQLiteStorage(configurationRoot, storageRootKey); + if (!storage) { + ACSDK_ERROR(LX("createLWAAuthorizationStorageInterfaceFailed").d("reason", "storageNull")); + return nullptr; + } + + std::shared_ptr propertiesFactory; + if (useEncryptionAtRest) { + ACSDK_INFO(LX("createLWAAuthorizationStorageInterface").m("encryptionAtRestEnabled")); + propertiesFactory = + createEncryptedPropertiesFactory(storage, SimpleMiscStorageUriMapper::create(), cryptoFactory, keyStore); + } else { + propertiesFactory = createPropertiesFactory(storage, SimpleMiscStorageUriMapper::create()); + } + + if (!propertiesFactory) { + ACSDK_ERROR(LX("createLWAAuthorizationStorageInterfaceFailed").d("reason", "propertiesFactoryNull")); + return nullptr; + } + + LWAStorageDataMigration{storage, propertiesFactory}.upgradeStorage(); + + return createStorage(propertiesFactory); +} + +LWAAuthorizationStorage::LWAAuthorizationStorage(const std::shared_ptr& propertiesFactory) : + m_propertiesFactory{propertiesFactory} { + ACSDK_DEBUG5(LX("LWAAuthorizationStorage")); +} + +LWAAuthorizationStorage::~LWAAuthorizationStorage() { + ACSDK_DEBUG5(LX("~LWAAuthorizationStorage")); +} + +bool LWAAuthorizationStorage::openOrCreate() { + ACSDK_DEBUG5(LX("openOrCreate")); + + m_properties = m_propertiesFactory->getProperties(CONFIG_URI); + if (!m_properties) { + ACSDK_ERROR(LX("openOrCreateFailed").d("reason", "propertiesGetError")); + return false; + } + + return true; +} + +bool LWAAuthorizationStorage::createDatabase() { + ACSDK_DEBUG5(LX("createDatabase")); + + return openOrCreate(); +} + +bool LWAAuthorizationStorage::open() { + ACSDK_DEBUG5(LX("open")); + + return openOrCreate(); +} + +bool LWAAuthorizationStorage::setRefreshToken(const std::string& refreshToken) { + ACSDK_DEBUG5(LX("setRefreshToken")); + + if (refreshToken.empty()) { + ACSDK_ERROR(LX("setRefreshTokenFailed").d("reason", "refreshTokenIsEmpty")); + return false; + } + + if (!m_properties) { + ACSDK_ERROR(LX("setRefreshTokenFailed").d("reason", "storageClosed")); + return false; + } + + if (!m_properties->putString(REFRESH_TOKEN_PROPERTY_NAME, refreshToken)) { + ACSDK_ERROR(LX("setRefreshTokenFailed").d("reason", "clearTableFailed")); + return false; + } + + return true; +} + +bool LWAAuthorizationStorage::clearRefreshToken() { + ACSDK_DEBUG5(LX("clearRefreshToken")); + + if (!m_properties) { + ACSDK_ERROR(LX("clearRefreshTokenFailed").d("reason", "storageClosed")); + return false; + } + + if (!m_properties->remove(REFRESH_TOKEN_PROPERTY_NAME)) { + ACSDK_ERROR(LX("clearRefreshTokenFailed").d("reason", "clearTableFailed")); + return false; + } + + return true; +} + +bool LWAAuthorizationStorage::getRefreshToken(std::string* refreshToken) { + ACSDK_DEBUG5(LX("getRefreshToken")); + + if (!refreshToken) { + ACSDK_ERROR(LX("getRefreshTokenFailed").d("reason", "nullRefreshToken")); + return false; + } + + if (!m_properties) { + ACSDK_ERROR(LX("getRefreshTokenFailed").d("reason", "storageClosed")); + return false; + } + + std::string tmp; + if (!m_properties->getString(REFRESH_TOKEN_PROPERTY_NAME, tmp)) { + ACSDK_ERROR(LX("getRefreshTokenFailed").d("reason", "stepFailed")); + return false; + } + + if (tmp.empty()) { + ACSDK_ERROR(LX("getRefreshTokenFailed").d("reason", "emptyValue")); + return false; + } + + *refreshToken = tmp; + + return true; +} + +bool LWAAuthorizationStorage::setUserId(const std::string& userId) { + ACSDK_DEBUG5(LX("setUserId").sensitive("userId", userId)); + + if (!m_properties) { + ACSDK_ERROR(LX("setUserIdFailed").d("reason", "storageClosed")); + return false; + } + + if (!m_properties->putString(USER_ID_PROPERTY_NAME, userId)) { + ACSDK_ERROR(LX("setUserIdFailed").d("reason", "putStringFailed")); + return false; + } + + return true; +} + +bool LWAAuthorizationStorage::getUserId(std::string* userId) { + ACSDK_DEBUG5(LX("getUserId")); + + if (!userId) { + ACSDK_ERROR(LX("getUserIdFailed").d("reason", "nullRefreshToken")); + return false; + } + + if (!m_properties) { + ACSDK_ERROR(LX("getUserIdFailed").d("reason", "storageClosed")); + return false; + } + + std::string tmp; + if (!m_properties->getString(USER_ID_PROPERTY_NAME, tmp)) { + ACSDK_ERROR(LX("getUserIdFailed").d("reason", "createStatementFailed")); + return false; + } + + if (tmp.empty()) { + ACSDK_ERROR(LX("getUserIdFailed").d("reason", "emptyValue")); + return false; + } + + *userId = tmp; + + return true; +} + +bool LWAAuthorizationStorage::clear() { + ACSDK_DEBUG5(LX("clear")); + + if (!m_properties) { + ACSDK_ERROR(LX("clearFailed").d("reason", "storageClosed")); + return false; + } + + // Be careful of short circuiting here. + return m_properties->clear(); +} + +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK diff --git a/core/Authorization/acsdkAuthorization/src/LWA/LWAStorageConstants.cpp b/core/Authorization/acsdkAuthorization/src/LWA/LWAStorageConstants.cpp new file mode 100644 index 0000000000..97c1473000 --- /dev/null +++ b/core/Authorization/acsdkAuthorization/src/LWA/LWAStorageConstants.cpp @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkAuthorization { +namespace lwa { + +const std::string REFRESH_TOKEN_PROPERTY_NAME{"refreshToken"}; + +const std::string USER_ID_PROPERTY_NAME{"userId"}; + +const std::string CONFIG_URI{"config/LWAAuthorizationStorage"}; + +const std::string REFRESH_TOKEN_TABLE_NAME{"refreshToken"}; + +const std::string REFRESH_TOKEN_COLUMN_NAME{"refreshToken"}; + +const std::string USER_ID_TABLE_NAME{"userId"}; + +const std::string USER_ID_COLUMN_NAME{"userId"}; + +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK diff --git a/core/Authorization/acsdkAuthorization/src/LWA/LWAStorageDataMigration.cpp b/core/Authorization/acsdkAuthorization/src/LWA/LWAStorageDataMigration.cpp new file mode 100644 index 0000000000..bd6fb31c02 --- /dev/null +++ b/core/Authorization/acsdkAuthorization/src/LWA/LWAStorageDataMigration.cpp @@ -0,0 +1,145 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace lwa { + +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces; +using namespace ::alexaClientSDK::storage; +using namespace ::alexaClientSDK::avsCommon::utils::logger; +using namespace ::alexaClientSDK::avsCommon::utils::error; + +/// String to identify log entries originating from this file. +static const std::string TAG("LWAStorageDataMigration"); + +LWAStorageDataMigration::LWAStorageDataMigration( + const std::shared_ptr& storage, + const std::shared_ptr& propertiesFactory) noexcept : + m_storage{storage}, + m_propertiesFactory{propertiesFactory} { +} + +void LWAStorageDataMigration::upgradeStorage() noexcept { + if (!m_storage) { + ACSDK_ERROR(LX("upgradeStorageFailed").d("reason", "storageNull")); + return; + } + if (!m_propertiesFactory) { + ACSDK_ERROR(LX("upgradeStorageFailed").d("reason", "propertiesFactoryNull")); + return; + } + + auto& db = m_storage->getDatabase(); + if (db.tableExists(REFRESH_TOKEN_TABLE_NAME) || db.tableExists(USER_ID_TABLE_NAME)) { + auto properties = m_propertiesFactory->getProperties(CONFIG_URI); + if (!properties) { + ACSDK_ERROR(LX("upgradeStorageFailed").d("reason", "getPropertiesError")); + return; + } + + if (!migrateSinglePropertyTable(USER_ID_TABLE_NAME, USER_ID_COLUMN_NAME, properties, USER_ID_PROPERTY_NAME)) { + ACSDK_WARN(LX("migrateLegacyTablesError").m("errorWhileMigratingUserId")); + } + + if (!migrateSinglePropertyTable( + REFRESH_TOKEN_TABLE_NAME, REFRESH_TOKEN_COLUMN_NAME, properties, REFRESH_TOKEN_PROPERTY_NAME)) { + ACSDK_WARN(LX("migrateLegacyTablesError").m("errorWhileMigratingRefreshToken")); + } + } +} + +bool LWAStorageDataMigration::migrateSinglePropertyTable( + const std::string& tableName, + const std::string& columnName, + const std::shared_ptr& properties, + const std::string& propertyName) noexcept { + if (!m_storage) { + ACSDK_ERROR(LX("migrateSinglePropertyTableFailed").d("reason", "storageNull")); + return false; + } + + if (!properties) { + ACSDK_ERROR(LX("migrateSinglePropertyTableFailed").d("reason", "propertiesNull")); + return false; + } + + auto& db = m_storage->getDatabase(); + if (db.tableExists(tableName)) { + FinallyGuard clearAndDropTable{[&]() -> void { + if (!db.clearTable(tableName)) { + ACSDK_WARN(LX("migrateSinglePropertyTable").m("tableClearFailed").sensitive("tableName", tableName)); + } + + if (!db.dropTable(tableName)) { + ACSDK_WARN(LX("migrateSinglePropertyTable").m("tableDropFailed").sensitive("tableName", tableName)); + } + + ACSDK_DEBUG0(LX("migrateSinglePropertyTable").m("tableRemoved").sensitive("tableName", tableName)); + }}; + + std::string sqlString = "SELECT * FROM " + tableName + ";"; + auto statement = db.createStatement(sqlString); + if (!statement) { + ACSDK_ERROR(LX("migrateSinglePropertyTableFailed") + .d("reason", "createStatementFailed") + .sensitive("tableName", tableName)); + return false; + } + + if (!statement->step()) { + ACSDK_ERROR( + LX("migrateSinglePropertyTableFailed").d("reason", "stepFailed").sensitive("tableName", tableName)); + return false; + } + + if (statement->getStepResult() != SQLITE_ROW) { + ACSDK_DEBUG5(LX("migrateSinglePropertyTableSuccess") + .d("reason", "noDataToMigrate") + .sensitive("tableName", tableName)); + return true; + } + + std::string resultColumnName = statement->getColumnName(0); + if (columnName != resultColumnName) { + ACSDK_ERROR(LX("migrateSinglePropertyTableFailed") + .d("reason", "unexpectedColumnName") + .sensitive("tableName", tableName) + .sensitive("columnName", resultColumnName) + .sensitive("expectedName", columnName)); + return false; + } + + auto text = statement->getColumnText(0); + + if (!properties->putString(propertyName, text)) { + ACSDK_ERROR( + LX("migrateSinglePropertyTableFailed").d("reason", "putStringError").sensitive("tableName", tableName)); + return false; + } + } + + ACSDK_DEBUG5(LX("migrateSinglePropertyTableSuccess").sensitive("tableName", tableName)); + return true; +} + +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK diff --git a/core/Authorization/acsdkAuthorization/src/LWA/SQLiteLWAAuthorizationStorage.cpp b/core/Authorization/acsdkAuthorization/src/LWA/SQLiteLWAAuthorizationStorage.cpp deleted file mode 100644 index 794e1e3e39..0000000000 --- a/core/Authorization/acsdkAuthorization/src/LWA/SQLiteLWAAuthorizationStorage.cpp +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkAuthorization/LWA/SQLiteLWAAuthorizationStorage.h" - -namespace alexaClientSDK { -namespace acsdkAuthorization { -namespace lwa { - -using namespace acsdkAuthorizationInterfaces; -using namespace acsdkAuthorizationInterfaces::lwa; -using namespace avsCommon::utils::logger; -using namespace avsCommon::utils::string; -using namespace avsCommon::utils::file; -using namespace alexaClientSDK::storage::sqliteStorage; - -/// String to identify log entries originating from this file. -static const std::string TAG("SQLiteLWAAuthorizationStorage"); - -/** - * 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 default @c ConfigurationNode for LWA -static const std::string CONFIG_KEY_LWA_AUTHORIZATION = "lwaAuthorization"; - -/// Name of @c databaseFilePath value in the @c ConfigurationNode. -static const std::string CONFIG_KEY_DB_FILE_PATH_KEY = "databaseFilePath"; - -/// The name of the refreshToken table. -#define REFRESH_TOKEN_TABLE_NAME "refreshToken" - -/// The name of the refreshToken column. -#define REFRESH_TOKEN_COLUMN_NAME "refreshToken" - -/// The name of the userId table. -#define USER_ID_TABLE_NAME "userId" - -/// The name of the userId column. -#define USER_ID_COLUMN_NAME "userId" - -/// String for creating the refreshToken table -static const std::string CREATE_REFRESH_TOKEN_TABLE_SQL_STRING = - "CREATE TABLE " REFRESH_TOKEN_TABLE_NAME " (" REFRESH_TOKEN_COLUMN_NAME " TEXT);"; - -/// String for creating the userId table -static const std::string CREATE_USER_ID_TABLE_SQL_STRING = - "CREATE TABLE " USER_ID_TABLE_NAME " (" USER_ID_COLUMN_NAME " TEXT);"; - -std::shared_ptr SQLiteLWAAuthorizationStorage::createLWAAuthorizationStorageInterface( - const std::shared_ptr& configurationRootPtr, - const std::string& storageRootKey) { - ACSDK_DEBUG5(LX(__func__)); - - if (!configurationRootPtr) { - ACSDK_ERROR(LX(__func__).d("reason", "nullConfigurationRoot")); - return nullptr; - } - - auto configurationRoot = *configurationRootPtr; - - std::string key = storageRootKey.empty() ? CONFIG_KEY_LWA_AUTHORIZATION : storageRootKey; - - auto storageConfigRoot = configurationRoot[key]; - if (!storageConfigRoot) { - ACSDK_ERROR(LX(__func__).d("reason", "missingConfigurationValue").d("key", key)); - return nullptr; - } - - std::string databaseFilePath; - if (!storageConfigRoot.getString(CONFIG_KEY_DB_FILE_PATH_KEY, &databaseFilePath) || databaseFilePath.empty()) { - ACSDK_ERROR(LX("createFailed").d("reason", "missingConfigurationValue").d("key", CONFIG_KEY_DB_FILE_PATH_KEY)); - return nullptr; - } - - return std::shared_ptr(new SQLiteLWAAuthorizationStorage(databaseFilePath)); -} - -SQLiteLWAAuthorizationStorage::~SQLiteLWAAuthorizationStorage() { - ACSDK_DEBUG5(LX(__func__)); - - close(); -} - -bool SQLiteLWAAuthorizationStorage::openOrCreate() { - ACSDK_DEBUG5(LX(__func__)); - - std::lock_guard lock(m_mutex); - - if (!m_database.open()) { - if (!m_database.initialize()) { - ACSDK_ERROR(LX("createDatabaseFailed").d("reason", "SQLiteCreateDatabaseFailed")); - return false; - } - } - - if (!m_database.tableExists(REFRESH_TOKEN_TABLE_NAME)) { - if (!m_database.performQuery(CREATE_REFRESH_TOKEN_TABLE_SQL_STRING)) { - ACSDK_ERROR(LX("createDatabaseFailed").d("reason", "failed to create refreshToken table")); - close(); - return false; - } - } - - if (!m_database.tableExists(USER_ID_TABLE_NAME)) { - if (!m_database.performQuery(CREATE_USER_ID_TABLE_SQL_STRING)) { - ACSDK_ERROR(LX("createDatabaseFailed").d("reason", "failed to create userId table")); - close(); - return false; - } - } - - return true; -} - -bool SQLiteLWAAuthorizationStorage::createDatabase() { - ACSDK_DEBUG5(LX(__func__)); - - std::lock_guard lock(m_mutex); - - if (!m_database.initialize()) { - ACSDK_ERROR(LX("createDatabaseFailed").d("reason", "SQLiteCreateDatabaseFailed")); - return false; - } - - if (!m_database.performQuery(CREATE_REFRESH_TOKEN_TABLE_SQL_STRING)) { - ACSDK_ERROR(LX("createDatabaseFailed").d("reason", "failed to create refreshToken table")); - close(); - return false; - } - - if (!m_database.performQuery(CREATE_USER_ID_TABLE_SQL_STRING)) { - ACSDK_ERROR(LX("createDatabaseFailed").d("reason", "failed to create userId table")); - close(); - return false; - } - - return true; -} - -bool SQLiteLWAAuthorizationStorage::open() { - ACSDK_DEBUG5(LX(__func__)); - - std::lock_guard lock(m_mutex); - - if (!m_database.open()) { - ACSDK_DEBUG0(LX("openFailed").d("reason", "openSQLiteDatabaseFailed")); - return false; - } - - if (!m_database.tableExists(REFRESH_TOKEN_TABLE_NAME)) { - if (!m_database.performQuery(CREATE_REFRESH_TOKEN_TABLE_SQL_STRING)) { - ACSDK_ERROR(LX("createDatabaseFailed").d("reason", "failed to create refreshToken table")); - close(); - return false; - } - } - - if (!m_database.tableExists(USER_ID_TABLE_NAME)) { - if (!m_database.performQuery(CREATE_USER_ID_TABLE_SQL_STRING)) { - ACSDK_ERROR(LX("createDatabaseFailed").d("reason", "failed to create userId table")); - close(); - return false; - } - } - - return true; -} - -bool SQLiteLWAAuthorizationStorage::setRefreshToken(const std::string& refreshToken) { - ACSDK_DEBUG5(LX(__func__)); - - std::lock_guard lock(m_mutex); - - if (refreshToken.empty()) { - ACSDK_ERROR(LX("setRefreshTokenFailed").d("reason", "refreshTokenIsEmpty")); - return false; - } - - if (!m_database.clearTable(REFRESH_TOKEN_TABLE_NAME)) { - ACSDK_ERROR(LX("setRefreshTokenFailed").d("reason", "clearTableFailed")); - return false; - } - - std::string sqlString = "INSERT INTO " REFRESH_TOKEN_TABLE_NAME " (" REFRESH_TOKEN_COLUMN_NAME ") VALUES (?);"; - auto statement = m_database.createStatement(sqlString); - - if (!statement) { - ACSDK_ERROR(LX("setRefreshToken").d("reason", "createStatementFailed")); - return false; - } - - if (!statement->bindStringParameter(1, refreshToken)) { - ACSDK_ERROR(LX("setRefreshToken").d("reason", "bindStringParameter")); - return false; - } - - if (!statement->step()) { - ACSDK_ERROR(LX("setRefreshToken").d("reason", "stepFailed")); - return false; - } - - return true; -} - -bool SQLiteLWAAuthorizationStorage::clearRefreshToken() { - ACSDK_DEBUG5(LX(__func__)); - - std::lock_guard lock(m_mutex); - - if (!m_database.clearTable(REFRESH_TOKEN_TABLE_NAME)) { - ACSDK_ERROR(LX("clearRefreshTokenFailed").d("reason", "clearTableFailed")); - return false; - } - - return true; -} - -bool SQLiteLWAAuthorizationStorage::getRefreshToken(std::string* refreshToken) { - ACSDK_DEBUG5(LX(__func__)); - - std::lock_guard lock(m_mutex); - - if (!refreshToken) { - ACSDK_ERROR(LX("getRefreshTokenFailed").d("reason", "nullRefreshToken")); - return false; - } - - std::string sqlString = "SELECT * FROM " REFRESH_TOKEN_TABLE_NAME ";"; - auto statement = m_database.createStatement(sqlString); - - if (!statement) { - ACSDK_ERROR(LX("getRefreshTokenFailed").d("reason", "createStatementFailed")); - return false; - } - - if (!statement->step()) { - ACSDK_ERROR(LX("getRefreshTokenFailed").d("reason", "stepFailed")); - return false; - } - - if (statement->getStepResult() != SQLITE_ROW) { - ACSDK_DEBUG0(LX("getRefreshTokenFailed").d("reason", "stepResultWasNotRow")); - return false; - } - - std::string columnName = statement->getColumnName(0); - if (columnName != REFRESH_TOKEN_COLUMN_NAME) { - ACSDK_ERROR(LX("getRefreshTokenFailed").d("reason", "unexpectedColumnName")); - return false; - } - - auto text = statement->getColumnText(0); - *refreshToken = text; - return true; -} - -bool SQLiteLWAAuthorizationStorage::setUserId(const std::string& userId) { - ACSDK_DEBUG5(LX(__func__).sensitive("userId", userId)); - - std::lock_guard lock(m_mutex); - - clearTableLocked(USER_ID_TABLE_NAME); - - std::string sqlString = "INSERT INTO " USER_ID_TABLE_NAME " (" USER_ID_COLUMN_NAME ") VALUES (?);"; - auto statement = m_database.createStatement(sqlString); - - if (!statement) { - ACSDK_ERROR(LX("setUserIdFailed").d("reason", "createStatementFailed")); - return false; - } - - if (!statement->bindStringParameter(1, userId)) { - ACSDK_ERROR(LX("setUserIdFailed").d("reason", "bindStringParameter")); - return false; - } - - if (!statement->step()) { - ACSDK_ERROR(LX("setUserIdFailed").d("reason", "stepFailed")); - return false; - } - - return true; -} - -bool SQLiteLWAAuthorizationStorage::getUserId(std::string* userId) { - ACSDK_DEBUG5(LX(__func__)); - - std::lock_guard lock(m_mutex); - - if (!userId) { - ACSDK_ERROR(LX("getUserIdFailed").d("reason", "nullRefreshToken")); - return false; - } - - std::string sqlString = "SELECT * FROM " USER_ID_TABLE_NAME ";"; - auto statement = m_database.createStatement(sqlString); - - if (!statement) { - ACSDK_ERROR(LX("getUserIdFailed").d("reason", "createStatementFailed")); - return false; - } - - if (!statement->step()) { - ACSDK_ERROR(LX("getUserIdFailed").d("reason", "stepFailed")); - return false; - } - - if (statement->getStepResult() != SQLITE_ROW) { - ACSDK_DEBUG0(LX("getUserIdFailed").d("reason", "stepResultWasNotRow")); - return false; - } - - std::string columnName = statement->getColumnName(0); - if (columnName != USER_ID_COLUMN_NAME) { - ACSDK_ERROR(LX("getUserIdFailed").d("reason", "unexpectedColumnName").d("columnName", columnName)); - return false; - } - - auto text = statement->getColumnText(0); - *userId = text; - return true; -} - -bool SQLiteLWAAuthorizationStorage::clear() { - ACSDK_DEBUG5(LX(__func__)); - - std::lock_guard lock(m_mutex); - - // Be careful of short circuiting here. - auto success = clearTableLocked(REFRESH_TOKEN_TABLE_NAME); - return clearTableLocked(USER_ID_TABLE_NAME) && success; -} - -bool SQLiteLWAAuthorizationStorage::clearTableLocked(const std::string& tableName) { - ACSDK_DEBUG5(LX(__func__)); - - if (!m_database.clearTable(tableName)) { - ACSDK_ERROR(LX("clearUserIdFailed").d("reason", "clearTableFailed").d("table", tableName)); - return false; - } - - return true; -} - -void SQLiteLWAAuthorizationStorage::close() { - ACSDK_DEBUG5(LX(__func__)); - - std::lock_guard lock(m_mutex); - - m_database.close(); -} - -SQLiteLWAAuthorizationStorage::SQLiteLWAAuthorizationStorage(const std::string& databaseFilePath) : - m_database{databaseFilePath} { -} - -} // namespace lwa -} // namespace acsdkAuthorization -} // namespace alexaClientSDK diff --git a/core/Authorization/acsdkAuthorization/test/AuthorizationManagerTest.cpp b/core/Authorization/acsdkAuthorization/test/AuthorizationManagerTest.cpp new file mode 100644 index 0000000000..3a6ad64ba1 --- /dev/null +++ b/core/Authorization/acsdkAuthorization/test/AuthorizationManagerTest.cpp @@ -0,0 +1,510 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace test { + +using namespace ::testing; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::test; +using namespace avsCommon::sdkInterfaces::storage::test; +using namespace avsCommon::utils; +using namespace acsdkAuthorization; +using namespace acsdkAuthorizationInterfaces; +using namespace registrationManager; + +/// Adapter Related Constants +/// @{ +static const std::string ADAPTER_ID = "test-adapter"; +static const std::string USER_ID = "test-user-id"; +static const std::string AUTH_TOKEN = "supersecureauthotoken"; +static const std::string ADAPTER_ID_2 = ADAPTER_ID + "2"; +static const std::string USER_ID_2 = USER_ID + "2"; +static const std::string AUTH_TOKEN_2 = AUTH_TOKEN + "2"; +/// @} + +/// Storage Constants +/// @{ +static const std::string MISC_TABLE_COMPONENT_NAME = "AuthorizationManager"; +static const std::string MISC_TABLE_TABLE_NAME = "authorizationState"; +static const std::string MISC_TABLE_ADAPTER_ID_KEY = "authAdapterId"; +static const std::string MISC_TABLE_USER_ID_KEY = "userId"; +/// @} + +/// Timeout for test cases that require synchronization. +std::chrono::milliseconds TIMEOUT{2000}; + +/// Class to implement AuthorizationInterface, which is used to retrieve the id. +class StubAuthorization : public acsdkAuthorizationInterfaces::AuthorizationInterface { +public: + /** + * Constructor. + * + * @param id The authorization id that identifies this authorization mechanism. + */ + StubAuthorization(const std::string& id); + + /** + * Returns the authorization id. + * + * @return The authorization id that identifies this authorization mechanism. + */ + std::string getId() override; + +private: + /// The id. + std::string m_id; +}; + +StubAuthorization::StubAuthorization(const std::string& id) : m_id{id} { +} + +std::string StubAuthorization::getId() { + return m_id; +} + +/// Mock class implementing @c RegistrationManagerInterface +class MockRegistrationManager : public registrationManager::RegistrationManagerInterface { +public: + MOCK_METHOD0(logout, void()); +}; + +/// Mock class implementing the adapter. +class MockAuthorizationAdapter + : public acsdkAuthorizationInterfaces::AuthorizationAdapterInterface + , public acsdkAuthorizationInterfaces::AuthorizationInterface { +public: + MockAuthorizationAdapter(std::string id = ADAPTER_ID); + + MOCK_METHOD0(getAuthToken, std::string()); + MOCK_METHOD0(reset, void()); + MOCK_METHOD1(onAuthFailure, void(const std::string&)); + MOCK_METHOD0(getState, avsCommon::sdkInterfaces::AuthObserverInterface::FullState()); + MOCK_METHOD0(getAuthorizationInterface, std::shared_ptr()); + MOCK_METHOD1( + onAuthorizationManagerReady, + avsCommon::sdkInterfaces::AuthObserverInterface::FullState( + const std::shared_ptr& manager)); + + MOCK_METHOD0(getId, std::string()); + +private: + /// The id. + std::string m_id; +}; + +/// Mock adapter. +MockAuthorizationAdapter::MockAuthorizationAdapter(std::string id) : m_id{id} { + ON_CALL(*this, getId()).WillByDefault(Return(m_id)); + ON_CALL(*this, getAuthorizationInterface()).WillByDefault(Return(std::make_shared(m_id))); +} + +class AuthorizationManagerTest : public ::testing::TestWithParam> { +public: + /// SetUp. + void SetUp() override; + + /** + * Checks if the specified key exists in the table. + * + * @param key The key. + * @return A boolean indicating whether the key exists. + */ + bool storageHasKey(const std::string& key); + + /** + * Checks if the specified key-value pair exists in the table. + * + * @param key The key. + * @param value The value. + * @return A boolean indicating whether the key exists. + */ + bool storageHasKeyValue(const std::string& key, const std::string& value); + + /// Used for synchronization. + WaitEvent m_wait; + + /// Mocks and Stubs. + std::shared_ptr m_mockRegMgr; + std::shared_ptr m_storage; + std::shared_ptr m_mockAdapter; + std::shared_ptr m_mockAdapter2; + std::shared_ptr m_mockCDM; + std::shared_ptr m_mockAuthObsv; + + /// The object under test. + std::shared_ptr m_authMgr; +}; + +void AuthorizationManagerTest::SetUp() { + m_mockRegMgr = std::make_shared>(); + ON_CALL(*m_mockRegMgr, logout()).WillByDefault(Invoke([this] { m_authMgr->clearData(); })); + + m_mockAdapter = std::make_shared>(); + m_mockAdapter2 = std::make_shared>(ADAPTER_ID_2); + m_mockCDM = std::make_shared>(); + m_storage = StubMiscStorage::create(); + m_mockAuthObsv = std::make_shared>(); + m_authMgr = AuthorizationManager::create(m_storage, m_mockCDM); + m_authMgr->add(m_mockAdapter); + m_authMgr->addAuthObserver(m_mockAuthObsv); + m_authMgr->setRegistrationManager(m_mockRegMgr); +} + +bool AuthorizationManagerTest::storageHasKey(const std::string& key) { + bool exists = false; + EXPECT_TRUE(m_storage->tableEntryExists(MISC_TABLE_COMPONENT_NAME, MISC_TABLE_TABLE_NAME, key, &exists)); + + return exists; +} + +bool AuthorizationManagerTest::storageHasKeyValue(const std::string& key, const std::string& value) { + std::string actualValue; + EXPECT_TRUE(m_storage->get(MISC_TABLE_COMPONENT_NAME, MISC_TABLE_TABLE_NAME, key, &actualValue)); + + return value == actualValue; +} + +/// Test that create succeeds +TEST_F(AuthorizationManagerTest, test_create_Succeeds) { + ASSERT_THAT(AuthorizationManager::create(m_storage, m_mockCDM), NotNull()); +} + +/// Test that create with null param results in nullptr. +TEST_F(AuthorizationManagerTest, test_createNullParam_Fails) { + ASSERT_THAT(AuthorizationManager::create(nullptr, nullptr), IsNull()); + ASSERT_THAT(AuthorizationManager::create(m_storage, nullptr), IsNull()); + ASSERT_THAT(AuthorizationManager::create(nullptr, m_mockCDM), IsNull()); +} + +/// Test that in the AUTHORIZING state, auth and user id are not persisted. +TEST_F(AuthorizationManagerTest, test_authorizingState_DoesNotPersist) { + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)) + .WillOnce(InvokeWithoutArgs([this]() { m_wait.wakeUp(); })); + + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + EXPECT_FALSE(storageHasKey(MISC_TABLE_ADAPTER_ID_KEY)); + EXPECT_FALSE(storageHasKey(MISC_TABLE_USER_ID_KEY)); +} + +/// Test that in the AUTHORIZING state, the getToken calls no-op. +TEST_F(AuthorizationManagerTest, test_authorizingState_NoToken) { + ON_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)) + .WillByDefault(InvokeWithoutArgs([this]() { m_wait.wakeUp(); })); + EXPECT_CALL(*m_mockAdapter, getAuthToken()).Times(0); + + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + ASSERT_THAT(m_authMgr->getAuthToken(), IsEmpty()); +} + +/// Check that in the REFRESHED state, the token is now valid as well as adapter id and user id are persisted. +TEST_F(AuthorizationManagerTest, test_refreshedState_Persisted) { + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS)) + .WillOnce(InvokeWithoutArgs([this]() { m_wait.wakeUp(); })); + + EXPECT_CALL(*m_mockAdapter, getAuthToken()).WillOnce(Return(AUTH_TOKEN)); + + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + m_authMgr->reportStateChange( + {AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + EXPECT_EQ(AUTH_TOKEN, m_authMgr->getAuthToken()); + + EXPECT_TRUE(storageHasKeyValue(MISC_TABLE_ADAPTER_ID_KEY, ADAPTER_ID)); + EXPECT_TRUE(storageHasKeyValue(MISC_TABLE_USER_ID_KEY, USER_ID)); +} + +/// Check logout behavior if AuthorizationManager is in the UNRECOVERABLE_ERROR state. +TEST_F(AuthorizationManagerTest, test_unrecoverableError_Logout) { + { + InSequence s; + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange( + AuthObserverInterface::State::UNRECOVERABLE_ERROR, AuthObserverInterface::Error::UNKNOWN_ERROR)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::UNINITIALIZED, AuthObserverInterface::Error::SUCCESS)) + .WillOnce(InvokeWithoutArgs([this]() { m_wait.wakeUp(); })); + } + + EXPECT_CALL(*m_mockRegMgr, logout()); + EXPECT_CALL(*m_mockAdapter, reset()); + + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + + m_authMgr->reportStateChange( + {AuthObserverInterface::State::UNRECOVERABLE_ERROR, AuthObserverInterface::Error::UNKNOWN_ERROR}, + ADAPTER_ID, + USER_ID); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + EXPECT_FALSE(storageHasKey(MISC_TABLE_ADAPTER_ID_KEY)); + EXPECT_FALSE(storageHasKey(MISC_TABLE_USER_ID_KEY)); +} + +/// Check invalid transitions. +TEST_F(AuthorizationManagerTest, test_invalidStateTransition_NoNotification) { + m_authMgr->reportStateChange( + {AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + m_authMgr->reportStateChange( + {AuthObserverInterface::State::EXPIRED, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + // Call this to ensure any async requests have been processed. + m_authMgr->doShutdown(); + + EXPECT_CALL(*m_mockAuthObsv, onAuthStateChange(_, _)).Times(0); +} + +/// Check that getState returns the correct thing. +TEST_F(AuthorizationManagerTest, test_getState) { + auto waitAndReset = [this]() { + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + m_wait.reset(); + }; + + EXPECT_CALL(*m_mockAuthObsv, onAuthStateChange(_, _)).WillRepeatedly(InvokeWithoutArgs([this]() { + m_wait.wakeUp(); + })); + ; + + std::vector states{ + AuthObserverInterface::State::AUTHORIZING, + AuthObserverInterface::State::REFRESHED, + AuthObserverInterface::State::EXPIRED, + // We don't check for UNRECOVERABLE_ERROR because it leads back to UNINITIALIZED + }; + + EXPECT_EQ(AuthObserverInterface::State::UNINITIALIZED, m_authMgr->getState()); + + for (const auto& state : states) { + m_authMgr->reportStateChange({state, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + waitAndReset(); + EXPECT_EQ(state, m_authMgr->getState()); + } +} + +/// Check that the active authorization returns the correct id. +TEST_F(AuthorizationManagerTest, test_activeAuthorization_Success) { + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS)) + .WillOnce(InvokeWithoutArgs([this]() { m_wait.wakeUp(); })); + + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + m_authMgr->reportStateChange( + {AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + EXPECT_EQ(ADAPTER_ID, m_authMgr->getActiveAuthorization()); +} + +/// Check that logout succeeds. +TEST_F(AuthorizationManagerTest, test_logout) { + EXPECT_CALL(*m_mockRegMgr, logout()).WillOnce(InvokeWithoutArgs([this]() { + m_authMgr->clearData(); + m_wait.wakeUp(); + })); + + m_authMgr->logout(); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); +} + +/// Parameterized Tests to test combinations of mismatched adapter and user ids. +INSTANTIATE_TEST_CASE_P( + Parameterized, + AuthorizationManagerTest, + ::testing::Values( + std::pair(ADAPTER_ID, USER_ID_2), + std::pair(ADAPTER_ID_2, USER_ID), + std::pair(ADAPTER_ID_2, USER_ID_2))); + +/// Check the implict logout behavior if AuthorizationManager is in the REFRESHED state. +TEST_P(AuthorizationManagerTest, test_mismatchingIdRefreshed_AuthorizingRequest_ImplictLogout) { + m_authMgr->add(m_mockAdapter2); + + std::string newAdapterId, newUserId; + std::tie(newAdapterId, newUserId) = GetParam(); + + { + InSequence s; + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::UNINITIALIZED, AuthObserverInterface::Error::SUCCESS)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)) + .WillOnce(InvokeWithoutArgs([this]() { m_wait.wakeUp(); })); + } + + EXPECT_CALL(*m_mockRegMgr, logout()); + EXPECT_CALL(*m_mockAdapter, reset()); + EXPECT_CALL(*m_mockAdapter2, reset()).Times(0); + + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + m_authMgr->reportStateChange( + {AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, newAdapterId, newUserId); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + EXPECT_FALSE(storageHasKey(MISC_TABLE_ADAPTER_ID_KEY)); + EXPECT_FALSE(storageHasKey(MISC_TABLE_USER_ID_KEY)); +} + +/** + * The new adapter will be allow to continue authorizing while we reset the current adapter + * and logout the rest of the device. + */ +TEST_P(AuthorizationManagerTest, test_mismatchingIdAuthorizing_AuthorizingRequest_ImplictLogout) { + std::string newAdapterId, newUserId; + std::tie(newAdapterId, newUserId) = GetParam(); + + { + InSequence s; + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::UNINITIALIZED, AuthObserverInterface::Error::SUCCESS)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)) + .WillOnce(InvokeWithoutArgs([this]() { m_wait.wakeUp(); })); + } + + EXPECT_CALL(*m_mockRegMgr, logout()); + EXPECT_CALL(*m_mockAdapter, reset()); + EXPECT_CALL(*m_mockAdapter2, reset()).Times(0); + + m_authMgr->add(m_mockAdapter2); + + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, newAdapterId, newUserId); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + EXPECT_FALSE(storageHasKey(MISC_TABLE_ADAPTER_ID_KEY)); + EXPECT_FALSE(storageHasKey(MISC_TABLE_USER_ID_KEY)); +} + +/** + * Another adapter has reported it is in REFRESHED state, meaning we have multiple authorizations. + * To protect customer data, force a logout, and reset all authorizations. + */ +TEST_P(AuthorizationManagerTest, test_mismatchingId_RefreshingRequest_Logout) { + std::string newAdapterId, newUserId; + std::tie(newAdapterId, newUserId) = GetParam(); + + { + InSequence s; + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS)); + + EXPECT_CALL( + *m_mockAuthObsv, + onAuthStateChange(AuthObserverInterface::State::UNINITIALIZED, AuthObserverInterface::Error::SUCCESS)) + .WillOnce(InvokeWithoutArgs([this]() { m_wait.wakeUp(); })); + } + + EXPECT_CALL(*m_mockRegMgr, logout()); + EXPECT_CALL(*m_mockAdapter, reset()).Times(AtLeast(1)); + // Only expect this adapter to be reset if it's reported REFRESHED. + if (ADAPTER_ID_2 == newAdapterId) { + EXPECT_CALL(*m_mockAdapter2, reset()); + } + + m_authMgr->add(m_mockAdapter2); + + m_authMgr->reportStateChange( + {AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS}, ADAPTER_ID, USER_ID); + m_authMgr->reportStateChange( + {AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS}, newAdapterId, newUserId); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + EXPECT_FALSE(storageHasKey(MISC_TABLE_ADAPTER_ID_KEY)); + EXPECT_FALSE(storageHasKey(MISC_TABLE_USER_ID_KEY)); +} + +} // namespace test +} // namespace acsdkAuthorization +} // namespace alexaClientSDK diff --git a/core/Authorization/acsdkAuthorization/test/CMakeLists.txt b/core/Authorization/acsdkAuthorization/test/CMakeLists.txt new file mode 100644 index 0000000000..66480b3bdc --- /dev/null +++ b/core/Authorization/acsdkAuthorization/test/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +if(BUILD_TESTING) + add_library(acsdkAuthorizationTestUtils + StubStorage.cpp + ) + target_include_directories(acsdkAuthorizationTestUtils PUBLIC + "${acsdkAuthorization_SOURCE_DIR}/test/include" + + ) + target_link_libraries(acsdkAuthorizationTestUtils + acsdkAuthorization + acsdkAuthorizationInterfaces) +endif() + +set(UNITTEST_LIBS + acsdkAuthorization + acsdkAuthorizationInterfaces + acsdkAuthorizationTestUtils + acsdkCryptoInterfaces + acsdkPropertiesInterfacesTestLib + AttachmentCommonTestLib + RegistrationManagerTestUtils + SDKInterfacesTests + UtilsCommonTestLib) + +set(UNITTEST_INCLUDES "${acsdkAuthorization_SOURCE_DIR}/privateInclude") + +discover_unit_tests("${UNITTEST_INCLUDES}" "${UNITTEST_LIBS}") diff --git a/core/Authorization/acsdkAuthorization/test/LWAAuthStorageMigrationTest.cpp b/core/Authorization/acsdkAuthorization/test/LWAAuthStorageMigrationTest.cpp new file mode 100644 index 0000000000..f4126df22d --- /dev/null +++ b/core/Authorization/acsdkAuthorization/test/LWAAuthStorageMigrationTest.cpp @@ -0,0 +1,196 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace lwa { +namespace test { + +using namespace ::alexaClientSDK::acsdkProperties; +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces::test; +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage; +using namespace ::alexaClientSDK::avsCommon::utils::configuration; +using namespace ::alexaClientSDK::storage::sqliteStorage; + +/// Logging tag. +static const std::string TAG = "LWAAuthStorageMigrationTest"; + +/// Component name for the misc DB tables +static const std::string COMPONENT_NAME = "config"; + +/// Table name for the misc DB tables +static const std::string TABLE_NAME = "LWAAuthorizationStorage"; + +/// Full table name. +static const std::string PROPERTIES_URI = COMPONENT_NAME + "/" + TABLE_NAME; + +/// Full table name for legacy refresh token. +static const std::string TABLE_NAME_REFRESH_TOKEN = "refreshToken"; + +/// Full table name for legacy user id. +static const std::string TABLE_NAME_USER_ID = "userId"; + +/** + * Test harness for @c LWAAuthorizationStorage class. + */ +class LWAAuthStorageMigrationTest : public ::testing::Test { +public: + /** + * Set up the test harness for running a test. + */ + void SetUp() override; + + /** + * Clean up the test harness after running a test. + */ + void TearDown() override; + +protected: + void createUserIdTable(); + void createRefreshTokenTable(); + + /// The Misc DB storage instance + std::shared_ptr m_miscStorage; + + /// Properties factory instance + std::shared_ptr m_propertiesFactory; +}; + +void LWAAuthStorageMigrationTest::SetUp() { + ACSDK_INFO(LX("SetUp")); + m_miscStorage = SQLiteMiscStorage::create("LWAAuthorizationStorageMigrationTest.db"); + ASSERT_NE(nullptr, m_miscStorage); + ASSERT_TRUE(m_miscStorage->open() || m_miscStorage->createDatabase()); + m_propertiesFactory = StubPropertiesFactory::create(); + ASSERT_NE(nullptr, m_propertiesFactory); + if (m_miscStorage->getDatabase().tableExists(TABLE_NAME_REFRESH_TOKEN)) { + ASSERT_TRUE(m_miscStorage->getDatabase().clearTable(TABLE_NAME_REFRESH_TOKEN)); + ASSERT_TRUE(m_miscStorage->getDatabase().dropTable(TABLE_NAME_REFRESH_TOKEN)); + } + if (m_miscStorage->getDatabase().tableExists(TABLE_NAME_USER_ID)) { + ASSERT_TRUE(m_miscStorage->getDatabase().clearTable(TABLE_NAME_USER_ID)); + ASSERT_TRUE(m_miscStorage->getDatabase().dropTable(TABLE_NAME_USER_ID)); + } + bool propsTableExists; + ASSERT_TRUE(m_miscStorage->tableExists(COMPONENT_NAME, TABLE_NAME, &propsTableExists)); + if (propsTableExists) { + ASSERT_TRUE(m_miscStorage->clearTable(COMPONENT_NAME, TABLE_NAME)); + ASSERT_TRUE(m_miscStorage->deleteTable(COMPONENT_NAME, TABLE_NAME)); + } +} + +void LWAAuthStorageMigrationTest::TearDown() { + ACSDK_INFO(LX("TearDown")); + m_miscStorage.reset(); + m_propertiesFactory.reset(); +} + +void LWAAuthStorageMigrationTest::createRefreshTokenTable() { + ASSERT_TRUE(m_miscStorage->getDatabase().performQuery("DROP TABLE IF EXISTS refreshToken")); + ASSERT_TRUE(m_miscStorage->getDatabase().performQuery("CREATE TABLE refreshToken (refreshToken TEXT)")); + ASSERT_TRUE(m_miscStorage->getDatabase().tableExists(TABLE_NAME_REFRESH_TOKEN)); + ASSERT_TRUE(m_miscStorage->getDatabase().performQuery("INSERT INTO refreshToken VALUES('refreshTokenValue')")); +} + +void LWAAuthStorageMigrationTest::createUserIdTable() { + ASSERT_TRUE(m_miscStorage->getDatabase().performQuery("DROP TABLE IF EXISTS userId")); + ASSERT_TRUE(m_miscStorage->getDatabase().performQuery("CREATE TABLE userId (userId TEXT)")); + ASSERT_TRUE(m_miscStorage->getDatabase().tableExists(TABLE_NAME_USER_ID)); + ASSERT_TRUE(m_miscStorage->getDatabase().performQuery("INSERT INTO userId VALUES('userIdValue')")); +} + +/// Tests with creating a string key - string value table +TEST_F(LWAAuthStorageMigrationTest, test_migrateEmptyDatabase) { + bool tableExists = false; + ASSERT_TRUE(m_miscStorage->tableExists(COMPONENT_NAME, TABLE_NAME, &tableExists)); + ASSERT_FALSE(tableExists); + + LWAStorageDataMigration{m_miscStorage, m_propertiesFactory}.upgradeStorage(); + + ASSERT_TRUE(m_miscStorage->tableExists(COMPONENT_NAME, TABLE_NAME, &tableExists)); + ASSERT_FALSE(tableExists); +} + +TEST_F(LWAAuthStorageMigrationTest, test_migrateRefreshToken) { + createRefreshTokenTable(); + LWAStorageDataMigration{m_miscStorage, m_propertiesFactory}.upgradeStorage(); + + auto properties = m_propertiesFactory->getProperties(PROPERTIES_URI); + ASSERT_NE(nullptr, properties); + + std::string refreshToken; + ASSERT_TRUE(properties->getString(REFRESH_TOKEN_PROPERTY_NAME, refreshToken)); + ASSERT_EQ("refreshTokenValue", refreshToken); +} + +TEST_F(LWAAuthStorageMigrationTest, test_migrateUserId) { + createUserIdTable(); + + LWAStorageDataMigration{m_miscStorage, m_propertiesFactory}.upgradeStorage(); + + ASSERT_FALSE(m_miscStorage->getDatabase().tableExists(TABLE_NAME_USER_ID)); + + auto properties = m_propertiesFactory->getProperties(PROPERTIES_URI); + ASSERT_NE(nullptr, properties); + + std::string userId; + ASSERT_TRUE(properties->getString(USER_ID_PROPERTY_NAME, userId)); + ASSERT_EQ("userIdValue", userId); +} + +TEST_F(LWAAuthStorageMigrationTest, test_verifyMigrationForSameDatabase) { + createRefreshTokenTable(); + createUserIdTable(); + + auto propertiesFactory = createPropertiesFactory(m_miscStorage); + + bool propsTableExists; + ASSERT_TRUE(m_miscStorage->tableExists(COMPONENT_NAME, TABLE_NAME, &propsTableExists)); + ASSERT_FALSE(propsTableExists); + + LWAStorageDataMigration{m_miscStorage, propertiesFactory}.upgradeStorage(); + + ASSERT_FALSE(m_miscStorage->getDatabase().tableExists(TABLE_NAME_USER_ID)); + ASSERT_FALSE(m_miscStorage->getDatabase().tableExists(TABLE_NAME_REFRESH_TOKEN)); + ASSERT_TRUE(m_miscStorage->tableExists(COMPONENT_NAME, TABLE_NAME, &propsTableExists)); + ASSERT_TRUE(propsTableExists); + + auto properties = propertiesFactory->getProperties(PROPERTIES_URI); + ASSERT_NE(nullptr, properties); + + std::string refreshToken; + ASSERT_TRUE(properties->getString(REFRESH_TOKEN_PROPERTY_NAME, refreshToken)); + ASSERT_EQ("refreshTokenValue", refreshToken); + + std::string userId; + ASSERT_TRUE(properties->getString(USER_ID_PROPERTY_NAME, userId)); + ASSERT_EQ("userIdValue", userId); +} + +} // namespace test +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK diff --git a/core/Authorization/acsdkAuthorization/test/LWAAuthorizationAdapterTest.cpp b/core/Authorization/acsdkAuthorization/test/LWAAuthorizationAdapterTest.cpp new file mode 100644 index 0000000000..1ec622261a --- /dev/null +++ b/core/Authorization/acsdkAuthorization/test/LWAAuthorizationAdapterTest.cpp @@ -0,0 +1,847 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace lwa { +namespace test { + +using namespace ::testing; +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils; +using namespace avsCommon::utils::configuration; +using namespace avsCommon::utils::libcurlUtils; +using namespace avsCommon::utils::http; +using namespace avsCommon::utils::libcurlUtils::test; +using namespace acsdkAuthorizationInterfaces; +using namespace acsdkAuthorizationInterfaces::lwa; + +/// An example user id that would be returned by the Customer Profile API. +static const std::string USER_ID = "test-user-id"; + +/// An example name that would be returned by the Customer Profile API. +static const std::string NAME = "Test User"; + +/// An example email that would be returned by the Customer Profile API. +static const std::string EMAIL = "test@user.com"; + +/// The Veritifcation URI. +static const std::string VERIFICATION_URI = "https://amazon.com/us/code"; + +/// An example CBL code to be returned from CBL auth. +static const std::string USER_CODE = "ABCDE"; + +/// An example device code to be returned from CBL auth. +static const std::string DEVICE_CODE = "deviceCode"; + +/// A test adapter id. +static const std::string ADAPTER_ID = "test-adapter-id"; + +/// Default adapter id. +const std::string DEFAULT_ADAPTER_ID = "lwa-adapter"; + +/// Timeout for test cases that require synchronization. +std::chrono::seconds TIMEOUT{2}; + +/// Long timeout for cases involving waiting for retries. +std::chrono::seconds LONG_TIMEOUT{20}; + +/// The config node for the LWAAuthorizationAdapter. +static const std::string CONFIG_ROOT_NODE = "lwaAuthorization"; + +/// The method name for authorizeUsingCBL. +static const std::string AUTHORIZE_USING_CBL = "authorizeUsingCBL"; + +/// The method name for authorizeUsingCBLWithCustomerProfile. +static const std::string AUTHORIZE_USING_CBL_WITH_CUSTOMER_PROFILE = "authorizeUsingCBLWithCustomerProfile"; + +/// Example responses from LWA. +const std::string EXPIRATION_S = "3600"; +const std::string INTERVAL_S = "3600"; +const std::string ACCESS_TOKEN = "myaccesstoken"; +const std::string REFRESH_TOKEN = "myrefreshtoken"; +const std::string TOKEN_TYPE = "bearer"; +const std::string EXPIRATION = "3600"; + +/// The Config JSON. +// clang-format off +static const std::string CONFIG_JSON = R"( +{ + "deviceInfo" : { + "clientId":"MyClientId", + "productId":"MyProductId", + "deviceSerialNumber":"0", + "manufacturerName":"MyCompany", + "description":"MyCommpany" + }, + ")" + CONFIG_ROOT_NODE + R"(" : {} +} +)"; + +/// A default successful code pair response. +static const HTTPResponse CODE_PAIR_RESPONSE{ + HTTPResponseCode::SUCCESS_OK, + R"( + { + "user_code": ")" + USER_CODE + R"(", + "device_code": ")" + DEVICE_CODE + R"(", + "verification_uri": ")" + VERIFICATION_URI + + R"(", + "expires_in": )" + EXPIRATION_S + R"(, + "interval": )" + INTERVAL_S + R"( + })"}; + +/// A default successful token exchange response. +static const HTTPResponse TOKEN_EXCHANGE_RESPONSE{ + HTTPResponseCode::SUCCESS_OK, + R"( + { + "access_token": ")" + ACCESS_TOKEN + R"(", + "refresh_token": ")" + REFRESH_TOKEN + R"(", + "token_type": ")" + TOKEN_TYPE + R"(", + "expires_in": )" + EXPIRATION + R"( + })"}; + +/// A defauilt successful customer profile response containing only the user id. +static const HTTPResponse CUSTOMER_PROFILE_SHORT_RESPONSE{ + HTTPResponseCode::SUCCESS_OK, + R"( + { + "user_id": ")" + USER_ID + R"(" + })"}; + +/// A defauilt successful customer profile response. +static const HTTPResponse CUSTOMER_PROFILE_RESPONSE{ + HTTPResponseCode::SUCCESS_OK, + R"( + { + "user_id": ")" + USER_ID + R"(", + "name": ")" + NAME + R"(", + "email": ")" + EMAIL + R"(" + })"}; +// clang-format on + +/// A mock observer. +class MockCBLObserver : public acsdkAuthorizationInterfaces::lwa::CBLAuthorizationObserverInterface { +public: + MOCK_METHOD2(onRequestAuthorization, void(const std::string& url, const std::string& code)); + MOCK_METHOD0(onCheckingForAuthorization, void()); + MOCK_METHOD1(onCustomerProfileAvailable, void(const CustomerProfile& customerProfile)); +}; + +/// A mock AuthorizationManager. +class MockAuthManager : public acsdkAuthorizationInterfaces::AuthorizationManagerInterface { +public: + MOCK_METHOD3( + reportStateChange, + void( + const avsCommon::sdkInterfaces::AuthObserverInterface::FullState& state, + const std::string& authId, + const std::string& userId)); + MOCK_METHOD1( + add, + void(const std::shared_ptr& adapter)); +}; + +/** + * This class tests the internals of the LWAAuthorizationAdapter class. + * Due to the complicated nature of the the APIs, whitebox testing is done. + */ +class LWAAuthorizationAdapterTest : public ::testing::TestWithParam { +public: + /// SetUp. + void SetUp() override; + + static const HTTPResponse NULL_HTTP_RESPONSE; + + /** + * Set expectations against expected responses from LWA using CBL using the following default responses: + * + * CODE_PAIR_RESPONSE + * TOKEN_EXCHANGE_RESPONSE + * + * @param customerProfileResponse The customerProfileResponse to use. + */ + void setCBLExpectations(HTTPResponse customerProfileResponse); + + /** + * Set expectations against expected responses from LWA using CBL with the provided responses. + * Passing in a value equal to NULL_HTTP_RESPONSE will cause the expectation to be omitted. + * + * @param codePairResponse Response from a code pair operation. + * @param tokenExchangeResponse Response from a token exchange operation. + * @param customerProfileResponse Response form a customer profile operation. + */ + void setCBLExpectations( + HTTPResponse codePairResponse, + HTTPResponse tokenExchangeResponse, + HTTPResponse customerProfileResponse); + + /** + * Initialize the configuration node object. + * + * @return An initialized configuration node. + */ + std::shared_ptr createConfig(); + + /// Mock Objects + /// @{ + MockHttpPost* m_httpPost; + MockHttpGet* m_httpGet; + std::shared_ptr m_configuration; + std::shared_ptr m_deviceInfo; + std::shared_ptr m_storage; + std::shared_ptr m_cblObserver; + std::shared_ptr m_manager; + /// @} + + /// Create an instance to retrieve constants. + std::unique_ptr m_lwaConfig; + + /// Object under test. + std::shared_ptr m_lwa; + + /// Used to synchronize in multi-thread test cases. + WaitEvent m_wait; +}; + +const HTTPResponse LWAAuthorizationAdapterTest::NULL_HTTP_RESPONSE{-1, ""}; + +void LWAAuthorizationAdapterTest::SetUp() { + auto httpPost = std::unique_ptr(new NiceMock()); + auto httpGet = std::unique_ptr(new NiceMock()); + + // Keep a pointer since we still need to set expectations. + // This is safe since LWAAuthorizationAdapter keeps the reference alive until destruction. + m_httpPost = httpPost.get(); + m_httpGet = httpGet.get(); + + m_configuration = createConfig(); + m_deviceInfo = DeviceInfo::createFromConfiguration(m_configuration); + m_storage = std::make_shared(); + m_cblObserver = std::make_shared>(); + m_manager = std::make_shared>(); + m_lwaConfig = LWAAuthorizationConfiguration::create(*m_configuration, m_deviceInfo, CONFIG_ROOT_NODE); + + m_lwa = LWAAuthorizationAdapter::create( + m_configuration, std::move(httpPost), m_deviceInfo, m_storage, std::move(httpGet)); +} + +std::shared_ptr LWAAuthorizationAdapterTest::createConfig() { + auto json = std::shared_ptr(new std::stringstream()); + *json << CONFIG_JSON; + std::vector> jsonStream; + jsonStream.push_back(json); + ConfigurationNode::initialize(jsonStream); + + return ConfigurationNode::createRoot(); +} + +void LWAAuthorizationAdapterTest::setCBLExpectations( + HTTPResponse codePairResponse, + HTTPResponse tokenExchangeResponse, + HTTPResponse customerProfileResponse) { + if (NULL_HTTP_RESPONSE.code != codePairResponse.code) { + EXPECT_CALL( + *m_httpPost, + doPost( + m_lwaConfig->getRequestCodePairUrl(), + _, + A>&>(), + _)) + .Times(AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([codePairResponse] { return codePairResponse; })); + } + + if (NULL_HTTP_RESPONSE.code != tokenExchangeResponse.code) { + EXPECT_CALL( + *m_httpPost, + doPost( + m_lwaConfig->getRequestTokenUrl(), _, A>&>(), _)) + .Times(AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([tokenExchangeResponse] { return tokenExchangeResponse; })); + } + + if (NULL_HTTP_RESPONSE.code != customerProfileResponse.code) { + EXPECT_CALL(*m_httpGet, doGet(HasSubstr("access_token=" + ACCESS_TOKEN), _)) + .Times(AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([customerProfileResponse] { return customerProfileResponse; })); + } +} + +void LWAAuthorizationAdapterTest::setCBLExpectations(HTTPResponse customerProfileResponse) { + setCBLExpectations(CODE_PAIR_RESPONSE, TOKEN_EXCHANGE_RESPONSE, customerProfileResponse); +} + +/// Test create with null ptr +TEST_F(LWAAuthorizationAdapterTest, test_create_NullParams_Nullptr) { + auto httpPost = std::unique_ptr(new NiceMock()); + auto httpGet = std::unique_ptr(new NiceMock()); + + EXPECT_THAT( + LWAAuthorizationAdapter::create(nullptr, std::move(httpPost), m_deviceInfo, m_storage, std::move(httpGet)), + IsNull()); + + httpPost = std::unique_ptr(new NiceMock()); + httpGet = std::unique_ptr(new NiceMock()); + + EXPECT_THAT( + LWAAuthorizationAdapter::create(m_configuration, nullptr, m_deviceInfo, m_storage, std::move(httpGet)), + IsNull()); + + httpPost = std::unique_ptr(new NiceMock()); + httpGet = std::unique_ptr(new NiceMock()); + + EXPECT_THAT( + LWAAuthorizationAdapter::create(m_configuration, std::move(httpPost), nullptr, m_storage, std::move(httpGet)), + IsNull()); + + httpPost = std::unique_ptr(new NiceMock()); + httpGet = std::unique_ptr(new NiceMock()); + + EXPECT_THAT( + LWAAuthorizationAdapter::create( + m_configuration, std::move(httpPost), m_deviceInfo, nullptr, std::move(httpGet)), + IsNull()); +} + +/// Check the default value for the adapter id both directly and via getAuthorizationInterface. +TEST_F(LWAAuthorizationAdapterTest, test_id_DefaultValue) { + EXPECT_EQ(DEFAULT_ADAPTER_ID, m_lwa->getId()); + EXPECT_EQ(DEFAULT_ADAPTER_ID, m_lwa->getAuthorizationInterface()->getId()); +} + +/// Check that no token is returned when not authorized. +TEST_F(LWAAuthorizationAdapterTest, test_getAuthTokenNoAuth_Fails) { + EXPECT_THAT(m_lwa->getAuthToken(), IsEmpty()); +} + +/// Check the custom value for the adapter id both directly and via getAuthorizationInterface. +TEST_F(LWAAuthorizationAdapterTest, test_id_CustomValue) { + auto httpPost = std::unique_ptr>(new NiceMock()); + auto httpGet = std::unique_ptr>(new NiceMock()); + + const std::string NEW_ID = "new-id"; + + auto lwa = LWAAuthorizationAdapter::create( + m_configuration, std::move(httpPost), m_deviceInfo, m_storage, std::move(httpGet), NEW_ID); + + EXPECT_EQ(NEW_ID, lwa->getId()); + EXPECT_EQ(NEW_ID, lwa->getAuthorizationInterface()->getId()); +} + +/// Check that authorizing without an AuthManager fails. +TEST_F(LWAAuthorizationAdapterTest, test_authorize_NoAuthMgr_Fails) { + EXPECT_FALSE(m_lwa->authorizeUsingCBL(m_cblObserver)); + EXPECT_FALSE(m_lwa->authorizeUsingCBLWithCustomerProfile(m_cblObserver)); +} + +/// Check that multiple authorization requests fail. Only one reportStateChange call for each state is expected. +TEST_F(LWAAuthorizationAdapterTest, test_multipleCBLAuthorization_Fails) { + setCBLExpectations(CUSTOMER_PROFILE_RESPONSE); + + { + InSequence s; + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")); + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + USER_ID)) + .WillOnce(InvokeWithoutArgs([this] { m_wait.wakeUp(); })); + } + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(m_lwa->authorizeUsingCBLWithCustomerProfile(m_cblObserver)); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + EXPECT_FALSE(m_lwa->authorizeUsingCBL(m_cblObserver)); + EXPECT_FALSE(m_lwa->authorizeUsingCBLWithCustomerProfile(m_cblObserver)); +} + +/// Check that the retry logic for CBL code pair requests is successful. +TEST_F(LWAAuthorizationAdapterTest, test_cblCodePairRetry_Succeeds) { + setCBLExpectations(NULL_HTTP_RESPONSE, TOKEN_EXCHANGE_RESPONSE, CUSTOMER_PROFILE_SHORT_RESPONSE); + + EXPECT_CALL( + *m_httpPost, + doPost( + m_lwaConfig->getRequestCodePairUrl(), _, A>&>(), _)) + .Times(AnyNumber()) + .WillOnce(InvokeWithoutArgs([] { + auto response = HTTPResponse(); + response.code = HTTPResponseCode::SERVER_ERROR_INTERNAL; + return response; + })) + .WillRepeatedly(InvokeWithoutArgs([this] { + m_wait.wakeUp(); + return CODE_PAIR_RESPONSE; + })); + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(m_lwa->authorizeUsingCBL(m_cblObserver)); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); +} + +/// Check that the retry logic for CBL token exchange requests is successful. +TEST_F(LWAAuthorizationAdapterTest, test_cblTokenExchangeRetry_Succeeds) { + setCBLExpectations(CODE_PAIR_RESPONSE, NULL_HTTP_RESPONSE, CUSTOMER_PROFILE_SHORT_RESPONSE); + + EXPECT_CALL( + *m_httpPost, + doPost(m_lwaConfig->getRequestTokenUrl(), _, A>&>(), _)) + .Times(AnyNumber()) + .WillOnce(InvokeWithoutArgs([] { return HTTPResponse(HTTPResponseCode::SERVER_ERROR_INTERNAL, ""); })) + .WillRepeatedly(InvokeWithoutArgs([this] { + m_wait.wakeUp(); + return TOKEN_EXCHANGE_RESPONSE; + })); + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(m_lwa->authorizeUsingCBL(m_cblObserver)); + EXPECT_TRUE(m_wait.wait(LONG_TIMEOUT)); +} + +/// Check that on auth failure, a retry is triggered. +TEST_F(LWAAuthorizationAdapterTest, test_authFailure_TriggersRetry) { + WaitEvent tokenExchangeRequestWait, onAuthFailureProcessedWait; + { + InSequence is; + + EXPECT_CALL( + *m_httpPost, + doPost( + m_lwaConfig->getRequestCodePairUrl(), + _, + A>&>(), + _)) + .Times(AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([] { return CODE_PAIR_RESPONSE; })); + + EXPECT_CALL( + *m_httpPost, + doPost( + m_lwaConfig->getRequestTokenUrl(), _, A>&>(), _)) + .Times(AnyNumber()) + .WillOnce(InvokeWithoutArgs([&tokenExchangeRequestWait] { + tokenExchangeRequestWait.wakeUp(); + return TOKEN_EXCHANGE_RESPONSE; + })) + // This second expectation is in response to an onAuthFailure() call. + .WillRepeatedly(InvokeWithoutArgs([&onAuthFailureProcessedWait] { + onAuthFailureProcessedWait.wakeUp(); + return TOKEN_EXCHANGE_RESPONSE; + })); + } + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(m_lwa->authorizeUsingCBL(m_cblObserver)); + EXPECT_TRUE(tokenExchangeRequestWait.wait(TIMEOUT)); + m_lwa->onAuthFailure(ACCESS_TOKEN); + EXPECT_TRUE(onAuthFailureProcessedWait.wait(TIMEOUT)); +} + +/// Check that getState returns the correct state. +TEST_F(LWAAuthorizationAdapterTest, test_getState_Succeeds) { + setCBLExpectations(CUSTOMER_PROFILE_SHORT_RESPONSE); + + { + InSequence s; + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")); + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + USER_ID)) + .WillRepeatedly(InvokeWithoutArgs([this] { m_wait.wakeUp(); })); + + EXPECT_THAT( + m_lwa->getState(), + AuthObserverInterface::FullState( + AuthObserverInterface::State::UNINITIALIZED, AuthObserverInterface::Error::SUCCESS)); + } + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(m_lwa->authorizeUsingCBL(m_cblObserver)); + + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + EXPECT_THAT( + m_lwa->getState(), + AuthObserverInterface::FullState( + AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS)); +} + +/// Check that reset operations are successful. +TEST_F(LWAAuthorizationAdapterTest, test_reset_Succeeds) { + setCBLExpectations(CUSTOMER_PROFILE_SHORT_RESPONSE); + + { + InSequence s; + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")); + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + USER_ID)) + .WillOnce(InvokeWithoutArgs([this] { m_wait.wakeUp(); })); + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::UNINITIALIZED, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")) + .WillOnce(InvokeWithoutArgs([this] { m_wait.wakeUp(); })); + } + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(m_lwa->authorizeUsingCBL(m_cblObserver)); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + m_wait.reset(); + + m_lwa->reset(); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + + std::string temp; + EXPECT_FALSE(m_storage->getRefreshToken(&temp)); + EXPECT_FALSE(m_storage->getUserId(&temp)); +} + +/// Check that the LWA token refreshing logic is successful. +TEST_F(LWAAuthorizationAdapterTest, test_refreshing_succeeds) { + setCBLExpectations(CODE_PAIR_RESPONSE, LWAAuthorizationAdapterTest::NULL_HTTP_RESPONSE, CUSTOMER_PROFILE_RESPONSE); + + EXPECT_CALL( + *m_httpPost, + doPost(m_lwaConfig->getRequestTokenUrl(), _, A>&>(), _)) + .Times(AtLeast(2)) + .WillOnce(InvokeWithoutArgs([] { + // Create a response that will expire immediately to force a refresh. + auto response = HTTPResponse(); + response.code = HTTPResponseCode::SUCCESS_OK; + // clang-format off + response.body = R"( + { + "access_token": ")" + + ACCESS_TOKEN + R"(", + "refresh_token": ")" + + REFRESH_TOKEN + R"(", + "token_type": ")" + + TOKEN_TYPE + R"(", + "expires_in": )" + + "1" + R"( + })"; + // clang-format on + return response; + })) + .WillRepeatedly(InvokeWithoutArgs([] { return TOKEN_EXCHANGE_RESPONSE; })); + + { + InSequence s; + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")); + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + USER_ID)) + .Times(AtLeast(1)) + .WillRepeatedly(InvokeWithoutArgs([this] { m_wait.wakeUp(); })); + } + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(m_lwa->authorizeUsingCBL(m_cblObserver)); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); +} + +/// Test resetting while waiting for code pair requests. +TEST_F(LWAAuthorizationAdapterTest, test_reset_CodePair) { + EXPECT_CALL( + *m_httpPost, + doPost( + m_lwaConfig->getRequestCodePairUrl(), _, A>&>(), _)) + .Times(AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([this] { + auto response = HTTPResponse(); + response.code = HTTPResponseCode::SERVER_UNAVAILABLE; + + m_wait.wakeUp(); + + return response; + })); + + { + InSequence s; + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")); + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::UNINITIALIZED, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")) + .Times(AtLeast(1)) + .WillRepeatedly(InvokeWithoutArgs([this] { m_wait.wakeUp(); })); + } + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(m_lwa->authorizeUsingCBL(m_cblObserver)); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + m_wait.reset(); + m_lwa->reset(); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); +} + +/// Test resetting while waiting for token exchange requests. +TEST_F(LWAAuthorizationAdapterTest, test_reset_TokenExchange) { + EXPECT_CALL( + *m_httpPost, + doPost( + m_lwaConfig->getRequestCodePairUrl(), _, A>&>(), _)) + .Times(AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([this] { + m_wait.wakeUp(); + + return CODE_PAIR_RESPONSE; + })); + + EXPECT_CALL( + *m_httpPost, + doPost(m_lwaConfig->getRequestTokenUrl(), _, A>&>(), _)) + .Times(AnyNumber()) + .WillOnce(InvokeWithoutArgs([] { + auto response = HTTPResponse(); + response.code = HTTPResponseCode::SERVER_UNAVAILABLE; + + return response; + })); + + { + InSequence s; + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")); + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::UNINITIALIZED, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")) + .Times(AtLeast(1)) + .WillRepeatedly(InvokeWithoutArgs([this] { m_wait.wakeUp(); })); + } + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(m_lwa->authorizeUsingCBL(m_cblObserver)); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + m_wait.reset(); + m_lwa->reset(); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); +} + +/// Parameterized Tests Exercising both authorizeUsingCBL and authorizeUsingCBLWithCustomerProfile +INSTANTIATE_TEST_CASE_P( + Parameterized, + LWAAuthorizationAdapterTest, + ::testing::Values(AUTHORIZE_USING_CBL, AUTHORIZE_USING_CBL_WITH_CUSTOMER_PROFILE)); + +/// Test that the happy case succeeds. +TEST_P(LWAAuthorizationAdapterTest, test_cblAuthorize_Succeeds) { + const std::string cblMethod = GetParam(); + + EXPECT_CALL(*m_cblObserver, onRequestAuthorization(VERIFICATION_URI, USER_CODE)); + EXPECT_CALL(*m_cblObserver, onCheckingForAuthorization()).Times(AtLeast(1)); + + std::function& observer)> + cblAuthFunc; + + if (AUTHORIZE_USING_CBL == cblMethod) { + setCBLExpectations(CUSTOMER_PROFILE_SHORT_RESPONSE); + + cblAuthFunc = std::bind(&LWAAuthorizationAdapter::authorizeUsingCBL, m_lwa, std::placeholders::_1); + } else if (AUTHORIZE_USING_CBL_WITH_CUSTOMER_PROFILE == cblMethod) { + EXPECT_CALL( + *m_cblObserver, onCustomerProfileAvailable(CBLAuthorizationObserverInterface::CustomerProfile(NAME, EMAIL))) + .Times(AtLeast(1)); + setCBLExpectations(CUSTOMER_PROFILE_RESPONSE); + + cblAuthFunc = + std::bind(&LWAAuthorizationAdapter::authorizeUsingCBLWithCustomerProfile, m_lwa, std::placeholders::_1); + } else { + FAIL(); + } + + { + InSequence s; + + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::AUTHORIZING, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + "")); + EXPECT_CALL( + *m_manager, + reportStateChange( + AuthObserverInterface::FullState( + AuthObserverInterface::State::REFRESHED, AuthObserverInterface::Error::SUCCESS), + DEFAULT_ADAPTER_ID, + USER_ID)) + .WillOnce(InvokeWithoutArgs([this] { m_wait.wakeUp(); })); + } + + m_lwa->onAuthorizationManagerReady(m_manager); + + // Function succeds and token is retrievable. + EXPECT_TRUE(cblAuthFunc(m_cblObserver)); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); + EXPECT_EQ(m_lwa->getAuthToken(), ACCESS_TOKEN); + + // Check that the storage states are correct. + std::string token; + EXPECT_TRUE(m_storage->getRefreshToken(&token)); + EXPECT_EQ(REFRESH_TOKEN, token); + + std::string id; + EXPECT_TRUE(m_storage->getUserId(&id)); + EXPECT_EQ(USER_ID, id); +} + +/// Check that authorizeUsingCBL is requesting with the correct scopes. +TEST_P(LWAAuthorizationAdapterTest, test_cblAuthorize_CorrectScopes) { + const std::string cblMethod = GetParam(); + + std::string scopes; + std::function& observer)> + cblAuthFunc; + + if (AUTHORIZE_USING_CBL == cblMethod) { + setCBLExpectations(NULL_HTTP_RESPONSE, TOKEN_EXCHANGE_RESPONSE, CUSTOMER_PROFILE_SHORT_RESPONSE); + + cblAuthFunc = std::bind(&LWAAuthorizationAdapter::authorizeUsingCBL, m_lwa, std::placeholders::_1); + scopes = "alexa:all profile:user_id"; + } else if (AUTHORIZE_USING_CBL_WITH_CUSTOMER_PROFILE == cblMethod) { + setCBLExpectations(NULL_HTTP_RESPONSE, TOKEN_EXCHANGE_RESPONSE, CUSTOMER_PROFILE_RESPONSE); + + cblAuthFunc = + std::bind(&LWAAuthorizationAdapter::authorizeUsingCBLWithCustomerProfile, m_lwa, std::placeholders::_1); + scopes = "alexa:all profile"; + } else { + FAIL(); + } + + EXPECT_CALL( + *m_httpPost, + doPost( + m_lwaConfig->getRequestCodePairUrl(), _, A>&>(), _)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke([this, scopes]( + const std::string& url, + const std::vector headerLines, + const std::vector>& data, + std::chrono::seconds timeout) { + auto expectedScopes = std::pair("scope", scopes); + EXPECT_THAT(data, Contains(expectedScopes)); + + m_wait.wakeUp(); + return CODE_PAIR_RESPONSE; + })); + + m_lwa->onAuthorizationManagerReady(m_manager); + EXPECT_TRUE(cblAuthFunc(m_cblObserver)); + EXPECT_TRUE(m_wait.wait(TIMEOUT)); +} + +} // namespace test +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK diff --git a/core/Authorization/acsdkAuthorization/test/LWAAuthorizationStorageTest.cpp b/core/Authorization/acsdkAuthorization/test/LWAAuthorizationStorageTest.cpp new file mode 100644 index 0000000000..e0b6856163 --- /dev/null +++ b/core/Authorization/acsdkAuthorization/test/LWAAuthorizationStorageTest.cpp @@ -0,0 +1,191 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace lwa { +namespace test { + +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces::test; +using namespace ::alexaClientSDK::avsCommon::avs::initialization; +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage; +using namespace ::alexaClientSDK::avsCommon::utils::configuration; +using namespace ::alexaClientSDK::storage::sqliteStorage; + +/// Component name for the misc DB tables. +static const std::string COMPONENT_NAME = "config"; + +/// Table name for the misc DB tables. +static const std::string TABLE_NAME = "LWAAuthorizationStorage"; + +/// Test value for refresh token. +static const std::string REFRESH_TOKEN_VALUE = "refreshTokenValue"; + +/// Test value for user id. +static const std::string USER_ID_VALUE = "userIdValue"; + +/// JSON text for miscDB config. +// clang-format off +static const std::string MISC_DB_CONFIG_JSON = R"( +{ + "lwaAuthorization": { + "databaseFilePath":"LWAAuthorizationStorageTest.db" + } +} +)"; +// clang-format on + +/** + * Test harness for @c LWAAuthorizationStorage class. + */ +class LWAAuthorizationStorageTest : public ::testing::Test { +public: + /** + * Constructor. + */ + LWAAuthorizationStorageTest(); + + /** + * Destructor. + */ + ~LWAAuthorizationStorageTest(); + +protected: + /** + * Deletes a test table from MiscDB. + */ + void cleanupTestDatabase(); +}; + +void LWAAuthorizationStorageTest::cleanupTestDatabase() { + std::remove("LWAAuthorizationStorageTest.db"); +} + +LWAAuthorizationStorageTest::LWAAuthorizationStorageTest() { + auto inString = std::shared_ptr(new std::istringstream(MISC_DB_CONFIG_JSON)); + AlexaClientSDKInit::initialize({inString}); +} + +LWAAuthorizationStorageTest::~LWAAuthorizationStorageTest() { + AlexaClientSDKInit::uninitialize(); +} + +TEST_F(LWAAuthorizationStorageTest, test_createFromEmptyStorage) { + auto propertiesFactory = StubPropertiesFactory::create(); + ASSERT_NE(nullptr, propertiesFactory); + auto storage = LWAAuthorizationStorage::createStorage(propertiesFactory); + ASSERT_NE(nullptr, storage); + + ASSERT_TRUE(storage->open()); + + std::string refreshToken; + std::string userId; + ASSERT_FALSE(storage->getRefreshToken(&refreshToken)); + ASSERT_FALSE(storage->getUserId(&userId)); + + storage->setRefreshToken(REFRESH_TOKEN_VALUE); + ASSERT_TRUE(storage->getRefreshToken(&refreshToken)); + ASSERT_EQ(REFRESH_TOKEN_VALUE, refreshToken); + + ASSERT_TRUE(storage->setUserId(USER_ID_VALUE)); + ASSERT_TRUE(storage->getUserId(&userId)); + ASSERT_EQ(USER_ID_VALUE, userId); +} + +TEST_F(LWAAuthorizationStorageTest, test_createFromNonEmptyStorage) { + auto propertiesFactory = StubPropertiesFactory::create(); + ASSERT_NE(nullptr, propertiesFactory); + auto properties = propertiesFactory->getProperties(CONFIG_URI); + ASSERT_NE(nullptr, properties); + + properties->putString(REFRESH_TOKEN_PROPERTY_NAME, REFRESH_TOKEN_VALUE); + properties->putString(USER_ID_PROPERTY_NAME, USER_ID_VALUE); + + auto storage = LWAAuthorizationStorage::createStorage(propertiesFactory); + ASSERT_NE(nullptr, storage); + + ASSERT_TRUE(storage->open()); + + std::string refreshToken; + std::string userId; + + ASSERT_TRUE(storage->getRefreshToken(&refreshToken)); + ASSERT_EQ(REFRESH_TOKEN_VALUE, refreshToken); + ASSERT_TRUE(storage->getUserId(&userId)); + ASSERT_EQ(USER_ID_VALUE, userId); +} + +TEST_F(LWAAuthorizationStorageTest, test_createFromEmptyDatabase) { + cleanupTestDatabase(); + + auto node = std::make_shared(ConfigurationNode::getRoot()); + auto storage = LWAAuthorizationStorage::createLWAAuthorizationStorageInterface(node, "", nullptr, nullptr); + ASSERT_NE(nullptr, storage); + + ASSERT_TRUE(storage->open()); + + std::string refreshToken; + std::string userId; + ASSERT_FALSE(storage->getRefreshToken(&refreshToken)); + ASSERT_FALSE(storage->getUserId(&userId)); +} + +TEST_F(LWAAuthorizationStorageTest, test_createsTableAfterPutCloseAndReopen) { + cleanupTestDatabase(); + + auto node = std::make_shared(ConfigurationNode::getRoot()); + auto storage = LWAAuthorizationStorage::createLWAAuthorizationStorageInterface(node, "", nullptr, nullptr); + ASSERT_NE(nullptr, storage); + ASSERT_TRUE(storage->open()); + + ASSERT_TRUE(storage->setUserId(USER_ID_VALUE)); + ASSERT_TRUE(storage->setRefreshToken(REFRESH_TOKEN_VALUE)); + + std::string refreshToken; + std::string userId; + ASSERT_TRUE(storage->getRefreshToken(&refreshToken)); + ASSERT_EQ(REFRESH_TOKEN_VALUE, refreshToken); + ASSERT_TRUE(storage->getUserId(&userId)); + ASSERT_EQ(USER_ID_VALUE, userId); + + storage.reset(); + + storage = LWAAuthorizationStorage::createLWAAuthorizationStorageInterface(node, "", nullptr, nullptr); + ASSERT_NE(nullptr, storage); + ASSERT_TRUE(storage->open()); + + refreshToken.clear(); + userId.clear(); + ASSERT_TRUE(storage->getRefreshToken(&refreshToken)); + ASSERT_EQ(REFRESH_TOKEN_VALUE, refreshToken); + ASSERT_TRUE(storage->getUserId(&userId)); + ASSERT_EQ(USER_ID_VALUE, userId); +} + +} // namespace test +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK diff --git a/core/Authorization/acsdkAuthorization/test/StubStorage.cpp b/core/Authorization/acsdkAuthorization/test/StubStorage.cpp new file mode 100644 index 0000000000..e328133a6f --- /dev/null +++ b/core/Authorization/acsdkAuthorization/test/StubStorage.cpp @@ -0,0 +1,85 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkAuthorization { +namespace lwa { +namespace test { + +bool StubStorage::openOrCreate() { + return true; +} + +bool StubStorage::createDatabase() { + return true; +} + +bool StubStorage::open() { + return true; +} + +bool StubStorage::setRefreshToken(const std::string& refreshToken) { + std::lock_guard lock(m_mutex); + m_refreshToken = refreshToken; + return true; +} + +bool StubStorage::clearRefreshToken() { + std::lock_guard lock(m_mutex); + m_refreshToken.clear(); + return true; +} + +bool StubStorage::getRefreshToken(std::string* refreshToken) { + if (!refreshToken) { + return false; + } + + std::lock_guard lock(m_mutex); + *refreshToken = m_refreshToken; + return !m_refreshToken.empty(); +} + +bool StubStorage::setUserId(const std::string& userId) { + std::lock_guard lock(m_mutex); + m_userId = userId; + return true; +} + +bool StubStorage::getUserId(std::string* userId) { + if (!userId) { + return false; + } + + std::lock_guard lock(m_mutex); + *userId = m_userId; + + return !m_userId.empty(); +} + +bool StubStorage::clear() { + std::lock_guard lock(m_mutex); + m_userId.clear(); + m_refreshToken.clear(); + + return true; +} + +} // namespace test +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK diff --git a/core/Authorization/acsdkAuthorization/test/include/acsdkAuthorization/LWA/test/StubStorage.h b/core/Authorization/acsdkAuthorization/test/include/acsdkAuthorization/LWA/test/StubStorage.h new file mode 100644 index 0000000000..4e5e50ce2d --- /dev/null +++ b/core/Authorization/acsdkAuthorization/test/include/acsdkAuthorization/LWA/test/StubStorage.h @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKAUTHORIZATION_LWA_TEST_STUBSTORAGE_H_ +#define ACSDKAUTHORIZATION_LWA_TEST_STUBSTORAGE_H_ + +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkAuthorization { +namespace lwa { +namespace test { + +class StubStorage : public acsdkAuthorizationInterfaces::lwa::LWAAuthorizationStorageInterface { +public: + bool openOrCreate() override; + bool open() override; + bool createDatabase() override; + bool setRefreshToken(const std::string& refreshToken) override; + bool clearRefreshToken() override; + bool getRefreshToken(std::string* refreshToken) override; + bool setUserId(const std::string& userId) override; + bool getUserId(std::string* userId) override; + bool clear() override; + +private: + std::mutex m_mutex; + std::string m_refreshToken; + std::string m_userId; +}; + +} // namespace test +} // namespace lwa +} // namespace acsdkAuthorization +} // namespace alexaClientSDK + +#endif // ACSDKAUTHORIZATION_LWA_TEST_STUBSTORAGE_H_ diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index f08a0f2208..20fed9cfd2 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_subdirectory("acsdkAlexaEventProcessedNotifierInterfaces") +add_subdirectory("acsdkCodecUtils") add_subdirectory("acsdkCore") add_subdirectory("acsdkPostConnectOperationProviderRegistrar") add_subdirectory("acsdkPostConnectOperationProviderRegistrarInterfaces") @@ -9,3 +10,5 @@ add_subdirectory("acsdkRegistrationManagerInterfaces") add_subdirectory("acsdkSystemClockMonitor") add_subdirectory("acsdkSystemClockMonitorInterfaces") add_subdirectory("Authorization") +add_subdirectory("Crypto") +add_subdirectory("Properties") diff --git a/core/Crypto/CMakeLists.txt b/core/Crypto/CMakeLists.txt new file mode 100644 index 0000000000..7c16f1fa0c --- /dev/null +++ b/core/Crypto/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.1) + +add_subdirectory("acsdkCryptoInterfaces") +add_subdirectory("acsdkCrypto") +add_subdirectory("acsdkPkcs11") diff --git a/core/Crypto/acsdkCrypto/CMakeLists.txt b/core/Crypto/acsdkCrypto/CMakeLists.txt new file mode 100644 index 0000000000..c6b1ef80d1 --- /dev/null +++ b/core/Crypto/acsdkCrypto/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +if (${CRYPTO_FOUND}) + message(STATUS "Adding Crypto Implementation.") + + project(acsdkCrypto LANGUAGES CXX) + add_subdirectory("src") + add_subdirectory("test") +else() + message(STATUS "Disabling Crypto Implementation.") +endif() diff --git a/core/Crypto/acsdkCrypto/doc/CryptoIMPL.dox b/core/Crypto/acsdkCrypto/doc/CryptoIMPL.dox new file mode 100644 index 0000000000..79f43e28ab --- /dev/null +++ b/core/Crypto/acsdkCrypto/doc/CryptoIMPL.dox @@ -0,0 +1,39 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \defgroup CryptoIMPL Cryptographic Functions Implementation + * @brief Implementations for \ref CryptoAPI + * + * CryptoIMPL provides implementation of cryptographic functions to encrypt and decrypt data, to generate keys and + * initialization vectors, and to compute digests. + * + * The reference implementation uses OpenSSL library, but can be extended to use platform-specific APIs. + * + * \sa CryptoAPI + * + * \sa alexaClientSDK::acsdkCrypto + * \sa alexaClientSDK::acsdkCrypto::test + * + * \namespace alexaClientSDK::acsdkCrypto + * \brief OpenSSL-based implementation. + * \ingroup CryptoIMPL + * \sa CryptoIMPL + * + * \namespace alexaClientSDK::acsdkCrypto::test + * \brief Test cases for \ref CryptoIMPL + * \ingroup CryptoIMPL + * \sa CryptoIMPL + */ diff --git a/core/Crypto/acsdkCrypto/include/acsdkCrypto/CryptoFactory.h b/core/Crypto/acsdkCrypto/include/acsdkCrypto/CryptoFactory.h new file mode 100644 index 0000000000..a7a84cf395 --- /dev/null +++ b/core/Crypto/acsdkCrypto/include/acsdkCrypto/CryptoFactory.h @@ -0,0 +1,36 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTO_CRYPTOFACTORY_H_ +#define ACSDKCRYPTO_CRYPTOFACTORY_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkCrypto { + +/** + * @brief Factory method for @c CryptoFactoryInterface. + * Provides crypto functions interface if available. + * + * @return Factory reference or nullptr on error. + * @see acsdkCryptoInterfaces::CryptoFactoryInterface + */ +std::shared_ptr createCryptoFactory() noexcept; + +} // namespace acsdkCrypto +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTO_CRYPTOFACTORY_H_ diff --git a/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/Logging.h b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/Logging.h new file mode 100644 index 0000000000..fecf44465f --- /dev/null +++ b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/Logging.h @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTO_PRIVATE_LOGGING_H_ +#define ACSDKCRYPTO_PRIVATE_LOGGING_H_ + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + * @private + * @ingroup CryptoIMPL + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +#include + +#endif // ACSDKCRYPTO_PRIVATE_LOGGING_H_ diff --git a/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslCryptoCodec.h b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslCryptoCodec.h new file mode 100644 index 0000000000..17e6c76fa9 --- /dev/null +++ b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslCryptoCodec.h @@ -0,0 +1,126 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTO_PRIVATE_OPENSSLCRYPTOCODEC_H_ +#define ACSDKCRYPTO_PRIVATE_OPENSSLCRYPTOCODEC_H_ + +#include + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkCrypto { + +using alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType; +using alexaClientSDK::acsdkCryptoInterfaces::CryptoCodecInterface; + +/** + * @brief Binary codec implementation. + * + * This class uses EVP API from OpenSSL library. We can add new algorithms as needed. + * + * @ingroup CryptoIMPL + */ +class OpenSslCryptoCodec : public CryptoCodecInterface { +public: + /** + * @brief Create decoder. + * + * Factory method to create decoder for an encryption algorithm. + * + * @param[in] type Encryption algorithm. + * + * @return Codec reference or nullptr on error. + */ + static std::unique_ptr createDecoder(AlgorithmType type) noexcept; + /** + * @brief Create encoder. + * + * Factory method to create encoder for an encryption algorithm. + * + * @param[in] type Encryption algorithm. + * + * @return Codec reference or nullptr on error. + */ + static std::unique_ptr createEncoder(AlgorithmType type) noexcept; + + /// @name CryptoCodecInterface methods. + ///@{ + ~OpenSslCryptoCodec() noexcept override; + bool init(const Key& key, const IV& iv) noexcept override; + bool processAAD(const DataBlock& dataIn) noexcept override; + bool processAAD(DataBlock::const_iterator dataInBegin, DataBlock::const_iterator dataInEnd) noexcept override; + bool process(const DataBlock& dataIn, DataBlock& dataOut) noexcept override; + bool process( + DataBlock::const_iterator dataInBegin, + DataBlock::const_iterator dataInEnd, + DataBlock& dataOut) noexcept override; + bool finalize(DataBlock& dataOut) noexcept override; + bool getTag(Tag& tag) noexcept override; + bool setTag(const Tag& tag) noexcept override; + ///@} + +private: + /** + * @brief Creates encoder or decoder. + * + * Creates encoder or decoder depending on arguments. + * + * @param[in] type Algorithm type. + * @param[in] codecType Codec type. + * + * @return Codec reference or nullptr on error. + */ + static std::unique_ptr createCodec(AlgorithmType type, CodecType codecType) noexcept; + + /** + * @brief Private constructor. + * + * @param[in] codecType Codec type. + * @param[in] algorithmType Cipher type. + */ + OpenSslCryptoCodec(CodecType codecType, AlgorithmType algorithmType) noexcept; + + /** + * @brief Checks if the algorithm is authenticated encryption and decryption. + * + * @return True, if @a m_algorithmType is from AEAD family. + */ + bool isAEADCipher() noexcept; + + /// Codec type. + const CodecType m_codecType; + + /// Codec cipher type. + const AlgorithmType m_algorithmType; + + /// Encryption context reference. + EVP_CIPHER_CTX* m_cipherCtx; + + /// Codec state. + bool m_initDone; + + /// OpenSSL cipher object. + const EVP_CIPHER* m_cipher; +}; + +} // namespace acsdkCrypto +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTO_PRIVATE_OPENSSLCRYPTOCODEC_H_ diff --git a/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslCryptoFactory.h b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslCryptoFactory.h new file mode 100644 index 0000000000..7554bef359 --- /dev/null +++ b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslCryptoFactory.h @@ -0,0 +1,66 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTO_PRIVATE_OPENSSLCRYPTOFACTORY_H_ +#define ACSDKCRYPTO_PRIVATE_OPENSSLCRYPTOFACTORY_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkCrypto { + +/** + * @brief Cryptography factory implementation based on OpenSSL. + * + * @ingroup CryptoIMPL + */ +class OpenSslCryptoFactory : public alexaClientSDK::acsdkCryptoInterfaces::CryptoFactoryInterface { +public: + /** + * @brief Initializes OpenSSL library and returns factory interface. + * + * @return Factory reference or nullptr on error. + */ + static std::shared_ptr create() noexcept; + + /// @name CryptoCodecInterface methods. + ///@{ + std::unique_ptr createEncoder( + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType type) noexcept override; + std::unique_ptr createDecoder( + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType type) noexcept override; + std::unique_ptr createDigest( + alexaClientSDK::acsdkCryptoInterfaces::DigestType type) noexcept override; + std::shared_ptr getKeyFactory() noexcept override; + ///@} +private: + /// Private object constructor. + OpenSslCryptoFactory() noexcept; + + /** + * @brief Initializes crypto library. + * + * @return Boolean indicating operation success. + */ + bool init() noexcept; + + /// Singleton instance of key factory. + std::shared_ptr m_keyFactory; +}; + +} // namespace acsdkCrypto +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTO_PRIVATE_OPENSSLCRYPTOFACTORY_H_ diff --git a/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslDigest.h b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslDigest.h new file mode 100644 index 0000000000..8e6ef2e04f --- /dev/null +++ b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslDigest.h @@ -0,0 +1,85 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTO_PRIVATE_OPENSSLDIGEST_H_ +#define ACSDKCRYPTO_PRIVATE_OPENSSLDIGEST_H_ + +#include + +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCrypto { + +using alexaClientSDK::acsdkCryptoInterfaces::DigestInterface; +using alexaClientSDK::acsdkCryptoInterfaces::DigestType; + +/** + * @brief Digest implementation based on OpenSSL. + * + * @ingroup CryptoIMPL + */ +class OpenSslDigest : public DigestInterface { +public: + /** + * @brief Creates a new digest instance. + * + * @param[in] type Digest type. + * + * @return Digest reference or nullptr on error. + */ + static std::unique_ptr create(DigestType type) noexcept; + + /// @name DigestInterface methods. + ///@{ + ~OpenSslDigest() noexcept override; + bool process(const DataBlock& data_in) noexcept override; + bool process(DataBlock::const_iterator begin, DataBlock::const_iterator end) noexcept override; + bool processByte(unsigned char value) noexcept override; + bool processUInt8(uint8_t value) noexcept override; + bool processUInt16(uint16_t value) noexcept override; + bool processUInt32(uint32_t value) noexcept override; + bool processUInt64(uint64_t value) noexcept override; + bool processString(const std::string& value) noexcept override; + bool finalize(DataBlock& digest) noexcept override; + bool reset() noexcept override; + ///@} +private: + /// @brief Private constructor. + OpenSslDigest() noexcept; + + /** + * @brief Initializes digest. + * + * @param[in] type Digest type. + * + * @return Boolean indicating operation success. + */ + bool init(DigestType type) noexcept; + + /// Digest algorithm reference. + const EVP_MD* m_md; + + /// Digest context structure. + EVP_MD_CTX* m_ctx; +}; + +} // namespace acsdkCrypto +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTO_PRIVATE_OPENSSLDIGEST_H_ diff --git a/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslErrorCleanup.h b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslErrorCleanup.h new file mode 100644 index 0000000000..4ee36ed50f --- /dev/null +++ b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslErrorCleanup.h @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTO_PRIVATE_OPENSSLERRORCLEANUP_H_ +#define ACSDKCRYPTO_PRIVATE_OPENSSLERRORCLEANUP_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace acsdkCrypto { + +/** + * @brief Helper class for handling OpenSSL errors. + * + * This class automatically clears OpenSSL error queue and logs errors to log on destruction. This helps with + * troubleshooting OpenSSL errors. + * + * @ingroup CryptoIMPL + */ +class OpenSslErrorCleanup : public avsCommon::utils::error::FinallyGuard { +public: + /** + * @brief Constructs cleanup object. + * + * Initializes finally object to clean up OpenSSL error queue on destructor. Internally this method configures + * @c FinallyGuard to call #clearAndLogOpenSslErrors() when destructor is called. + * + * @param[in] logTag Logging tag to use. + */ + OpenSslErrorCleanup(const std::string& logTag) noexcept; + + /** + * @brief Method clears openssl error queue and prints it to logger. + * + * This method clears OpenSSL error queue and prints errors to logger with a given tag. + * + * @param[in] logTag Logging tag to use. + */ + static void clearAndLogOpenSslErrors(const std::string& logTag) noexcept; +}; + +} // namespace acsdkCrypto +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTO_PRIVATE_OPENSSLERRORCLEANUP_H_ diff --git a/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslKeyFactory.h b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslKeyFactory.h new file mode 100644 index 0000000000..7e462b30fa --- /dev/null +++ b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslKeyFactory.h @@ -0,0 +1,66 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTO_PRIVATE_OPENSSLKEYFACTORY_H_ +#define ACSDKCRYPTO_PRIVATE_OPENSSLKEYFACTORY_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace acsdkCrypto { + +using alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType; +using alexaClientSDK::acsdkCryptoInterfaces::KeyFactoryInterface; + +/** + * @brief Key factory implementation based on OpenSSL. + * + * @ingroup CryptoIMPL + */ +class OpenSslKeyFactory : public KeyFactoryInterface { +public: + /** + * @brief Factory method. + * + * @return Key factory reference or nullptr. + */ + static std::shared_ptr create() noexcept; + + /// @name KeyFactoryInterface methods. + ///@{ + bool generateKey(AlgorithmType type, Key& key) noexcept override; + bool generateIV(AlgorithmType type, IV& iv) noexcept override; + ///@} +private: + /// Private constructor. + OpenSslKeyFactory() noexcept; + + /** + * @brief Helper method to generate random output. + * + * @param[out] data Random data destination. + * @param[in] size Result size. + * + * @return Boolean, indicating operation success. + */ + bool generateRandom(std::vector& data, int size) noexcept; +}; + +} // namespace acsdkCrypto +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTO_PRIVATE_OPENSSLKEYFACTORY_H_ diff --git a/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslTypeMapper.h b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslTypeMapper.h new file mode 100644 index 0000000000..06537ba047 --- /dev/null +++ b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslTypeMapper.h @@ -0,0 +1,86 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTO_PRIVATE_OPENSSLTYPEMAPPER_H_ +#define ACSDKCRYPTO_PRIVATE_OPENSSLTYPEMAPPER_H_ + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkCrypto { + +using alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType; +using alexaClientSDK::acsdkCryptoInterfaces::DigestType; + +/** + * @brief Helper class to map SDK types into types from OpenSSL EVP API. + * + * @ingroup CryptoIMPL + */ +class OpenSslTypeMapper { +public: + /** + * @brief Find OpenSSL codec implementation. + * + * Finds OpenSSL codec implementation for a given encryption algorithm. + * + * @param[in] type Encryption algorithm. + * + * @return OpenSSL algorithm reference or nullptr on error. + */ + static const EVP_CIPHER* mapAlgorithmToEvpCipher(AlgorithmType type) noexcept; + + /** + * @brief Determine padding mode for an encryption algorithm, + * + * Finds OpenSSL padding mode for a given encryption algorithm. + * + * @param[in] type Encryption Algorithm. + * @param[out] mode Padding mode. + * + * @return Boolean indicating success. + */ + static bool mapAlgorithmToPadding(AlgorithmType type, PaddingMode& mode) noexcept; + + /** + * @brief Maps algorithm to tag size for AEAD algorithms. + * + * @param[in] type Encryption algorithm. + * @param[out] tagSize Tag size or 0 if algorithm doesn't support tags. + * + * @return Boolean indicating success. + */ + static bool mapAlgorithmToTagSize(AlgorithmType type, size_t& tagSize) noexcept; + + /** + * @brief Find OpenSSL digest implementation. + * + * Finds OpenSSL digest implementation for a given digest algorithm. + * + * @param[in] type Digest algorithm. + * + * @return OpenSSL algorithm reference or nullptr on error. + */ + static const EVP_MD* mapDigestToEvpMd(DigestType type) noexcept; +}; + +} // namespace acsdkCrypto +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTO_PRIVATE_OPENSSLTYPEMAPPER_H_ diff --git a/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslTypes.h b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslTypes.h new file mode 100644 index 0000000000..aaa9886eb8 --- /dev/null +++ b/core/Crypto/acsdkCrypto/privateInclude/acsdkCrypto/private/OpenSslTypes.h @@ -0,0 +1,72 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTO_PRIVATE_OPENSSLTYPES_H_ +#define ACSDKCRYPTO_PRIVATE_OPENSSLTYPES_H_ + +/** + * @brief Macro for cutting off OpenSSL features introduced before 1.1.0 release. + * @private + * @ingroup CryptoIMPL + */ +#define OPENSSL_VERSION_NUMBER_1_1_0 0x10100000L + +#include + +namespace alexaClientSDK { +namespace acsdkCrypto { + +/** + * @brief Typed enumeration for codec types to use with EVP API. + * + * This enumeration defines values for use with EVP_CipherInit_ex method. + * + * @ingroup CryptoIMPL + */ +enum class CodecType : int { + Decoder = 0, ///< Decoder. + Encoder = 1, ///< Encoder. +}; + +/** + * @brief Typed enumeration for padding mode to use with EVP API. + * + * This enumeration defines values for use with EVP_CIPHER_CTX_set_padding method. + * + * @ingroup CryptoIMPL + */ +enum class PaddingMode : int { + NoPadding = 0, ///< No padding. + Padding = 1, ///< PKCS#7 padding. +}; + +/// Success code for some OpenSSL functions. +/// @private +/// @ingroup CryptoIMPL +static constexpr int OPENSSL_OK = 1; + +} // namespace acsdkCrypto +} // namespace alexaClientSDK + +namespace std { + +/// @brief Pretty-print function for padding mode values. +/// @private +/// @ingroup CryptoIMPL +std::ostream& operator<<(std::ostream& o, alexaClientSDK::acsdkCrypto::PaddingMode mode); + +} // namespace std + +#endif // ACSDKCRYPTO_PRIVATE_OPENSSLTYPES_H_ diff --git a/core/Crypto/acsdkCrypto/src/CMakeLists.txt b/core/Crypto/acsdkCrypto/src/CMakeLists.txt new file mode 100644 index 0000000000..b9f28418ec --- /dev/null +++ b/core/Crypto/acsdkCrypto/src/CMakeLists.txt @@ -0,0 +1,37 @@ + +set(acsdkCrypto_SOURCES + CryptoFactory.cpp + OpenSslCryptoCodec.cpp + OpenSslCryptoFactory.cpp + OpenSslDigest.cpp + OpenSslErrorCleanup.cpp + OpenSslKeyFactory.cpp + OpenSslTypeMapper.cpp + OpenSslTypes.cpp + ) +set(acsdkCrypto_COMPILE_DEFS + ACSDK_LOG_MODULE=acsdkCrypto + ) +set(acsdkCrypto_INCLUDES + "${acsdkCrypto_SOURCE_DIR}/include" + ) +set(acsdkCrypto_PRIVATE_INCLUDES + "${acsdkCrypto_SOURCE_DIR}/privateInclude" + ) +set(acsdkCrypto_LIBRARIES + AVSCommon + acsdkCryptoInterfaces + ${CRYPTO_LDFLAGS} + ) + +list(APPEND acsdkCrypto_LIBRARIES ${CRYPTO_LDFLAGS}) +list(APPEND acsdkCrypto_PRIVATE_INCLUDES ${CRYPTO_INCLUDE_DIRS}) + +add_library(acsdkCrypto ${acsdkCrypto_SOURCES}) +target_compile_definitions(acsdkCrypto PRIVATE ${acsdkCrypto_COMPILE_DEFS}) +target_include_directories(acsdkCrypto PUBLIC ${acsdkCrypto_INCLUDES}) +target_include_directories(acsdkCrypto PRIVATE ${acsdkCrypto_PRIVATE_INCLUDES}) +target_link_libraries(acsdkCrypto ${acsdkCrypto_LIBRARIES}) + +# install target +asdk_install() diff --git a/core/Crypto/acsdkCrypto/src/CryptoFactory.cpp b/core/Crypto/acsdkCrypto/src/CryptoFactory.cpp new file mode 100644 index 0000000000..23b9256e3d --- /dev/null +++ b/core/Crypto/acsdkCrypto/src/CryptoFactory.cpp @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCrypto { + +using namespace alexaClientSDK::acsdkCryptoInterfaces; + +std::shared_ptr createCryptoFactory() noexcept { + return OpenSslCryptoFactory::create(); +} + +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/src/OpenSslCryptoCodec.cpp b/core/Crypto/acsdkCrypto/src/OpenSslCryptoCodec.cpp new file mode 100644 index 0000000000..2699bafab5 --- /dev/null +++ b/core/Crypto/acsdkCrypto/src/OpenSslCryptoCodec.cpp @@ -0,0 +1,295 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkCrypto { + +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"OpenSSL::CryptoCodec"}; + +std::unique_ptr OpenSslCryptoCodec::createDecoder(AlgorithmType type) noexcept { + ACSDK_DEBUG9(LX("createDecoder").d("algorithmType", type)); + auto cipher = createCodec(type, CodecType::Decoder); + if (!cipher) { + ACSDK_ERROR(LX("createDecoderFailed").d("algorithmType", type)); + } + return cipher; +} + +std::unique_ptr OpenSslCryptoCodec::createEncoder(AlgorithmType type) noexcept { + ACSDK_DEBUG9(LX("createEncoder").d("algorithmType", type)); + auto cipher = createCodec(type, CodecType::Encoder); + if (!cipher) { + ACSDK_ERROR(LX("createEncoderFailed").d("algorithmType", type)); + } + return cipher; +} + +std::unique_ptr OpenSslCryptoCodec::createCodec(AlgorithmType type, CodecType codecType) noexcept { + const EVP_CIPHER* cipher = OpenSslTypeMapper::mapAlgorithmToEvpCipher(type); + + if (!cipher) { + return nullptr; + } + + return std::unique_ptr(new OpenSslCryptoCodec(codecType, type)); +} + +OpenSslCryptoCodec::OpenSslCryptoCodec(CodecType codecType, AlgorithmType algorithmType) noexcept : + m_codecType{codecType}, + m_algorithmType{algorithmType}, + m_cipherCtx{EVP_CIPHER_CTX_new()}, + m_initDone{false} { +} + +OpenSslCryptoCodec::~OpenSslCryptoCodec() noexcept { + OpenSslErrorCleanup errorCleanup{TAG}; + EVP_CIPHER_CTX_free(m_cipherCtx); +} + +bool OpenSslCryptoCodec::init(const Key& key, const IV& iv) noexcept { + OpenSslErrorCleanup errorCleanup{TAG}; + + m_cipher = OpenSslTypeMapper::mapAlgorithmToEvpCipher(m_algorithmType); + if (!m_cipher) { + ACSDK_ERROR(LX("initFailed").d("reason", "cipherNull")); + return false; + } + + PaddingMode paddingMode = PaddingMode::NoPadding; + + if (!OpenSslTypeMapper::mapAlgorithmToPadding(m_algorithmType, paddingMode)) { + ACSDK_ERROR(LX("initFailed").d("reason", "badPaddingMode")); + return false; + } + + auto cipher_key_length = EVP_CIPHER_key_length(m_cipher); + auto ivLength = EVP_CIPHER_iv_length(m_cipher); + + m_initDone = false; + + if (iv.size() != (size_t)ivLength) { + ACSDK_ERROR(LX("initFailed").d("reason", "badIvSize")); + return false; + } + + if (key.size() != (size_t)cipher_key_length) { + ACSDK_ERROR(LX("initFailed").d("reason", "badKeySize")); + return false; + } + + EVP_CIPHER_CTX_init(m_cipherCtx); + if (OPENSSL_OK == + EVP_CipherInit_ex(m_cipherCtx, m_cipher, nullptr, &key[0], &iv[0], static_cast(m_codecType))) { + if (OPENSSL_OK == EVP_CIPHER_CTX_set_padding(m_cipherCtx, static_cast(paddingMode))) { + m_initDone = true; + } else { + ACSDK_ERROR(LX("initFailed").m("failedToSetPadding")); + } + } else { + ACSDK_ERROR(LX("initFailed").d("reason", "cipherInitFailed")); + } + if (!m_initDone) { + EVP_CIPHER_CTX_cleanup(m_cipherCtx); + } + + return m_initDone; +} + +bool OpenSslCryptoCodec::processAAD( + DataBlock::const_iterator dataInBegin, + DataBlock::const_iterator dataInEnd) noexcept { + if (!m_initDone) { + ACSDK_ERROR(LX("processAADFailed").d("reason", "cipherIsNotInitialized")); + return false; + } + if (dataInBegin > dataInEnd) { + ACSDK_ERROR(LX("processAADFailed").d("reason", "dataInAfterDataOut")); + return false; + } + if (dataInBegin == dataInEnd) { + return true; + } + + if (!isAEADCipher()) { + ACSDK_ERROR(LX("processAADFailed").d("reason", "notAEAD")); + return false; + } + + OpenSslErrorCleanup errorCleanup{TAG}; + + int cipherBlockSize = EVP_CIPHER_block_size(m_cipher); + int outLen = (dataInEnd - dataInBegin) + cipherBlockSize; + + if (OPENSSL_OK == EVP_CipherUpdate(m_cipherCtx, nullptr, &outLen, &*dataInBegin, dataInEnd - dataInBegin)) { + return true; + } else { + ACSDK_ERROR(LX("processAADFailed").d("reason", "cipherUpdateFailed")); + EVP_CIPHER_CTX_cleanup(m_cipherCtx); + m_initDone = false; + return false; + } +} + +bool OpenSslCryptoCodec::processAAD(const DataBlock& dataIn) noexcept { + return processAAD(dataIn.cbegin(), dataIn.cend()); +} + +bool OpenSslCryptoCodec::process( + DataBlock::const_iterator dataInBegin, + DataBlock::const_iterator dataInEnd, + DataBlock& dataOut) noexcept { + if (!m_initDone) { + ACSDK_ERROR(LX("processFailed").d("reason", "cipherIsNotInitialized")); + return false; + } + if (dataInBegin > dataInEnd) { + ACSDK_ERROR(LX("processFailed").d("reason", "dataInAfterDataOut")); + return false; + } + if (dataInBegin == dataInEnd) { + return true; + } + + OpenSslErrorCleanup errorCleanup{TAG}; + + int cipherBlockSize = EVP_CIPHER_block_size(m_cipher); + + int outLen = (dataInEnd - dataInBegin) + cipherBlockSize; + size_t index = dataOut.size(); + dataOut.resize(index + outLen); + + if (OPENSSL_OK == EVP_CipherUpdate(m_cipherCtx, &dataOut[index], &outLen, &*dataInBegin, dataInEnd - dataInBegin)) { + dataOut.resize(index + outLen); + + return true; + } else { + ACSDK_ERROR(LX("processFailed").d("reason", "cipherUpdateFailed")); + EVP_CIPHER_CTX_cleanup(m_cipherCtx); + m_initDone = false; + return false; + } +} + +bool OpenSslCryptoCodec::process(const DataBlock& dataIn, DataBlock& dataOut) noexcept { + return process(dataIn.cbegin(), dataIn.cend(), dataOut); +} + +bool OpenSslCryptoCodec::finalize(DataBlock& dataOut) noexcept { + if (!m_initDone) { + ACSDK_ERROR(LX("finalizeFailed").d("reason", "cipherIsNotInitialized")); + return false; + } + + OpenSslErrorCleanup errorCleanup{TAG}; + + int cipherBlockSize = EVP_CIPHER_block_size(m_cipher); + size_t index = dataOut.size(); + dataOut.resize(index + cipherBlockSize); + int outLen = cipherBlockSize; + + int rv = EVP_CipherFinal_ex(m_cipherCtx, (unsigned char*)&dataOut[index], &outLen); + if (!isAEADCipher() || CodecType::Decoder == m_codecType) { + m_initDone = false; + EVP_CIPHER_CTX_cleanup(m_cipherCtx); + } + if (OPENSSL_OK != rv) { + ACSDK_ERROR(LX("finalizeFailed").d("reason", "cipherFinalFailed")); + return false; + } + + dataOut.resize(index + outLen); + return true; +} + +bool OpenSslCryptoCodec::getTag(Tag& tag) noexcept { + if (!m_initDone) { + ACSDK_ERROR(LX("getTagFailed").d("reason", "cipherIsNotInitialized")); + return false; + } + if (!isAEADCipher()) { + ACSDK_ERROR(LX("getTagFailed").d("reason", "notAEAD")); + return false; + } + if (CodecType::Encoder != m_codecType) { + ACSDK_ERROR(LX("getTagFailed").d("reason", "notEncoder")); + return false; + } + size_t tagSize; + if (!OpenSslTypeMapper::mapAlgorithmToTagSize(m_algorithmType, tagSize)) { + ACSDK_ERROR(LX("getTagFailed").d("reason", "tagSizeUnknown")); + return false; + } + size_t tagOffset = tag.size(); + tag.resize(tagOffset + tagSize); + OpenSslErrorCleanup errorCleanup{TAG}; + + if (OPENSSL_OK != EVP_CIPHER_CTX_ctrl(m_cipherCtx, EVP_CTRL_GCM_GET_TAG, tag.size(), tag.data() + tagOffset)) { + ACSDK_ERROR(LX("getTagFailed").d("reason", "sslError")); + return false; + } + + m_initDone = false; + EVP_CIPHER_CTX_cleanup(m_cipherCtx); + + return true; +} + +bool OpenSslCryptoCodec::setTag(const Tag& tag) noexcept { + if (!m_initDone) { + ACSDK_ERROR(LX("setTagFailed").d("reason", "cipherIsNotInitialized")); + return false; + } + if (!isAEADCipher()) { + ACSDK_ERROR(LX("setTagFailed").d("reason", "notAEAD")); + return false; + } + if (CodecType::Decoder != m_codecType) { + ACSDK_ERROR(LX("setTagFailed").d("reason", "notDecoder")); + return false; + } + + OpenSslErrorCleanup errorCleanup{TAG}; + + unsigned char* const tagData = const_cast(tag.data()); + + if (OPENSSL_OK != EVP_CIPHER_CTX_ctrl(m_cipherCtx, EVP_CTRL_GCM_SET_TAG, tag.size(), tagData)) { + ACSDK_ERROR(LX("setTagFailed").d("reason", "opensslError")); + return false; + } + + return true; +} + +bool OpenSslCryptoCodec::isAEADCipher() noexcept { + switch (m_algorithmType) { + case AlgorithmType::AES_256_GCM: + case AlgorithmType::AES_128_GCM: + return true; + default: + return false; + } +} + +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/src/OpenSslCryptoFactory.cpp b/core/Crypto/acsdkCrypto/src/OpenSslCryptoFactory.cpp new file mode 100644 index 0000000000..972bb4e546 --- /dev/null +++ b/core/Crypto/acsdkCrypto/src/OpenSslCryptoFactory.cpp @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCrypto { + +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"OpenSSL::CryptoFactory"}; + +std::shared_ptr OpenSslCryptoFactory::create() noexcept { + auto res = std::shared_ptr(new OpenSslCryptoFactory); + if (!res->init()) { + ACSDK_ERROR(LX("createFailed")); + res.reset(); + } + + return res; +} + +OpenSslCryptoFactory::OpenSslCryptoFactory() noexcept { +} + +bool OpenSslCryptoFactory::init() noexcept { + m_keyFactory = OpenSslKeyFactory::create(); + if (!m_keyFactory) { + ACSDK_ERROR(LX("keyFactoryCreateFailed")); + return false; + } + + return true; +} + +std::unique_ptr OpenSslCryptoFactory::createEncoder(AlgorithmType type) noexcept { + return OpenSslCryptoCodec::createEncoder(type); +} + +std::unique_ptr OpenSslCryptoFactory::createDecoder(AlgorithmType type) noexcept { + return OpenSslCryptoCodec::createDecoder(type); +} + +std::unique_ptr OpenSslCryptoFactory::createDigest(DigestType type) noexcept { + return OpenSslDigest::create(type); +} + +std::shared_ptr OpenSslCryptoFactory::getKeyFactory() noexcept { + return m_keyFactory; +} + +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/src/OpenSslDigest.cpp b/core/Crypto/acsdkCrypto/src/OpenSslDigest.cpp new file mode 100644 index 0000000000..2202861c93 --- /dev/null +++ b/core/Crypto/acsdkCrypto/src/OpenSslDigest.cpp @@ -0,0 +1,140 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkCrypto { + +using namespace alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"OpenSSL::Digest"}; + +#if CHAR_BIT != 8 +#error This source file must be modified to support 7-bit characters +#endif + +std::unique_ptr OpenSslDigest::create(DigestType type) noexcept { + std::unique_ptr res(new OpenSslDigest); + + if (!res->init(type)) { + ACSDK_ERROR(LX("createFailed")); + res.reset(); + } + + return res; +} + +OpenSslDigest::OpenSslDigest() noexcept : m_ctx{nullptr} { + OpenSslErrorCleanup errorCleanup{TAG}; +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_NUMBER_1_1_0 + m_ctx = EVP_MD_CTX_new(); +#else + m_ctx = EVP_MD_CTX_create(); +#endif +} + +OpenSslDigest::~OpenSslDigest() noexcept { +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_NUMBER_1_1_0 + EVP_MD_CTX_free(m_ctx); +#else + EVP_MD_CTX_destroy(m_ctx); +#endif +} + +bool OpenSslDigest::init(DigestType type) noexcept { + m_md = OpenSslTypeMapper::mapDigestToEvpMd(type); + + return OPENSSL_OK == EVP_DigestInit_ex(m_ctx, m_md, nullptr); +} + +bool OpenSslDigest::process(const DataBlock& data_in) noexcept { + OpenSslErrorCleanup errorCleanup{TAG}; + return data_in.empty() || OPENSSL_OK == EVP_DigestUpdate(m_ctx, &data_in[0], data_in.size()); +} + +bool OpenSslDigest::process(DataBlock::const_iterator begin, DataBlock::const_iterator end) noexcept { + OpenSslErrorCleanup errorCleanup{TAG}; + return begin == end || OPENSSL_OK == EVP_DigestUpdate(m_ctx, &*begin, end - begin); +} + +bool OpenSslDigest::processByte(unsigned char value) noexcept { + OpenSslErrorCleanup errorCleanup{TAG}; + return OPENSSL_OK == EVP_DigestUpdate(m_ctx, &value, sizeof(value)); +} + +bool OpenSslDigest::processUInt8(uint8_t value) noexcept { + OpenSslErrorCleanup errorCleanup{TAG}; + return OPENSSL_OK == EVP_DigestUpdate(m_ctx, &value, sizeof(value)); +} + +bool OpenSslDigest::processUInt16(uint16_t value) noexcept { + const DataBlock bigEndianValue{(DataBlock::value_type)(value >> 8), (DataBlock::value_type)value}; + return process(bigEndianValue); +} + +bool OpenSslDigest::processUInt32(uint32_t value) noexcept { + const DataBlock bigEndianValue{(DataBlock::value_type)(value >> CHAR_BIT * 3), + (DataBlock::value_type)(value >> CHAR_BIT * 2), + (DataBlock::value_type)(value >> CHAR_BIT * 1), + (DataBlock::value_type)value}; + return process(bigEndianValue); +} + +bool OpenSslDigest::processUInt64(uint64_t value) noexcept { + const DataBlock bigEndianValue{(DataBlock::value_type)(value >> CHAR_BIT * 7), + (DataBlock::value_type)(value >> CHAR_BIT * 6), + (DataBlock::value_type)(value >> CHAR_BIT * 5), + (DataBlock::value_type)(value >> CHAR_BIT * 4), + (DataBlock::value_type)(value >> CHAR_BIT * 3), + (DataBlock::value_type)(value >> CHAR_BIT * 2), + (DataBlock::value_type)(value >> CHAR_BIT * 1), + (DataBlock::value_type)value}; + return process(bigEndianValue); +} + +bool OpenSslDigest::processString(const std::string& value) noexcept { + return value.empty() || OPENSSL_OK == EVP_DigestUpdate(m_ctx, value.c_str(), value.size()); +} + +bool OpenSslDigest::finalize(DataBlock& dataOut) noexcept { + OpenSslErrorCleanup errorCleanup{TAG}; + size_t index = dataOut.size(); + dataOut.resize(index + EVP_MD_size(m_md)); + bool res = (OPENSSL_OK == EVP_DigestFinal_ex(m_ctx, &dataOut[index], nullptr)); + reset(); + return res; +} + +bool OpenSslDigest::reset() noexcept { + OpenSslErrorCleanup errorCleanup{TAG}; +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_NUMBER_1_1_0 + return OPENSSL_OK == EVP_MD_CTX_reset(m_ctx); +#else + EVP_MD_CTX_cleanup(m_ctx); + return OPENSSL_OK == EVP_DigestInit_ex(m_ctx, m_md, nullptr); +#endif +} + +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/src/OpenSslErrorCleanup.cpp b/core/Crypto/acsdkCrypto/src/OpenSslErrorCleanup.cpp new file mode 100644 index 0000000000..4afd2636e0 --- /dev/null +++ b/core/Crypto/acsdkCrypto/src/OpenSslErrorCleanup.cpp @@ -0,0 +1,40 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCrypto { + +using ::alexaClientSDK::avsCommon::utils::logger::LogEntry; + +OpenSslErrorCleanup::OpenSslErrorCleanup(const std::string& logTag) noexcept : + avsCommon::utils::error::FinallyGuard{std::bind(OpenSslErrorCleanup::clearAndLogOpenSslErrors, logTag)} { +} + +void OpenSslErrorCleanup::clearAndLogOpenSslErrors(const std::string& logTag) noexcept { + unsigned long opensslErrorCode; + while ((opensslErrorCode = ERR_get_error())) { + char opensslErrorStr[256]; + ERR_error_string_n(opensslErrorCode, opensslErrorStr, sizeof(opensslErrorStr)); + ACSDK_DEBUG0(LogEntry(logTag, "opensslError").m(opensslErrorStr)); + } +} + +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/src/OpenSslKeyFactory.cpp b/core/Crypto/acsdkCrypto/src/OpenSslKeyFactory.cpp new file mode 100644 index 0000000000..8bed39851a --- /dev/null +++ b/core/Crypto/acsdkCrypto/src/OpenSslKeyFactory.cpp @@ -0,0 +1,75 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkCrypto { + +using namespace alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"OpenSSL::KeyFactory"}; + +std::shared_ptr OpenSslKeyFactory::create() noexcept { + return std::shared_ptr(new OpenSslKeyFactory); +} + +OpenSslKeyFactory::OpenSslKeyFactory() noexcept { +} + +bool OpenSslKeyFactory::generateKey(AlgorithmType type, Key& key) noexcept { + const EVP_CIPHER* evpCipher = OpenSslTypeMapper::mapAlgorithmToEvpCipher(type); + if (!evpCipher) { + ACSDK_ERROR(LX("cipherNotRecognized")); + return false; + } + + OpenSslErrorCleanup errorCleanup{TAG}; + int len = EVP_CIPHER_key_length(evpCipher); + return generateRandom(key, len); +} + +bool OpenSslKeyFactory::generateIV(AlgorithmType type, IV& iv) noexcept { + const EVP_CIPHER* evpCipher = OpenSslTypeMapper::mapAlgorithmToEvpCipher(type); + if (!evpCipher) { + ACSDK_ERROR(LX("cipherNotRecognized")); + return false; + } + + OpenSslErrorCleanup errorCleanup{TAG}; + int len = EVP_CIPHER_iv_length(evpCipher); + return generateRandom(iv, len); +} + +bool OpenSslKeyFactory::generateRandom(std::vector& data, int size) noexcept { + if (size < 0) { + ACSDK_ERROR(LX("negativeBlockSize")); + return false; + } + data.resize(static_cast(size)); + RAND_bytes(&data[0], size); + return true; +} + +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/src/OpenSslTypeMapper.cpp b/core/Crypto/acsdkCrypto/src/OpenSslTypeMapper.cpp new file mode 100644 index 0000000000..185ca465b7 --- /dev/null +++ b/core/Crypto/acsdkCrypto/src/OpenSslTypeMapper.cpp @@ -0,0 +1,113 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkCrypto { + +using namespace alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"OpenSSL::TypeMapper"}; + +const EVP_CIPHER* OpenSslTypeMapper::mapAlgorithmToEvpCipher(AlgorithmType type) noexcept { + const EVP_CIPHER* evpCipher = nullptr; + + switch (type) { + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_256_CBC_PAD: + evpCipher = EVP_aes_256_cbc(); + break; + case AlgorithmType::AES_128_CBC: + case AlgorithmType::AES_128_CBC_PAD: + evpCipher = EVP_aes_128_cbc(); + break; + case AlgorithmType::AES_128_GCM: + evpCipher = EVP_aes_128_gcm(); + break; + case AlgorithmType::AES_256_GCM: + evpCipher = EVP_aes_256_gcm(); + break; + default: + ACSDK_ERROR(LX("unknownAlgorithmType").d("type", type)); + break; + } + + return evpCipher; +} + +bool OpenSslTypeMapper::mapAlgorithmToPadding(AlgorithmType type, PaddingMode& mode) noexcept { + switch (type) { + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_128_CBC: + mode = PaddingMode::NoPadding; + break; + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_128_CBC_PAD: + mode = PaddingMode::Padding; + break; + case AlgorithmType::AES_256_GCM: + case AlgorithmType::AES_128_GCM: + mode = PaddingMode::NoPadding; + break; + default: + ACSDK_ERROR(LX("unknownAlgorithmType").d("type", type)); + return false; + } + + return true; +} + +bool OpenSslTypeMapper::mapAlgorithmToTagSize(AlgorithmType type, size_t& tagSize) noexcept { + switch (type) { + case AlgorithmType::AES_256_GCM: + case AlgorithmType::AES_128_GCM: + tagSize = 16u; + return true; + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_128_CBC: + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_128_CBC_PAD: + tagSize = 0u; + return true; + default: + ACSDK_ERROR(LX("unknownAlgorithmType").d("type", type)); + return false; + } +} + +const EVP_MD* OpenSslTypeMapper::mapDigestToEvpMd(DigestType type) noexcept { + const EVP_MD* evpMd = nullptr; + + switch (type) { + case DigestType::SHA_256: + evpMd = EVP_sha256(); + break; + default: + ACSDK_ERROR(LX("unknownAlgorithmType").d("type", type)); + break; + } + + return evpMd; +} + +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/src/OpenSslTypes.cpp b/core/Crypto/acsdkCrypto/src/OpenSslTypes.cpp new file mode 100644 index 0000000000..0a0632b0ea --- /dev/null +++ b/core/Crypto/acsdkCrypto/src/OpenSslTypes.cpp @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 std { + +std::ostream& operator<<(std::ostream& o, alexaClientSDK::acsdkCrypto::PaddingMode mode) { + const char* type; + switch (mode) { + case alexaClientSDK::acsdkCrypto::PaddingMode::NoPadding: + type = "NoPadding"; + break; + case alexaClientSDK::acsdkCrypto::PaddingMode::Padding: + type = "Padding"; + break; + default: + return o << static_cast(mode); + } + return o << type; +} + +} // namespace std diff --git a/core/Crypto/acsdkCrypto/test/CMakeLists.txt b/core/Crypto/acsdkCrypto/test/CMakeLists.txt new file mode 100644 index 0000000000..dbb5822fd5 --- /dev/null +++ b/core/Crypto/acsdkCrypto/test/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +set(TEST_INCLUDES + "${acsdkCrypto_SOURCE_DIR}/privateInclude" + "${CRYPTO_INCLUDE_DIRS}" + ) +set(TEST_LIBRIRIES + acsdkCrypto acsdkCodecUtils ${CRYPTO_LDFLAGS} + ) + +add_definitions("-DACSDK_LOG_MODULE=acsdkCryptoTest") +discover_unit_tests("${TEST_INCLUDES}" "${TEST_LIBRIRIES}") diff --git a/core/Crypto/acsdkCrypto/test/OpenSslCryptoCodecAEADTest.cpp b/core/Crypto/acsdkCrypto/test/OpenSslCryptoCodecAEADTest.cpp new file mode 100644 index 0000000000..73091a163b --- /dev/null +++ b/core/Crypto/acsdkCrypto/test/OpenSslCryptoCodecAEADTest.cpp @@ -0,0 +1,329 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCrypto { +namespace test { + +using namespace ::testing; +using namespace alexaClientSDK::acsdkCryptoInterfaces; +using namespace alexaClientSDK::acsdkCodecUtils; + +static std::vector hexStringToBytes(const std::string& hex); + +/// Test string for encryption and decryption. +static const std::string TEST_STR{"The quick brown fox jumps over the lazy dog"}; +/// Test authentication data for encryption and decryption. +static const std::string TEST_AD{"Authentication data"}; +/// Initialization vector. +const OpenSslCryptoCodec::IV TEST_IV = hexStringToBytes("0EB033BB783123FBA5391E94"); +/// AES-128 bit key. +const OpenSslCryptoCodec::Key TEST_KEY128 = hexStringToBytes("3595292D00F5F379C231DD785609C3F1"); +/// AES-256 bit key. +const OpenSslCryptoCodec::Key TEST_KEY256 = + hexStringToBytes("829E7C69986F36F0F3116F3D3F9E941839193C3849D6CCCCA42AA734792A7081"); +/// MAC for encrypting @a TEST_STR with @a TEST_KEY128 and @a TEST_IV. +const std::string TEST_TAG128{"0554a0cb6e9d120b041a246c0376b02b"}; +/// MAC for encrypting @a TEST_STR with @a TEST_KEY256 and @a TEST_IV. +const std::string TEST_TAG256{"d79fbdd28e70ff74f267301f51c2471e"}; +/// Random MAC code. +const std::string TEST_TAGBAD{"00000000000000000000000000000000"}; +/// Ciphertext from encrypting @a TEST_STR with @a TEST_KEY128 and @a TEST_IV. +const std::string TEST_CIPHERTEXT128{ + "40d7b2a1e750f8e3d731424f7536b4a113b77ca248c3356075d3a9cfedcd7fae84ea2d7983e86f9581833f"}; +/// Ciphertext from encrypting @a TEST_STR with @a TEST_KEY256 and @a TEST_IV. +const std::string TEST_CIPHERTEXT256{ + "f940a05f273315d1fae75e4fc68f401848051231d7c20319ea7efaa7eb6166b56fcfb790056fc84a912050"}; + +// Helper function to convert hex string to byte vector. +static std::vector hexStringToBytes(const std::string& hex) { + std::vector bytes; + decodeHex(hex, bytes); + return bytes; +} + +// Helper function to convert byte vector to hex string. +static std::string bytesToHexString(const std::vector& bytes) { + std::string hexString; + encodeHex(bytes, hexString); + return hexString; +} + +// Helper function to represent string as a byte vector. +static std::vector stringToBytes(const std::string& str) { + std::vector bytes( + reinterpret_cast(str.data()), + reinterpret_cast(str.data()) + str.size()); + return bytes; +} + +// Helper function to represent byte vector as a string. +static std::string bytesToString(const std::vector& bytes) { + std::string result( + reinterpret_cast(bytes.data()), reinterpret_cast(bytes.data()) + bytes.size()); + return result; +} + +/// Test parameter type. +/// +/// Tests take algorithm type, key, tag, and ciphertext as input. +typedef std::tuple TestParams; + +/** + * Test fixture for generic parameter error tests. + * + * Parameters include: algorithm type, used key, expected tag, and expected ciphertext. + * @private + */ +class AeAdCodecTest : public testing::TestWithParam { +public: + /// Allocates key factory. + static void SetUpTestCase() { + c_keyFactory = OpenSslKeyFactory::create(); + } + + /// Releases key factory. + static void TearDownTestCase() { + c_keyFactory.reset(); + } + + // Key factory used in some tests. + static std::shared_ptr c_keyFactory; +}; +std::shared_ptr AeAdCodecTest::c_keyFactory; + +TEST_P(AeAdCodecTest, test_encodeNoInit) { + AlgorithmType algorithmType; + std::tie(algorithmType, std::ignore, std::ignore, std::ignore) = GetParam(); + auto encoder = OpenSslCryptoCodec::createEncoder(algorithmType); + ASSERT_NE(nullptr, encoder); + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_FALSE(encoder->process({}, encoded)); +} + +TEST_P(AeAdCodecTest, test_decodeNoInit) { + AlgorithmType algorithmType; + std::tie(algorithmType, std::ignore, std::ignore, std::ignore) = GetParam(); + auto decoder = OpenSslCryptoCodec::createDecoder(algorithmType); + ASSERT_NE(nullptr, decoder); + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_FALSE(decoder->process({}, decoded)); +} + +TEST_P(AeAdCodecTest, test_encodeFinalizeNoInit) { + AlgorithmType algorithmType; + std::tie(algorithmType, std::ignore, std::ignore, std::ignore) = GetParam(); + auto encoder = OpenSslCryptoCodec::createEncoder(algorithmType); + ASSERT_NE(nullptr, encoder); + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_FALSE(encoder->finalize(encoded)); +} + +TEST_P(AeAdCodecTest, test_decodeFinalizeNoInit) { + AlgorithmType algorithmType; + std::tie(algorithmType, std::ignore, std::ignore, std::ignore) = GetParam(); + auto decoder = OpenSslCryptoCodec::createDecoder(algorithmType); + ASSERT_NE(nullptr, decoder); + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_FALSE(decoder->finalize(decoded)); +} + +TEST_P(AeAdCodecTest, test_encodeDecodeEmpty) { + AlgorithmType algorithmType; + std::tie(algorithmType, std::ignore, std::ignore, std::ignore) = GetParam(); + auto encoder = OpenSslCryptoCodec::createEncoder(algorithmType); + ASSERT_NE(nullptr, encoder); + auto decoder = OpenSslCryptoCodec::createDecoder(algorithmType); + ASSERT_NE(nullptr, decoder); + CryptoCodecInterface::Key key; + CryptoCodecInterface::IV iv; + ASSERT_TRUE(c_keyFactory->generateKey(algorithmType, key)); + ASSERT_TRUE(c_keyFactory->generateIV(algorithmType, iv)); + ASSERT_TRUE(encoder->init(key, iv)); + ASSERT_TRUE(decoder->init(key, iv)); + + CryptoCodecInterface::DataBlock plaintext; + CryptoCodecInterface::DataBlock ciphertext; + CryptoCodecInterface::DataBlock plaintext2; + + ASSERT_TRUE(encoder->process(plaintext, ciphertext)); + ASSERT_TRUE(encoder->finalize(ciphertext)); + + CryptoCodecInterface::Tag tag; + ASSERT_TRUE(encoder->getTag(tag)); + ASSERT_EQ(16u, tag.size()); + + ASSERT_TRUE(decoder->process(ciphertext, plaintext2)); + ASSERT_TRUE(decoder->setTag(tag)); + ASSERT_TRUE(decoder->finalize(plaintext2)); + + ASSERT_TRUE(plaintext2.empty()); +} + +TEST_P(AeAdCodecTest, test_encodeDecodeNonEmpty) { + AlgorithmType algorithmType; + std::tie(algorithmType, std::ignore, std::ignore, std::ignore) = GetParam(); + auto encoder = OpenSslCryptoCodec::createEncoder(algorithmType); + ASSERT_NE(nullptr, encoder); + auto decoder = OpenSslCryptoCodec::createDecoder(algorithmType); + ASSERT_NE(nullptr, decoder); + CryptoCodecInterface::Key key; + CryptoCodecInterface::IV iv; + ASSERT_TRUE(c_keyFactory->generateKey(algorithmType, key)); + ASSERT_TRUE(c_keyFactory->generateIV(algorithmType, iv)); + ASSERT_TRUE(encoder->init(key, iv)); + ASSERT_TRUE(decoder->init(key, iv)); + + CryptoCodecInterface::DataBlock plaintext = stringToBytes(TEST_STR); + CryptoCodecInterface::DataBlock ciphertext; + CryptoCodecInterface::DataBlock plaintext2; + CryptoCodecInterface::Tag tag; + + ASSERT_TRUE(encoder->processAAD(stringToBytes(TEST_AD))); + ASSERT_TRUE(encoder->process(plaintext, ciphertext)); + ASSERT_TRUE(encoder->finalize(ciphertext)); + ASSERT_TRUE(encoder->getTag(tag)); + ASSERT_FALSE(ciphertext.empty()); + + ASSERT_TRUE(decoder->processAAD(stringToBytes(TEST_AD))); + ASSERT_TRUE(decoder->process(ciphertext, plaintext2)); + ASSERT_TRUE(decoder->setTag(tag)); + ASSERT_TRUE(decoder->finalize(plaintext2)); + + ASSERT_EQ(TEST_STR, bytesToString(plaintext2)); +} + +TEST_P(AeAdCodecTest, test_encodeAadAfterProcess) { + AlgorithmType algorithmType; + CryptoCodecInterface::Key key; + std::tie(algorithmType, key, std::ignore, std::ignore) = GetParam(); + auto encoder = OpenSslCryptoCodec::createEncoder(algorithmType); + ASSERT_NE(nullptr, encoder); + + ASSERT_TRUE(encoder->init(key, TEST_IV)); + + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_TRUE(encoder->process(stringToBytes(TEST_STR), encoded)); + ASSERT_FALSE(encoder->processAAD(stringToBytes(TEST_AD))); +} + +TEST_P(AeAdCodecTest, test_decodeAadAfterProcess) { + AlgorithmType algorithmType; + CryptoCodecInterface::Key key; + std::string ciphertext; + std::tie(algorithmType, key, std::ignore, ciphertext) = GetParam(); + auto decoder = OpenSslCryptoCodec::createDecoder(algorithmType); + ASSERT_NE(nullptr, decoder); + + ASSERT_TRUE(decoder->init(key, TEST_IV)); + + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_TRUE(decoder->process(stringToBytes(ciphertext), decoded)); + ASSERT_FALSE(decoder->processAAD(stringToBytes(TEST_AD))); +} + +TEST_P(AeAdCodecTest, test_encodeTestData) { + AlgorithmType algorithmType; + CryptoCodecInterface::Key key; + std::string tag; + std::string ciphertext; + std::tie(algorithmType, key, tag, ciphertext) = GetParam(); + auto encoder = OpenSslCryptoCodec::createEncoder(algorithmType); + ASSERT_NE(nullptr, encoder); + + ASSERT_TRUE(encoder->init(key, TEST_IV)); + ASSERT_TRUE(encoder->processAAD(stringToBytes(TEST_AD))); + + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_TRUE(encoder->process(stringToBytes(TEST_STR), encoded)); + ASSERT_TRUE(encoder->finalize(encoded)); + ASSERT_EQ(ciphertext, bytesToHexString(encoded)); + + OpenSslCryptoCodec::Tag tag2; + ASSERT_TRUE(encoder->getTag(tag2)); + ASSERT_EQ(tag, bytesToHexString(tag2)); +} + +TEST_P(AeAdCodecTest, test_decodeTestData) { + AlgorithmType algorithmType; + CryptoCodecInterface::Key key; + std::string tag; + std::string ciphertext; + std::tie(algorithmType, key, tag, ciphertext) = GetParam(); + auto decoder = OpenSslCryptoCodec::createDecoder(algorithmType); + ASSERT_NE(nullptr, decoder); + + ASSERT_TRUE(decoder->init(key, TEST_IV)); + ASSERT_TRUE(decoder->processAAD(stringToBytes(TEST_AD))); + + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_TRUE(decoder->process(hexStringToBytes(ciphertext), decoded)); + ASSERT_TRUE(decoder->setTag(hexStringToBytes(tag))); + ASSERT_TRUE(decoder->finalize(decoded)); + ASSERT_EQ(TEST_STR, bytesToString(decoded)); +} + +TEST_P(AeAdCodecTest, test_decodeStringWrongTag) { + AlgorithmType algorithmType; + CryptoCodecInterface::Key key; + std::string ciphertext; + std::tie(algorithmType, key, std::ignore, ciphertext) = GetParam(); + auto decoder = OpenSslCryptoCodec::createDecoder(algorithmType); + ASSERT_NE(nullptr, decoder); + + ASSERT_TRUE(decoder->init(key, TEST_IV)); + ASSERT_TRUE(decoder->processAAD(stringToBytes(TEST_AD))); + + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_TRUE(decoder->process(hexStringToBytes(ciphertext), decoded)); + ASSERT_TRUE(decoder->setTag(hexStringToBytes(TEST_TAGBAD))); + ASSERT_FALSE(decoder->finalize(decoded)); +} + +TEST_P(AeAdCodecTest, test_decodeStringNoTag) { + AlgorithmType algorithmType; + CryptoCodecInterface::Key key; + std::string ciphertext; + std::tie(algorithmType, key, std::ignore, ciphertext) = GetParam(); + auto decoder = OpenSslCryptoCodec::createDecoder(algorithmType); + ASSERT_NE(nullptr, decoder); + + ASSERT_TRUE(decoder->init(key, TEST_IV)); + ASSERT_TRUE(decoder->processAAD(stringToBytes(TEST_AD))); + + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_TRUE(decoder->process(hexStringToBytes(ciphertext), decoded)); + ASSERT_FALSE(decoder->finalize(decoded)); +} + +INSTANTIATE_TEST_CASE_P( + OpenSslCryptoCodecAEADTest, + AeAdCodecTest, + Values( + TestParams{AlgorithmType::AES_256_GCM, TEST_KEY256, TEST_TAG256, TEST_CIPHERTEXT256}, + TestParams{AlgorithmType::AES_128_GCM, TEST_KEY128, TEST_TAG128, TEST_CIPHERTEXT128})); + +} // namespace test +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/test/OpenSslCryptoCodecTest.cpp b/core/Crypto/acsdkCrypto/test/OpenSslCryptoCodecTest.cpp new file mode 100644 index 0000000000..4e433dbea0 --- /dev/null +++ b/core/Crypto/acsdkCrypto/test/OpenSslCryptoCodecTest.cpp @@ -0,0 +1,351 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkCrypto { +namespace test { + +using namespace ::testing; +using namespace alexaClientSDK::acsdkCryptoInterfaces; +using namespace alexaClientSDK::acsdkCodecUtils; + +const std::string TEST_STR = "The quick brown fox jumps over the lazy dog"; // 43 characters +const std::string TEST_STR2 = "The quick brown fox jumps over the lazy dog....."; // 48 characters + +/// @private +static constexpr AlgorithmType BAD_ALGORITHM_TYPE = static_cast(0); + +static std::vector hexStringToBytes(const std::string& hex) { + std::vector bytes; + decodeHex(hex, bytes); + return bytes; +} + +static std::string bytesToHexString(const std::vector& bytes) { + std::string result; + encodeHex(bytes, result); + return result; +} + +static std::vector stringToBytes(const std::string& str) { + std::vector bytes((const unsigned char*)str.data(), (const unsigned char*)str.data() + str.size()); + return bytes; +} + +const OpenSslCryptoCodec::Key ZERO_KEY = + hexStringToBytes("0000000000000000000000000000000000000000000000000000000000000000"); +/// Zero IV. +const OpenSslCryptoCodec::IV IV0 = hexStringToBytes("00000000000000000000000000000000"); +/// Random IV. +const OpenSslCryptoCodec::IV IVR = hexStringToBytes("0123456789abcdef0123456789abcdef"); +/// Bad IV. +const OpenSslCryptoCodec::IV IVB = hexStringToBytes("0123456789"); +/// Test string encrypted with AES-256-CBC-PAD with IVR. +const std::string AES256CBCPAD_CIPHERTEXT_IVR = + "0df523194582f51a623a9ad0395d5ed62f7880b70e14818f7648fb01999bca27f955aac7e15dff71944d952de2ca9e99"; +/// Test string encrypted with AES-256-CBC-PAD with IV0. +const std::string AES256CBCPAD_CIPHERTEXT_IV0 = + "6db0c67c0cf728b37640f65f0e7db88f5cd217822b08cbad8817dda0f19476684d05a1b1c6a7b5184510b3a0e43b552a"; +/// Test string 2 encrypted with AES-256-CBC with IVR. +const std::string AES256CBC_CIPHERTEXT_IVR = + "0df523194582f51a623a9ad0395d5ed62f7880b70e14818f7648fb01999bca27cd24efc62c1b96e0c14b661d4ef5cdf9"; +/// Test string 2 encrypted with AES-256-CBC with IV0. +const std::string AES256CBC_CIPHERTEXT_IV0 = + "6db0c67c0cf728b37640f65f0e7db88f5cd217822b08cbad8817dda0f194766832570123a3c6dd75c19fd304f9321b6f"; + +static std::string bytesToString(const std::vector& bytes) { + std::string result(bytes.data(), bytes.data() + bytes.size()); + return result; +} + +TEST(OpenSslCryptoCodecTest, test_badAlgorithmEncoder) { + auto encoder = OpenSslCryptoCodec::createEncoder(BAD_ALGORITHM_TYPE); + ASSERT_EQ(nullptr, encoder); +} + +TEST(OpenSslCryptoCodecTest, test_badAlgorithmDecoder) { + auto decoder = OpenSslCryptoCodec::createDecoder(BAD_ALGORITHM_TYPE); + ASSERT_EQ(nullptr, decoder); +} + +TEST(OpenSslCryptoCodecTest, test_aes256CbcEncoder) { + auto encoder = OpenSslCryptoCodec::createEncoder(AlgorithmType::AES_256_CBC); + ASSERT_NE(nullptr, encoder); +} + +TEST(OpenSslCryptoCodecTest, test_aes256CbcPadEncoder) { + auto encoder = OpenSslCryptoCodec::createEncoder(AlgorithmType::AES_256_CBC_PAD); + ASSERT_NE(nullptr, encoder); +} + +/** + * Test fixture for generic parameter error tests. + * + * @private + */ +class GenericCodecTest : public testing::TestWithParam { +public: + /// Allocates key factory. + static void SetUpTestCase() { + c_keyFactory = OpenSslKeyFactory::create(); + } + + /// Releases key factory. + static void TearDownTestCase() { + c_keyFactory.reset(); + } + + // Key factory used in tests. + static std::shared_ptr c_keyFactory; +}; +std::shared_ptr GenericCodecTest::c_keyFactory; + +TEST_P(GenericCodecTest, test_encodeNoInit) { + auto encoder = OpenSslCryptoCodec::createEncoder(GetParam()); + ASSERT_NE(nullptr, encoder); + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_FALSE(encoder->process({}, encoded)); +} + +TEST_P(GenericCodecTest, test_decodeNoInit) { + auto decoder = OpenSslCryptoCodec::createDecoder(GetParam()); + ASSERT_NE(nullptr, decoder); + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_FALSE(decoder->process({}, decoded)); +} + +TEST_P(GenericCodecTest, test_encodeFinalizeNoInit) { + auto encoder = OpenSslCryptoCodec::createEncoder(GetParam()); + ASSERT_NE(nullptr, encoder); + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_FALSE(encoder->finalize(encoded)); +} + +TEST_P(GenericCodecTest, test_decodeFinalizeNoInit) { + auto decoder = OpenSslCryptoCodec::createDecoder(GetParam()); + ASSERT_NE(nullptr, decoder); + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_FALSE(decoder->finalize(decoded)); +} + +TEST_P(GenericCodecTest, test_encoderInitBadIV) { + auto encoder = OpenSslCryptoCodec::createEncoder(GetParam()); + ASSERT_NE(nullptr, encoder); + CryptoCodecInterface::Key key; + ASSERT_TRUE(c_keyFactory->generateKey(GetParam(), key)); + ASSERT_FALSE(encoder->init(key, IVB)); +} + +TEST_P(GenericCodecTest, test_decoderInitBadIV) { + auto decoder = OpenSslCryptoCodec::createDecoder(GetParam()); + ASSERT_NE(nullptr, decoder); + CryptoCodecInterface::Key key; + ASSERT_TRUE(c_keyFactory->generateKey(GetParam(), key)); + ASSERT_FALSE(decoder->init(key, IVB)); +} + +TEST_P(GenericCodecTest, test_encoderInitBadKey) { + auto encoder = OpenSslCryptoCodec::createEncoder(GetParam()); + ASSERT_NE(nullptr, encoder); + CryptoCodecInterface::IV iv; + ASSERT_TRUE(c_keyFactory->generateIV(GetParam(), iv)); + ASSERT_FALSE(encoder->init(IVB, iv)); +} + +TEST_P(GenericCodecTest, test_decoderInitBadKey) { + auto decoder = OpenSslCryptoCodec::createDecoder(GetParam()); + ASSERT_NE(nullptr, decoder); + CryptoCodecInterface::IV iv; + ASSERT_TRUE(c_keyFactory->generateIV(GetParam(), iv)); + ASSERT_FALSE(decoder->init(IVB, iv)); +} + +TEST_P(GenericCodecTest, test_encodeDecodeEmpty) { + auto encoder = OpenSslCryptoCodec::createEncoder(GetParam()); + ASSERT_NE(nullptr, encoder); + auto decoder = OpenSslCryptoCodec::createDecoder(GetParam()); + ASSERT_NE(nullptr, decoder); + CryptoCodecInterface::Key key; + CryptoCodecInterface::IV iv; + ASSERT_TRUE(c_keyFactory->generateKey(GetParam(), key)); + ASSERT_TRUE(c_keyFactory->generateIV(GetParam(), iv)); + ASSERT_TRUE(encoder->init(key, iv)); + ASSERT_TRUE(decoder->init(key, iv)); + + CryptoCodecInterface::DataBlock plaintext; + CryptoCodecInterface::DataBlock ciphertext; + CryptoCodecInterface::DataBlock plaintext2; + + ASSERT_TRUE(encoder->process(plaintext, ciphertext)); + ASSERT_TRUE(encoder->finalize(ciphertext)); + + ASSERT_TRUE(decoder->process(ciphertext, plaintext2)); + ASSERT_TRUE(decoder->finalize(plaintext2)); + + ASSERT_TRUE(plaintext2.empty()); +} + +TEST_P(GenericCodecTest, test_encodeDecodeNonEmpty) { + auto encoder = OpenSslCryptoCodec::createEncoder(GetParam()); + ASSERT_NE(nullptr, encoder); + auto decoder = OpenSslCryptoCodec::createDecoder(GetParam()); + ASSERT_NE(nullptr, decoder); + CryptoCodecInterface::Key key; + CryptoCodecInterface::IV iv; + ASSERT_TRUE(c_keyFactory->generateKey(GetParam(), key)); + ASSERT_TRUE(c_keyFactory->generateIV(GetParam(), iv)); + ASSERT_TRUE(encoder->init(key, iv)); + ASSERT_TRUE(decoder->init(key, iv)); + + CryptoCodecInterface::DataBlock plaintext; + plaintext.assign(TEST_STR2.data(), TEST_STR2.data() + TEST_STR2.size()); + CryptoCodecInterface::DataBlock ciphertext; + CryptoCodecInterface::DataBlock plaintext2; + + ASSERT_TRUE(encoder->process(plaintext, ciphertext)); + ASSERT_TRUE(encoder->finalize(ciphertext)); + ASSERT_FALSE(ciphertext.empty()); + ASSERT_EQ(0u, ciphertext.size() % 16); + + ASSERT_TRUE(decoder->process(ciphertext, plaintext2)); + ASSERT_TRUE(decoder->finalize(plaintext2)); + + ASSERT_EQ(plaintext, plaintext2); +} + +TEST_P(GenericCodecTest, test_decodeEmptyError) { + auto decoder = OpenSslCryptoCodec::createDecoder(GetParam()); + CryptoCodecInterface::Key key; + CryptoCodecInterface::IV iv; + ASSERT_TRUE(c_keyFactory->generateKey(GetParam(), key)); + ASSERT_TRUE(c_keyFactory->generateIV(GetParam(), iv)); + ASSERT_TRUE(decoder->init(key, iv)); + + CryptoCodecInterface::DataBlock ciphertext; + CryptoCodecInterface::DataBlock plaintext; + + ASSERT_TRUE(decoder->process(ciphertext, plaintext)); + ASSERT_TRUE(plaintext.empty()); + + switch (GetParam()) { + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_128_CBC: + ASSERT_TRUE(decoder->finalize(plaintext)); + ASSERT_TRUE(plaintext.empty()); + break; + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_128_CBC_PAD: + ASSERT_FALSE(decoder->finalize(plaintext)); + break; + default: + ASSERT_FALSE(true); + break; + } +} + +INSTANTIATE_TEST_CASE_P( + OpenSslCryptoCodecTest, + GenericCodecTest, + Values( + AlgorithmType::AES_256_CBC, + AlgorithmType::AES_256_CBC_PAD, + AlgorithmType::AES_128_CBC, + AlgorithmType::AES_128_CBC_PAD)); + +TEST(OpenSslCryptoCodecTest, test_aes256CbcPadEncodeEmpty) { + auto encoder = OpenSslCryptoCodec::createEncoder(AlgorithmType::AES_256_CBC_PAD); + ASSERT_NE(nullptr, encoder); + ASSERT_TRUE(encoder->init(ZERO_KEY, IV0)); + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_TRUE(encoder->process({}, encoded)); + ASSERT_TRUE(encoded.empty()); + ASSERT_TRUE(encoder->finalize(encoded)); + ASSERT_EQ(16u, encoded.size()); + ASSERT_EQ("1f788fe6d86c317549697fbf0c07fa43", bytesToHexString(encoded)); +} + +TEST(OpenSslCryptoCodecTest, test_aes256CbcEncodeZeroIV) { + auto encoder = OpenSslCryptoCodec::createEncoder(AlgorithmType::AES_256_CBC); + ASSERT_NE(nullptr, encoder); + ASSERT_TRUE(encoder->init(ZERO_KEY, IV0)); + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_TRUE(encoder->process(stringToBytes(TEST_STR2), encoded)); + ASSERT_TRUE(encoder->finalize(encoded)); + ASSERT_EQ(AES256CBC_CIPHERTEXT_IV0, bytesToHexString(encoded)); +} + +TEST(OpenSslCryptoCodecTest, test_aes256CbcPadEncodeZeroIV) { + auto encoder = OpenSslCryptoCodec::createEncoder(AlgorithmType::AES_256_CBC_PAD); + ASSERT_NE(nullptr, encoder); + ASSERT_TRUE(encoder->init(ZERO_KEY, IV0)); + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_TRUE(encoder->process(stringToBytes(TEST_STR), encoded)); + ASSERT_TRUE(encoder->finalize(encoded)); + ASSERT_EQ(AES256CBCPAD_CIPHERTEXT_IV0, bytesToHexString(encoded)); +} + +TEST(OpenSslCryptoCodecTest, test_aes256CbcEncodeNonEmptyIV) { + auto encoder = OpenSslCryptoCodec::createEncoder(AlgorithmType::AES_256_CBC); + ASSERT_NE(nullptr, encoder); + ASSERT_TRUE(encoder->init(ZERO_KEY, IVR)); + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_TRUE(encoder->process(stringToBytes(TEST_STR2), encoded)); + ASSERT_TRUE(encoder->finalize(encoded)); + ASSERT_EQ(AES256CBC_CIPHERTEXT_IVR, bytesToHexString(encoded)); +} + +TEST(OpenSslCryptoCodecTest, test_aes256CbcPadEncodeNonEmptyIV) { + auto encoder = OpenSslCryptoCodec::createEncoder(AlgorithmType::AES_256_CBC_PAD); + ASSERT_NE(nullptr, encoder); + ASSERT_EQ(true, encoder->init(ZERO_KEY, IVR)); + OpenSslCryptoCodec::DataBlock encoded; + ASSERT_TRUE(encoder->process(stringToBytes(TEST_STR), encoded)); + ASSERT_TRUE(encoder->finalize(encoded)); + ASSERT_EQ(AES256CBCPAD_CIPHERTEXT_IVR, bytesToHexString(encoded)); +} + +TEST(OpenSslCryptoCodecTest, test_aes256CbcPadDecodeEmptyError) { + auto decoder = OpenSslCryptoCodec::createDecoder(AlgorithmType::AES_256_CBC_PAD); + ASSERT_NE(nullptr, decoder); + ASSERT_TRUE(decoder->init(ZERO_KEY, IV0)); + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_TRUE(decoder->process({}, decoded)); + ASSERT_TRUE(decoded.empty()); + // We expect an error. + ASSERT_FALSE(decoder->finalize(decoded)); +} + +TEST(OpenSslCryptoCodecTest, test_aes256CbcDecodeString) { + auto decoder = OpenSslCryptoCodec::createDecoder(AlgorithmType::AES_256_CBC_PAD); + ASSERT_NE(nullptr, decoder); + ASSERT_TRUE(decoder->init(ZERO_KEY, IVR)); + OpenSslCryptoCodec::DataBlock decoded; + ASSERT_TRUE(decoder->process(hexStringToBytes(AES256CBCPAD_CIPHERTEXT_IVR), decoded)); + ASSERT_TRUE(decoder->finalize(decoded)); + + ASSERT_EQ(TEST_STR, bytesToString(decoded)); +} + +} // namespace test +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/test/OpenSslCryptoFactoryTest.cpp b/core/Crypto/acsdkCrypto/test/OpenSslCryptoFactoryTest.cpp new file mode 100644 index 0000000000..b6d6aee26d --- /dev/null +++ b/core/Crypto/acsdkCrypto/test/OpenSslCryptoFactoryTest.cpp @@ -0,0 +1,121 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkCrypto { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; +using namespace ::alexaClientSDK::acsdkCodecUtils; + +/// Test IV for AES. +/// @private +static const std::string TEST_IV_HEX{"19100e18da95041e1c373806ba809254"}; + +/// Test key for AES 256. +/// @private +static const std::string TEST_KEY_HEX{"9afdf8f0d042299300c9dc50e7363c34ed5f4a78f4066163574e7d2641365855"}; + +/// Test plaintext to encrypt. +/// @private +static const std::string TEST_PLAINTEXT{"some plaintext value"}; + +/// Encrypted plaintext in hex form. This is \a TEST_PLAINTEXT value encrypted with \a TEST_KEY_HEX key and +/// \a TEST_IV_HEX IV usign AES-256-CBC with PKCS#7 padding. +/// @private +static const std::string TEST_CIPHERTEXT{"f3fa1a4bef50e2f55f3caa49fad568fe1c33fe8c7a66aadd6527c15dffc0a77a"}; + +/// Bad crypto algorithm type +/// @private +static constexpr AlgorithmType BAD_ALGORITHM_TYPE = static_cast(0); + +/// Bad digest type +/// @private +static constexpr DigestType BAD_DIGEST_TYPE = static_cast(0); + +TEST(OpenSslCryptoFactoryTest, test_createNotNull) { + auto factory = OpenSslCryptoFactory::create(); + ASSERT_NE(nullptr, factory); +} + +TEST(OpenSslCryptoFactoryTest, test_createTools) { + auto factory = OpenSslCryptoFactory::create(); + ASSERT_NE(nullptr, factory->createDecoder(AlgorithmType::AES_256_CBC)); + ASSERT_NE(nullptr, factory->createEncoder(AlgorithmType::AES_256_CBC)); + ASSERT_NE(nullptr, factory->createDecoder(AlgorithmType::AES_256_CBC_PAD)); + ASSERT_NE(nullptr, factory->createEncoder(AlgorithmType::AES_256_CBC_PAD)); + ASSERT_NE(nullptr, factory->createDecoder(AlgorithmType::AES_128_CBC)); + ASSERT_NE(nullptr, factory->createEncoder(AlgorithmType::AES_128_CBC)); + ASSERT_NE(nullptr, factory->createDecoder(AlgorithmType::AES_256_CBC_PAD)); + ASSERT_NE(nullptr, factory->createEncoder(AlgorithmType::AES_256_CBC_PAD)); + ASSERT_NE(nullptr, factory->createDecoder(AlgorithmType::AES_128_GCM)); + ASSERT_NE(nullptr, factory->createEncoder(AlgorithmType::AES_128_GCM)); + ASSERT_NE(nullptr, factory->createDecoder(AlgorithmType::AES_256_GCM)); + ASSERT_NE(nullptr, factory->createEncoder(AlgorithmType::AES_256_GCM)); + ASSERT_NE(nullptr, factory->getKeyFactory()); + ASSERT_NE(nullptr, factory->createDigest(DigestType::SHA_256)); +} + +TEST(OpenSslCryptoFactoryTest, test_createUnknownTools) { + auto factory = OpenSslCryptoFactory::create(); + ASSERT_EQ(nullptr, factory->createDecoder(BAD_ALGORITHM_TYPE)); + ASSERT_EQ(nullptr, factory->createEncoder(BAD_ALGORITHM_TYPE)); + ASSERT_EQ(nullptr, factory->createDigest(BAD_DIGEST_TYPE)); +} + +TEST(OpenSslCryptoFactoryTest, test_encryptDecrypt) { + auto cryptoFactory = createCryptoFactory(); + ASSERT_NE(nullptr, cryptoFactory); + + CryptoCodecInterface::IV iv; + ASSERT_TRUE(decodeHex(TEST_IV_HEX, iv)); + CryptoCodecInterface::Key key; + ASSERT_TRUE(decodeHex(TEST_KEY_HEX, key)); + + std::string text{TEST_PLAINTEXT}; + CryptoCodecInterface::DataBlock plaintext; + plaintext.assign(text.data(), text.data() + text.size()); + + CryptoCodecInterface::DataBlock ciphertext; + auto encoder = cryptoFactory->createEncoder(AlgorithmType::AES_256_CBC_PAD); + ASSERT_NE(nullptr, encoder); + ASSERT_TRUE(encoder->init(key, iv)); + ASSERT_TRUE(encoder->process(plaintext, ciphertext)); + ASSERT_TRUE(encoder->finalize(ciphertext)); + + std::string ciphertextStr; + ASSERT_TRUE(encodeHex(ciphertext, ciphertextStr)); + ASSERT_EQ(TEST_CIPHERTEXT, ciphertextStr); + + auto decoder = cryptoFactory->createDecoder(AlgorithmType::AES_256_CBC_PAD); + ASSERT_NE(nullptr, decoder); + CryptoCodecInterface::DataBlock plaintext2; + ASSERT_TRUE(decoder->init(key, iv)); + ASSERT_TRUE(decoder->process(ciphertext, plaintext2)); + ASSERT_TRUE(decoder->finalize(plaintext2)); + + ASSERT_EQ(plaintext, plaintext2); +} + +} // namespace test +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/test/OpenSslDigestTest.cpp b/core/Crypto/acsdkCrypto/test/OpenSslDigestTest.cpp new file mode 100644 index 0000000000..440430f9f9 --- /dev/null +++ b/core/Crypto/acsdkCrypto/test/OpenSslDigestTest.cpp @@ -0,0 +1,119 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCrypto { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; +using namespace ::alexaClientSDK::acsdkCodecUtils; + +static const char TEST_STR[] = "The quick brown fox jumps over the lazy dog"; +static const std::vector TEST_DATA{TEST_STR, TEST_STR + sizeof(TEST_STR) - 1}; +static const uint8_t TEST_UINT8 = 1; +static const uint16_t TEST_UINT16 = 1; +static const uint32_t TEST_UINT32 = 1; +static const uint64_t TEST_UINT64 = 1; +static const std::string SHA256_EMPTY_HEX{"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}; +static const std::string SHA256_TEST_DATA_HEX{"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"}; +// To verify: echo 01 | xxd -r -p | openssl dgst -sha256 +static const std::string SHA256_UINT8_HEX{"4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"}; +// To verify: echo 0001 | xxd -r -p | openssl dgst -sha256 +static const std::string SHA256_UNT16_HEX{"b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2"}; +// To verify: echo 00000001 | xxd -r -p | openssl dgst -sha256 +static const std::string SHA256_UINT32_HEX{"b40711a88c7039756fb8a73827eabe2c0fe5a0346ca7e0a104adc0fc764f528d"}; +// To verify: echo 0000000000000001 | xxd -r -p | openssl dgst -sha256 +static const std::string SHA256_UINT64_HEX{"cd2662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a50"}; +static constexpr DigestType BAD_DIGEST_TYPE = static_cast(0); + +TEST(OpenSslDigestTest, test_createSHA256) { + auto digest = OpenSslDigest::create(DigestType::SHA_256); + ASSERT_NE(nullptr, digest); +} + +TEST(OpenSslDigestTest, test_createInvalid) { + auto digest = OpenSslDigest::create(BAD_DIGEST_TYPE); + ASSERT_EQ(nullptr, digest); +} + +TEST(OpenSslDigestTest, test_emptySha256Digest) { + auto digest = OpenSslDigest::create(DigestType::SHA_256); + digest->process({}); + OpenSslDigest::DataBlock res; + ASSERT_TRUE(digest->finalize(res)); + std::string hex; + ASSERT_TRUE(encodeHex(res, hex)); + ASSERT_EQ(SHA256_EMPTY_HEX, hex); +} + +TEST(OpenSslDigestTest, test_digest) { + auto digest = OpenSslDigest::create(DigestType::SHA_256); + digest->process(TEST_DATA); + OpenSslDigest::DataBlock res; + ASSERT_TRUE(digest->finalize(res)); + std::string hex; + ASSERT_TRUE(encodeHex(res, hex)); + ASSERT_EQ(SHA256_TEST_DATA_HEX, hex); +} + +TEST(OpenSslDigestTest, test_digestUInt8) { + auto digest = OpenSslDigest::create(DigestType::SHA_256); + ASSERT_TRUE(digest->processUInt8(TEST_UINT8)); + OpenSslDigest::DataBlock res; + ASSERT_TRUE(digest->finalize(res)); + std::string hex; + ASSERT_TRUE(encodeHex(res, hex)); + ASSERT_EQ(SHA256_UINT8_HEX, hex); +} + +TEST(OpenSslDigestTest, test_digestUInt16) { + auto digest = OpenSslDigest::create(DigestType::SHA_256); + ASSERT_TRUE(digest->processUInt16(TEST_UINT16)); + OpenSslDigest::DataBlock res; + ASSERT_TRUE(digest->finalize(res)); + std::string hex; + ASSERT_TRUE(encodeHex(res, hex)); + ASSERT_EQ(SHA256_UNT16_HEX, hex); +} + +TEST(OpenSslDigestTest, test_digestUInt32) { + auto digest = OpenSslDigest::create(DigestType::SHA_256); + ASSERT_TRUE(digest->processUInt32(TEST_UINT32)); + OpenSslDigest::DataBlock res; + ASSERT_TRUE(digest->finalize(res)); + std::string hex; + ASSERT_TRUE(encodeHex(res, hex)); + ASSERT_EQ(SHA256_UINT32_HEX, hex); +} + +TEST(OpenSslDigestTest, test_digestUInt64) { + auto digest = OpenSslDigest::create(DigestType::SHA_256); + ASSERT_TRUE(digest->processUInt64(TEST_UINT64)); + OpenSslDigest::DataBlock res; + ASSERT_TRUE(digest->finalize(res)); + std::string hex; + ASSERT_TRUE(encodeHex(res, hex)); + ASSERT_EQ(SHA256_UINT64_HEX, hex); +} + +} // namespace test +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/test/OpenSslKeyFactoryTest.cpp b/core/Crypto/acsdkCrypto/test/OpenSslKeyFactoryTest.cpp new file mode 100644 index 0000000000..7293528462 --- /dev/null +++ b/core/Crypto/acsdkCrypto/test/OpenSslKeyFactoryTest.cpp @@ -0,0 +1,124 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCrypto { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +static constexpr size_t AES_256_KEY_SIZE = 32u; // 256 bits +static constexpr size_t AES_128_KEY_SIZE = 16u; // 128 bits +static constexpr size_t AES_CBC_IV_SIZE = 16u; // 128 bits +static constexpr size_t AES_GCM_IV_SIZE = 12u; // 96 bits +static constexpr AlgorithmType BAD_ALGORITHM_TYPE = static_cast(0); + +/** + * Test fixture for generic parameter error tests. + * + * @private + */ +class KeyFactoryTest : public TestWithParam {}; + +TEST_P(KeyFactoryTest, testCreateUniqueKeys) { + auto factory = OpenSslKeyFactory::create(); + ASSERT_NE(nullptr, factory); + KeyFactoryInterface::Key key1; + KeyFactoryInterface::Key key2; + ASSERT_TRUE(factory->generateKey(GetParam(), key1)); + ASSERT_TRUE(factory->generateKey(GetParam(), key2)); + size_t expectedSize = 0; + switch (GetParam()) { + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_256_GCM: + expectedSize = AES_256_KEY_SIZE; + break; + case AlgorithmType::AES_128_CBC_PAD: + case AlgorithmType::AES_128_CBC: + case AlgorithmType::AES_128_GCM: + expectedSize = AES_128_KEY_SIZE; + break; + default: + ASSERT_TRUE(false); + break; + } + ASSERT_EQ(expectedSize, key1.size()); + ASSERT_EQ(expectedSize, key2.size()); + ASSERT_NE(key1, key2); +} + +TEST_P(KeyFactoryTest, testCreateUniqueIVs) { + auto factory = OpenSslKeyFactory::create(); + ASSERT_NE(nullptr, factory); + KeyFactoryInterface::IV iv1; + KeyFactoryInterface::IV iv2; + ASSERT_TRUE(factory->generateIV(GetParam(), iv1)); + ASSERT_TRUE(factory->generateIV(GetParam(), iv2)); + size_t expectedSize = 0; + switch (GetParam()) { + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_128_CBC_PAD: + case AlgorithmType::AES_128_CBC: + expectedSize = AES_CBC_IV_SIZE; + break; + case AlgorithmType::AES_256_GCM: + case AlgorithmType::AES_128_GCM: + expectedSize = AES_GCM_IV_SIZE; + break; + default: + ASSERT_TRUE(false); + break; + } + + ASSERT_EQ(expectedSize, iv1.size()); + ASSERT_EQ(expectedSize, iv2.size()); + ASSERT_NE(iv1, iv2); +} + +TEST_F(KeyFactoryTest, test_createKeyUnknown) { + auto factory = OpenSslKeyFactory::create(); + ASSERT_NE(nullptr, factory); + KeyFactoryInterface::Key key1; + ASSERT_FALSE(factory->generateKey(BAD_ALGORITHM_TYPE, key1)); +} + +TEST_F(KeyFactoryTest, test_createIvUnknown) { + auto factory = OpenSslKeyFactory::create(); + ASSERT_NE(nullptr, factory); + KeyFactoryInterface::IV iv1; + ASSERT_FALSE(factory->generateIV(BAD_ALGORITHM_TYPE, iv1)); +} + +INSTANTIATE_TEST_CASE_P( + OpenSslKeyFactoryTest, + KeyFactoryTest, + Values( + AlgorithmType::AES_256_CBC, + AlgorithmType::AES_256_CBC_PAD, + AlgorithmType::AES_128_CBC, + AlgorithmType::AES_128_CBC_PAD, + AlgorithmType::AES_128_GCM, + AlgorithmType::AES_256_GCM)); + +} // namespace test +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCrypto/test/OpenSslTypeMapperTest.cpp b/core/Crypto/acsdkCrypto/test/OpenSslTypeMapperTest.cpp new file mode 100644 index 0000000000..f86a16e4fb --- /dev/null +++ b/core/Crypto/acsdkCrypto/test/OpenSslTypeMapperTest.cpp @@ -0,0 +1,69 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCrypto { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +class MapDigest : public TestWithParam> {}; + +TEST_P(MapDigest, testMapDigest) { + auto param = GetParam(); + auto digest = OpenSslTypeMapper::mapDigestToEvpMd(DigestType::SHA_256); + ASSERT_EQ(param.second, digest); +} + +INSTANTIATE_TEST_CASE_P( + OpenSslTypeMapperTest, + MapDigest, + Values(std::pair{DigestType::SHA_256, EVP_sha256()})); + +TEST(OpenSslTypeMapperTest, test_unknownDigest) { + auto digest = OpenSslTypeMapper::mapDigestToEvpMd(static_cast(0)); + ASSERT_EQ(nullptr, digest); +} + +class MapCipher : public TestWithParam> {}; + +TEST_P(MapCipher, testCipherMap) { + auto param = GetParam(); + auto cipher = OpenSslTypeMapper::mapAlgorithmToEvpCipher(param.first); + ASSERT_EQ(param.second, cipher); +} + +INSTANTIATE_TEST_CASE_P( + OpenSslTypeMapperTest, + MapCipher, + Values( + std::pair{AlgorithmType::AES_256_CBC, EVP_aes_256_cbc()}, + std::pair{AlgorithmType::AES_256_CBC_PAD, EVP_aes_256_cbc()}, + std::pair{AlgorithmType::AES_128_CBC, EVP_aes_128_cbc()}, + std::pair{AlgorithmType::AES_128_CBC_PAD, EVP_aes_128_cbc()})); + +TEST(OpenSslTypeMapperTest, test_unknownAlgorithm) { + auto cipher = OpenSslTypeMapper::mapAlgorithmToEvpCipher(static_cast(0)); + ASSERT_EQ(nullptr, cipher); +} + +} // namespace test +} // namespace acsdkCrypto +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCryptoInterfaces/CMakeLists.txt b/core/Crypto/acsdkCryptoInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..6d08945310 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(acsdkCryptoInterfaces LANGUAGES CXX) + +add_subdirectory("src") +add_subdirectory(test) diff --git a/core/Crypto/acsdkCryptoInterfaces/doc/CryptoAPI.dox b/core/Crypto/acsdkCryptoInterfaces/doc/CryptoAPI.dox new file mode 100644 index 0000000000..069c700fb6 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/doc/CryptoAPI.dox @@ -0,0 +1,36 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \defgroup CryptoAPI Cryptography API + * @brief Cryptographic Functions for ACSDK + * + * This module provides APIs for encrypting and decrypting data, and for computing secure digests. + * + * There are two ways for data encryption and decryption: through user-space cryptographic functions, accessible through + * \ref alexaClientSDK::acsdkCryptoInterfaces::CryptoFactoryInterface and through hardware security module (HSM) + * accessible through \ref alexaClientSDK::acsdkCryptoInterfaces::KeyStoreInterface. + * + * When using \ref alexaClientSDK::acsdkCryptoInterfaces::CryptoFactoryInterface user can generate keys and + * initialization vectors through \ref alexaClientSDK::acsdkCryptoInterfaces::KeyFactoryInterface to use with + * cryptographic functions. When working with \ref alexaClientSDK::acsdkCryptoInterfaces::KeyStoreInterface, keys must + * be pre-provisioned by device manufacturer and those keys can be used through references (aliases). + * + * \sa CryptoIMPL + * \sa CryptoPKCS11 + * + * \sa alexaClientSDK::acsdkCryptoInterfaces + * \sa alexaClientSDK::acsdkCryptoInterfaces::test + */ diff --git a/core/Crypto/acsdkCryptoInterfaces/doc/Namespaces.dox b/core/Crypto/acsdkCryptoInterfaces/doc/Namespaces.dox new file mode 100644 index 0000000000..a4c16db169 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/doc/Namespaces.dox @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \namespace ::alexaClientSDK::acsdkCryptoInterfaces + * \brief Cryptographic Types and Interfaces. + * \ingroup CryptoAPI + */ diff --git a/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/AlgorithmType.h b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/AlgorithmType.h new file mode 100644 index 0000000000..22f74711fb --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/AlgorithmType.h @@ -0,0 +1,81 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_ALGORITHMTYPE_H_ +#define ACSDKCRYPTOINTERFACES_ALGORITHMTYPE_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { + +/** + * @brief Enumeration of all supported encryption protocols. + * + * This enumeration defines cipher type, key size, mode of operation, and padding. + * + * @ingroup CryptoAPI + */ +enum class AlgorithmType { + /// @brief AES 256 CBC. + /// + /// AES Encryption with 256 bit key size and cipher block chaining. + AES_256_CBC = 1, + + /// @brief AES 256 CBC with Padding. + /// + /// AES Encryption with 256 bit key size, cipher block chaining, and PKCS#7 padding. + AES_256_CBC_PAD = 2, + + /// @brief AES 128 CBC. + /// + /// AES Encryption with 128 bit key size and cipher block chaining. + AES_128_CBC = 3, + + /// @brief AES 128 CBC with Padding. + /// + /// AES Encryption with 128 bit key size, cipher block chaining, and PKCS#7 padding. + AES_128_CBC_PAD = 4, + + /// @brief AES 256 GCM. + /// + /// AES Encryption with 256 bit key size and Galois/Counter mode. This algorithm belongs to Authenticated Encryption + /// / Authenticated Decryption (AEAD) family. + AES_256_GCM = 5, + + /// @brief AES 128 GCM. + /// + /// AES Encryption with 128 bit key size and Galois/Counter mode. This algorithm belongs to Authenticated Encryption + /// / Authenticated Decryption (AEAD) family. + AES_128_GCM = 6, +}; + +/** + * @brief Helper method to write algorithm type as a literal constant. + * + * This method enables logging functions to accept algorithm type as a value. + * + * @param[in] out Output stream. + * @param[in] value Value to write. + * + * @return Output stream. + * @ingroup CryptoAPI + */ +std::ostream& operator<<(std::ostream& out, AlgorithmType value); + +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_ALGORITHMTYPE_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/CryptoCodecInterface.h b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/CryptoCodecInterface.h new file mode 100644 index 0000000000..c0e169f4b7 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/CryptoCodecInterface.h @@ -0,0 +1,368 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_CRYPTOCODECINTERFACE_H_ +#define ACSDKCRYPTOINTERFACES_CRYPTOCODECINTERFACE_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { + +/** + * @brief Crypto codec (cipher) interface. + * + * This interface provides functions to encrypt and decrypt the data, and behaviour depends on the way the interface is + * created. See @ref CryptoFactoryInterface for details on this interface creation. + * + * * * * + * ### Using Encryption and Decryption without Authentication + * + * For encryption without authentication, the algorithm takes key, initialization vector, and plaintext (unencrypted + * data) as inputs, and produces ciphertext (encrypted data) as output. Application must keep the key secret, while + * initialization vector can be stored or transferred along ciphertext. + * + * For decryption without authentication, the algorithm takes key, initialization vector, and ciphertext (encrypted + * data) as inputs, and produces plaintext (unencrypted data) as output. When decrypting, key and initialization vector + * must match the ones, provided during decryption. + * + * Codec must be initialized before use with a call to CryptoCodecInterface::init method. The data is encrypted or + * decrypted with subsequent calls to CryptoCodecInterface::process. Because the codec may cache some of the output + * inside internal buffers, the user must call CryptoCodecInterface::finalize method to get the output remainder. + * + * @code{.cpp} + * CryptoCodecInterface::DataBlock plaintext = ...; + * CryptoCodecInterface::Key key = ...; + * CryptoCodecInterface::IV iv = ...; + * + * auto codec = cryptoFactory->createEncoder(AlgorithmType::AES_256_CBC_PAD); + * cipher->init(key, iv); + * CryptoCodecInterface::DataBlock ciphertext; + * codec->encode(plaintext, &ciphertext); + * codec->finalize(&ciphertext); + * @endcode + * + * The interface allows processing of a continuous stream of data, broken into parts, as long as each part is mappable + * into one of supported "process" inputs. + * + * @code{.cpp} + * cipher->init(key, iv); + * CryptoCodecInterface::DataBlock ciphertext; + * // Encode data blocks + * codec->process(plaintext1, ciphertext); + * codec->process(plaintext2, ciphertext); + * ... + * codec->process(plaintextN, ciphertext); + * codec->finalize(&ciphertext); + * @endcode + * + * The instance of this class can be re-initialized for reuse by calling #init() method and supplying new key and IV. + * This method resets codec state and prepares it for encoding/decoding new data. Key and IV can have the same value + * or be different: + * @code{.cpp} + * // Encode the data with a key and first IV. + * cipher->init(key, iv1); + * codec->process(plaintext1, ciphertext1); + * ... + * codec->finalize(&ciphertext1); + * // Reset the codec to re-encode data with the same key but different IV + * codec->init(key, iv2) + * codec->process(plaintext1, ciphertext2); + * ... + * codec->finalize(&ciphertext2); + * @endcode + * + * * * * + * ### Using Authenticated Encryption and Authenticated Decryption (AEAD) Algorithms + * + * #### Authenticated encryption + * + * For authenticated encryption, the algorithm takes key, initialization vector, additional authenticated data (AAD) and + * plaintext (unencrypted data) as inputs, and produces ciphertext (encrypted data) and tag (also known as Message + * Authentication Code/MAC) as outputs. + * + * Application must keep the key data secret, while initialization vector and tag can be stored or transferred along + * ciphertext. The best practice is not to store AAD but to compute it depending on domain scenario. For example, when + * encrypting property values, property key can be used as AAD, which would protect encrypted data from being reused for + * a property with a different name. + * + * Overall, encryption process is similar to un-authenticated encryption, with the following differences: + * * Caller must provide AAD right after initialization and before processing is done. AAD is optional, but recommended. + * * Caller must get a tag (MAC) after encryption is finalized. + * + * After codec is initialized with #init() method, additional data is provided with #processAAD() calls before starting + * encryption with #process() method calls. The tag is retrieved with #getTag() call after #finalize(). + * + * @code{.cpp} + * // Encrypt the data with a key and first IV. + * cipher->init(key, iv1); + * codec->processAAD(additionalAuthenticatedData); + * ... + * // Start encrypting data + * codec->process(plaintext1, ciphertext1); + * ... + * codec->finalize(&ciphertext1); + * + * // Retrieve tag + * codec->getTag(tag); + * @endcode + * + * #### Authenticated Decryption + * + * For authenticated decryption, the algorithm takes key, initialization vector, additional authenticated data (AAD), + * tag (also known as Message Authentication Code/MAC), and ciphertext (encrypted data) as inputs, and produces + * plaintext (unencrypted data) as output. + * + * Overall, decryption process is similar to un-authenticated decryption, with the following differences: + * * Caller must provide AAD right after initialization and before processing is done. AAD must be the same as one, + * used during authenticated encryption. + * * Caller must set a tag (MAC) for verification before decryption is finalized. + * + * After codec is initialized with #init() method, additional data is provided with #processAAD() calls before starting + * decryption with #process() method calls. The tag is set with #setTag() call before #finalize(). + * + * @code{.cpp} + * // Decrypt the data with a key and first IV. + * cipher->init(key, iv1); + * codec->processAAD(additionalAuthenticatedData); + * ... + * // Start decrypting data + * codec->process(plaintext1, ciphertext1); + * ... + * // Set tag for verification + * code->setTag(tag); + * // Finalize the operation + * codec->finalize(&ciphertext1); + * @endcode + * + * ### Thread Safety + * + * This interface is not thread safe and caller must ensure only one thread can make calls at any time. + * + * @ingroup CryptoAPI + */ +class CryptoCodecInterface { +public: + /// @brief Data block type. + /// This type represents a byte array. + typedef std::vector DataBlock; + + /// @brief Key type. + /// This type contains key bytes. + typedef std::vector Key; + + /// @brief Initialization vector type. + /// Initialization vector contains data to initialize codec state before encrypting or + /// decrypting data. + typedef std::vector IV; + + /// @brief Tag vector type. + /// Tag is used with AEAD mode of operation like with Galois/Counter mode. + typedef std::vector Tag; + + /// @brief Default destructor. + virtual ~CryptoCodecInterface() noexcept = default; + + /** + * @brief Initialize the codec. + * + * Initializes (or re-initializes) codec with a given key and initialization vector. This method must be called + * before any processing can be done. + * + * This method can be called to reset and re-initialize codec instance for reuse. + * + * @param[in] key Key to use. The method will fail with an error if the size of the key doesn't correspond to + * cipher type. + * @param[in] iv Initialization vector. The method will fail with an error if the size of IV doesn't correspond + * to cipher type. + * + * @return True if operation has succeeded. If operation fails, false is returned, and the state + * of codec is undefined, and the object must be discarded. + */ + virtual bool init(const Key& key, const IV& iv) noexcept = 0; + + /** + * @brief Process AAD data block. + * + * Processes Additional Authenticated Data block. AAD is used for Authenticated Encryption Authenticated Decryption + * algorithms like AES-GCM, and cannot be used with non-AEAD algorithms. + * + * AEAD algorithms allow submission of arbitrary amount of AAD (including none), and this data affects algorithm + * output and tag value computation. When data is encrypted with AAD, the same AAD must be used for decryption. + * + * AAD doesn't impact the output size of ciphertext when encrypting, nor the size of plaintext when decrypting. For + * data decryption the total submitted AAD input must match the one used for encryption. There is no difference if + * AAD is submitted all at once, or split into smaller chunks and submitted through a series of calls. + * + * This method can be called any number of times after #init() has been performed and before calling #process(). If + * there is no more data to process, the user must call #finalize() to get the final data block. The method will + * fail, if this method is called before #init() or after #process() or #finalize() calls. The method will fail if + * the codec algorithm is not from AEAD family. + * + * @param[in] dataIn Additional authenticated data. If the data container is empty, the method will do nothing + * and return true. + * + * @return True if operation has succeeded. If operation fails, false is returned, and the state + * of codec is undefined, and the codec object must be re-initialized or discarded. + */ + virtual bool processAAD(const DataBlock& dataIn) noexcept = 0; + + /** + * @brief Process AAD data block range. + * + * Processes Additional Authenticated Data block range. AAD is used for Authenticated Encryption Authenticated + * Decryption algorithms like AES-GCM, and cannot be used with non-AEAD algorithms. + * + * AEAD algorithms allow submission of arbitrary amount of AAD (including none), and this data affects algorithm + * output and tag value computation. When data is encrypted with AAD, the same AAD must be used for decryption. + * + * AAD doesn't impact the output size of ciphertext when encrypting, nor the size of plaintext when decrypting. For + * data decryption the total submitted AAD input must match the one used for encryption. There is no difference if + * AAD is submitted all at once, or split into smaller chunks and submitted through a series of calls. + * + * This method can be called any number of times after #init() has been performed and before calling #process(). If + * there is no more data to process, the user must call #finalize() to get the final data block. The method will + * fail, if this method is called before #init() or after #process() or #finalize() calls. The method will fail if + * the codec algorithm is not from AEAD family. + * + * @param[in] dataInBegin Range start. This parameter must be equal or less than @a dataInEnd. If the parameter is + * greater than @a dataInEnd the implementation does nothing and returns false. + * @param[in] dataInEnd Range end. This parameter must be equal or greater than @a dataInBegin. If the parameter + * is smaller than @a dataInBegin the implementation does nothing and returns false. + * + * @return True if operation has succeeded. If operation fails, false is returned, and the state + * of codec is undefined, and the codec object must be re-initialized or discarded. + */ + virtual bool processAAD(DataBlock::const_iterator dataInBegin, DataBlock::const_iterator dataInEnd) noexcept = 0; + + /** + * @brief Encrypt or decrypt a data block. + * + * Processes (encrypts or decrypts) a data block. This method consumes a block of input data and optionally produces + * output data. Because cipher algorithms can cache some data internally, the size of output may not match size of + * input. + * + * This method can be called any number of times after #init has been performed and before calling #finalize. If + * there is no more data to process, the user must call #finalize() to get the final data block. The method will + * fail, if this method is called before #init() or after #finalize() calls. + * + * When cipher is processing data, the output is appended to @a dataOut container. The caller should not make + * assumptions how many bytes will be appended, as the implementation may cache data internally. + * + * @param[in] dataIn Data to encrypt or decrypt. If the data container is empty, the method will do nothing and + * return true. + * @param[out] dataOut Processed data. Method appends data to @a dataOut container. The size of output may differ + * from the size of input. + * + * @return True if operation has succeeded. If operation fails, false is returned, and the state + * of codec and @a dataOut are undefined, and the codec object must be re-initialized or discarded. + */ + virtual bool process(const DataBlock& dataIn, DataBlock& dataOut) noexcept = 0; + + /** + * @brief Encrypt or decrypt a data block range. + * + * Processes (encrypts or decrypts) a data block range. This method consumes a block of input data and optionally + * produces output data. Because cipher algorithms can cache some data internally, the size of output may not match + * size of input. + * + * This method can be called any number of times after #init has been performed and before calling #finalize. If + * there is no more data to process, the user must call #finalize() to get the final data block. The method will + * fail, if this method is called before #init() or after #finalize() calls. + * + * When cipher is processing data, the output is appended to @a dataOut container. The caller should not make + * assumptions how many bytes will be appended, as the implementation may cache data internally. + * + * @param[in] dataInBegin Range start. This parameter must be equal or less than @a dataInEnd. If the parameter is + * greater than @a dataInEnd the implementation does nothing and returns false. + * @param[in] dataInEnd Range end. This parameter must be equal or greater than @a dataInBegin. If the parameter + * is smaller than @a dataInBegin the implementation does nothing and returns false. + * @param[out] dataOut Processed data. Method appends (not replaces) data to @a dataOut container. The size of + * output may differ from the size of input. + * + * @return True if operation has succeeded. If operation fails, false is returned, and the state + * of codec and @a dataOut are undefined, and the codec object must be re-initialized or discarded. + */ + virtual bool process( + DataBlock::const_iterator dataInBegin, + DataBlock::const_iterator dataInEnd, + DataBlock& dataOut) noexcept = 0; + + /** + * @brief Complete data processing. + * + * Completes processing (encryption or decryption) of data. This method writes a final data block to @a dataOut if + * necessary. Finalize may or may not produce a final data block depending on codec state and encryption mode. For + * example, when block cipher is used without padding, this method never produces contents (it may still fail if + * previous input didn't match block boundary), but when PKCS#7 padding is used, this method may produce up to block + * size bytes of data. + * + * When performing Authenticated Encryption, this method completes tag (MAC) computation and #getTag() method shall + * be called after this method. + * + * When performing Authenticated Decryption, #setTag() method shall be called with a tag (MAC) and this method + * performs tag validation. + * + * @param[out] dataOut Processed data. Method appends data to @a dataOut container. + * + * @return True if operation has succeeded. If operation fails, false is returned, and the state + * of codec and @a dataOut are undefined, and the codec object must be re-initialized or discarded. + * + * @see #getTag() + * @see #setTag() + */ + virtual bool finalize(DataBlock& dataOut) noexcept = 0; + + /** + * @brief Provides tag from authenticated encryption. + * + * This method returns a tag (known as Message Authentication Code/MAC) after authenticated encryption is completed + * with #finalize() call. This method must be used with Authenticated Encryption Authenticated Decryption ciphers + * like AES-GCM, and cannot be used with non-AEAD algorithms. TThe method will fail if the codec algorithm is not + * from AEAD family. + * + * @param[out] tag Tag value. Method appends a value to @a tag container. + * + * @return True if operation has succeeded. If operation fails, false is returned, and the state of codec and @a tag + * are undefined, and the codec object must be re-initialized or discarded. + * + * @see #setTag() + * @see https://en.wikipedia.org/wiki/Message_authentication_code + */ + virtual bool getTag(Tag& tag) noexcept = 0; + + /** + * @brief Sets tag for authenticated decryption. + * + * This method provide a tag (known as Message Authentication Code/MAC) to authenticated decryption algorithm after + * all ciphertext is submitted with #process() calls and before completing it with #finalize() call. This method + * must be used with Authenticated Encryption Authenticated Decryption ciphers like AES-GCM, and cannot be used with + * non-AEAD algorithms. The method will fail if the codec algorithm is not from AEAD family. + * + * @param[in] tag Tag value. + * + * @return True if operation has succeeded. If operation fails, false is returned, the state of codec is undefined, + * and the codec object must be re-initialized or discarded. + * + * @see #getTag() + * @see https://en.wikipedia.org/wiki/Message_authentication_code + */ + virtual bool setTag(const Tag& tag) noexcept = 0; +}; + +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_CRYPTOCODECINTERFACE_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/CryptoFactoryInterface.h b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/CryptoFactoryInterface.h new file mode 100644 index 0000000000..8fcf3e24af --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/CryptoFactoryInterface.h @@ -0,0 +1,93 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_CRYPTOFACTORYINTERFACE_H_ +#define ACSDKCRYPTOINTERFACES_CRYPTOFACTORYINTERFACE_H_ + +#include + +#include "AlgorithmType.h" +#include "KeyFactoryInterface.h" +#include "CryptoCodecInterface.h" +#include "DigestInterface.h" +#include "DigestType.h" + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { + +/** + * @brief Crypto API factory interface. + * + * This interface allows construction of platform-specific crypto facilities. + * + * ### Thread Safety + * + * This interface is thread safe and can be used concurrently by different threads. + * + * @ingroup CryptoAPI + * @sa CryptoIMPL + */ +class CryptoFactoryInterface { +public: + /// @brief Default destructor. + virtual ~CryptoFactoryInterface() noexcept = default; + + /** + * @brief Create new encoder cipher. + * + * Creates a new encoder instance for a given algorithm type to encrypt data. + * + * @param[in] type Encryption algorithm type. + * + * @return Encoder reference or nullptr on error. + */ + virtual std::unique_ptr createEncoder(AlgorithmType type) noexcept = 0; + + /** + * @brief Create new decodec cipher. + * + * Creates a new decoder instance for a given algorithm type to decrypt data. + * + * @param[in] type Decryption algorithm type. + * + * @return Decoder reference or nullptr on error. + */ + virtual std::unique_ptr createDecoder(AlgorithmType type) noexcept = 0; + + /** + * @brief Create new hash function. + * + * Creates a new digest instance for a given digest type. + * + * @param[in] type Digest type. + * + * @return Digest reference or nullptr on error. + */ + virtual std::unique_ptr createDigest(DigestType type) noexcept = 0; + + /** + * @brief Provides key factory. + * + * Provides a key factory interface. Key factory allows creation of random keys and initialization vectors. + * + * @return Key factory reference or nullptr on error. + */ + virtual std::shared_ptr getKeyFactory() noexcept = 0; +}; + +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_CRYPTOFACTORYINTERFACE_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/DigestInterface.h b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/DigestInterface.h new file mode 100644 index 0000000000..e6bdb5ce71 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/DigestInterface.h @@ -0,0 +1,223 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_DIGESTINTERFACE_H_ +#define ACSDKCRYPTOINTERFACES_DIGESTINTERFACE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { + +/** + * @brief Digest computation interface. + * + * This interface wraps up logic for computing various digest types (SHA-2, RC5, etc.). + * + * To compute the digest, the user shall call any of @a process methods to consume all input data, and when all data + * is consume, call #finalize to get the result. + * + * @code{.cpp} + * DigestInterface::DataBlock input = ...; + * + * auto digest = cryptoFactory->createDigest(DigestType::SHA256); + * codec->process(input); + * codec->process(input); + * DigestInterface::DataBlock digestData; + * codec->finalize(&digestData); + * @endcode + * + * The instance of the class is reusable, and it also can be used if any of the methods returned + * an error code after call to #reset(). + * + * ### Thread Safety + * + * This interface is not thread safe and caller must ensure only one thread can make calls at any time. + * + * @ingroup CryptoAPI + */ +class DigestInterface { +public: + /// @brief Data block type. + /// This type represents a byte array. + typedef std::vector DataBlock; + + /// Default destructor. + virtual ~DigestInterface() noexcept = default; + + /** + * @brief Updates digest with a data block. + * + * Updates digest value with a data from a data block. + * + * This call is logical equivalent to a following code: + * @code{.cpp} + * for (b: dataIn) processUInt8(b); + * @endcode + * + * @param[in] dataIn Data for digest. + * + * @return True if operation has succeeded. If operation fails, the state of object is undefined, and user must + * call #reset() to reuse the object. + */ + virtual bool process(const DataBlock& dataIn) noexcept = 0; + + /** + * @brief Updates digest with a data block range. + * + * Updates digest value with a data from a data block range. + * + * This call is logical equivalent to a following code: + * @code{.cpp} + * for (auto it = begin; it != end; ++it) processUInt8(*it); + * @endcode + * + * @param[in] begin Begin of data block. This parameter must be equal or less than @a end. If the parameter is + * greater than @a dataInEnd the implementation does nothing and returns false. + * @param[in] end Range end. This parameter must be equal or greater than @a begin. If the parameter is smaller + * than @a begin the implementation does nothing and returns false. + * + * @return True if operation has succeeded. If operation fails, the state of object is undefined, and user must + * call #reset() to reuse the object. + */ + virtual bool process(DataBlock::const_iterator begin, DataBlock::const_iterator end) noexcept = 0; + + /** + * @brief Updates digest with a byte value. + * + * Updates digest value with a single byte value. + * + * @param[in] value byte value. + * + * @return True if operation has succeeded. If operation fails, the state of object is undefined, and user must + * call #reset() to reuse the object. + */ + virtual bool processByte(unsigned char value) noexcept = 0; + + /** + * @brief Updates digest with uint8_t value. + * + * Updates digest value with a 8-bit value. + * + * @param[in] value Integer value. + * + * @return True if operation has succeeded. If operation fails, the state of object is undefined, and user must + * call #reset() to reuse the object. + */ + virtual bool processUInt8(uint8_t value) noexcept = 0; + + /** + * @brief Updates digest with uint16_t integer value. + * + * Updates digest value with uint16_t data. This method uses big endian (network byte order) encoding for + * presenting input value as a byte array. + * + * This method is equivalent to the following: + * @code + * processUInt8(value >> 8) && processUInt8(value); + * @endcode + * + * @param[in] value Integer value. + * + * @return True if operation has succeeded. If operation fails, the state of object is undefined, and user must + * call #reset() to reuse the object. + */ + virtual bool processUInt16(uint16_t value) noexcept = 0; + + /** + * @brief Updates digest with uint32_t integer value. + * + * Updates digest value with uint32_t data. This method uses big endian (network byte order) encoding for + * presenting input value as a byte array. + * + * This method is equivalent to the following: + * @code + * processUInt16(value >> 16) && processUInt16(value); + * @endcode + * + * @param[in] value Integer value. + * + * @return True if operation has succeeded. If operation fails, the state of object is undefined, and user must + * call #reset() to reuse the object. + */ + virtual bool processUInt32(uint32_t value) noexcept = 0; + + /** + * @brief Updates digest with uint64_t integer value. + * + * Updates digest value with uint64_t data. This method uses big endian (network byte order) encoding for + * presenting input value as a byte array. + * + * This method is equivalent to the following: + * @code + * processUInt32((uint32_t)(value >> 32)) && processUInt32((uint32_t)value); + * @endcode + * + * @param[in] value Integer value. + * + * @return True if operation has succeeded. If operation fails, the state of object is undefined, and user must + * call #reset() to reuse the object. + */ + virtual bool processUInt64(uint64_t value) noexcept = 0; + + /** + * @brief Updates digest with string value. + * + * Updates digest with bytes from a string object. The input is treated as a byte array without terminating null + * character. + * + * This method is equivalent to the following: + * @code + * for (auto ch: value) processByte(ch); + * @endcode + * + * @param[in] value String value. + * + * @return True if operation has succeeded. If operation fails, the state of object is undefined, and user must + * call #reset() to reuse the object. + */ + virtual bool processString(const std::string& value) noexcept = 0; + + /** + * @brief Finishes digest computation and produces the result. + * + * This method finishes digest computation and produces the result. The object is reset if this call succeeds and + * can be reused for computing new digest. + * + * @param[out] dataOut Computed digest. The size of output depends on the selected digest algorithm. The method + * appends data to @a dataOut container. + * + * @return True if operation has succeeded. This object state is reset and ready to start computing a new digest. + * If operation fails, the state of the object and contents of @a dataOut container are undefined. The user can + * call #reset() to reuse the object in this case. + */ + virtual bool finalize(DataBlock& dataOut) noexcept = 0; + + /** + * @brief Resets the digest. + * + * This method resets object state and prepares it for reuse. + * + * @return True if operation has succeeded. If operation fails, the state of the object is undefined and the object + * must not used. + */ + virtual bool reset() noexcept = 0; +}; + +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_DIGESTINTERFACE_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/DigestType.h b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/DigestType.h new file mode 100644 index 0000000000..68b4379ce0 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/DigestType.h @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_DIGESTTYPE_H_ +#define ACSDKCRYPTOINTERFACES_DIGESTTYPE_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { + +/** + * @brief Enumeration of all supported digest algorithms. + * + * This enumeration provides list of supported digest algorithms. + * + * @ingroup CryptoAPI + */ +enum class DigestType { + /// @brief SHA-256 digest algorithm. + /// + /// Secure Hash Algorithm 2 with 256 bit digest. + /// + /// @see https://en.wikipedia.org/wiki/SHA-2 + SHA_256 = 1, +}; + +/** + * @brief Helper method to write digest type as a literal constant. + * + * This method enables logging functions to accept digest type as a value. + * + * @param[in] out Output stream. + * @param[in] value Value to write. + * + * @return Output stream. + * @ingroup CryptoAPI + */ +std::ostream& operator<<(std::ostream& out, DigestType value); + +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_DIGESTTYPE_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/KeyFactoryInterface.h b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/KeyFactoryInterface.h new file mode 100644 index 0000000000..c30e54a49a --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/KeyFactoryInterface.h @@ -0,0 +1,80 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_KEYFACTORYINTERFACE_H_ +#define ACSDKCRYPTOINTERFACES_KEYFACTORYINTERFACE_H_ + +#include + +#include "AlgorithmType.h" + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { + +/** + * @brief Key factory interface. + * + * This interface allows construction of new keys and initialization vectors. + * + * ### Thread Safety + * + * This interface is thread safe and can be used concurrently by different threads. + * + * @ingroup CryptoAPI + */ +class KeyFactoryInterface { +public: + /// @brief Key data. + /// Key is a sequence of bytes, and the size depends on an encryption algorithm. + typedef std::vector Key; + + /// @brief Initialization vector type. + /// IV is a sequence of bytes, and the size depends on a encryption algorithm. + typedef std::vector IV; + + /// Default destructor. + virtual ~KeyFactoryInterface() noexcept = default; + + /** + * @brief Generates a new key. + * + * Generates a new key for a given algorithm. + * + * @param[in] type Encryption algorithm type. + * @param[out] key Key data. + * + * @return Boolean indicating operation success. If operation fails, the state of + * @a key is undefined. + */ + virtual bool generateKey(AlgorithmType type, Key& key) noexcept = 0; + + /** + * @brief Generates a new initialization vector. + * + * Generate random initialization vector. + * + * @param[in] type Algorithm type. + * @param[out] iv Initialization vector. + * + * @return Boolean indicating operation success. If operation fails, the state of + * @a iv is undefined. + */ + virtual bool generateIV(AlgorithmType type, IV& iv) noexcept = 0; +}; + +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_KEYFACTORYINTERFACE_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/KeyStoreInterface.h b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/KeyStoreInterface.h new file mode 100644 index 0000000000..a9f3358382 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/include/acsdkCryptoInterfaces/KeyStoreInterface.h @@ -0,0 +1,211 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_KEYSTOREINTERFACE_H_ +#define ACSDKCRYPTOINTERFACES_KEYSTOREINTERFACE_H_ + +#include +#include +#include + +#include "AlgorithmType.h" + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { + +/** + * @brief Key Store Interface. + * + * Interface provides integration with platform-specific key storage and operations. The vendor can choose how to + * implement this interface for a best security. + * + * This interface enables data encryption and decryption without accessing encryption key data. Keys must be provided + * by device manufacturer (vendor), and cryptography functions access those keys through key aliases. + * + * ACSDK provides a reference implementation of this interface to integrate with Hardware Security Module through + * PKCS#11 API. + * + * ### Thread Safety + * + * This interface is thread safe and can be used concurrently by different threads. + * + * @sa CryptoPKCS11 + * @ingroup CryptoAPI + */ +class KeyStoreInterface { +public: + /// @brief Data type for data block (encrypted or unencrypted). + typedef std::vector DataBlock; + + /// @brief Data type for initialization vector data. + typedef std::vector IV; + + /// @brief Data type for key checksum. + typedef std::vector KeyChecksum; + + /// @brief Data type for tag. + /// Tag (known as Message Authentication Code) is used with AEAD mode of operation like with Galois/Counter mode. + typedef std::vector Tag; + + /// @brief Default destructor. + virtual ~KeyStoreInterface() noexcept = default; + + /** + * @brief Encrypts data block. + * + * This method encrypts data block. The method locates the key, checks if the key type supports the algorithm, + * and performs encryption using provided initialization vector. As a result, the method provides key checksum + * (if supported), and encrypted content. + * + * @param[in] keyAlias Key alias. + * @param[in] type Algorithm type to use. The method will fail, if @a type is AEAD algorithm like AES-GCM. + * @param[in] iv Initialization vector. + * @param[in] plaintext Data to encrypt. + * @param[out] checksum Key checksum. The method appends data to @a checksum if this attribute is supported by + * implementation. + * @param[out] ciphertext Encrypted data. The method appends data to @a ciphertext container. + * + * @return Boolean indicating operation success. If operation fails, the contents of @a checksum and @a ciphertext + * are undefined. + */ + virtual bool encrypt( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext) noexcept = 0; + + /** + * @brief Encrypts data block using authenticated encryption algorithm. + * + * Method encrypts data block using authenticated encryption. The method locates the key, checks if the key type + * supports the algorithm, and performs encryption using provided initialization vector and additional + * authenticated data. As a result, the method provides key checksum (if supported), authentication tag (also known + * as Message Authentication Code/MAC), and encrypted content. + * + * @param[in] keyAlias Key alias. + * @param[in] type Algorithm type to use. The method will fail, if @a type is not AEAD algorithm like + * AES-GCM. + * @param[in] iv Initialization vector. + * @param[in] aad Additional authenticated data. This data works as an input to encryption function to + * ensure that the resulting ciphertext can be decrypted only with the same AAD. + * @param[in] plaintext Data to encrypt. + * @param[out] checksum Key checksum. The method appends data to @a checksum if this attribute is supported by + * implementation. + * @param[out] ciphertext Encrypted data. The method appends data to @a ciphertext container. + * @param[out] tag Authentication tag (also known as MAC). Authentication tag must be provided to + * decryption function to prevent data tampering. The method appends data to @a tag + * container. + * + * @return Boolean indicating operation success. If operation fails, the contents of @a checksum, @a ciphertext, and + * @a tag are undefined. + * + * @see #decryptAD() + */ + virtual bool encryptAE( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& aad, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext, + Tag& tag) noexcept = 0; + + /** + * @brief Decrypts data block. + * + * Method decrypts data block. The method locates the key, checks if key type supports requested algorithm and has + * matching checksum (if checksum is supported), and performs decryption. + * + * @param[in] keyAlias Key alias. + * @param[in] type Algorithm type to use. The method will fail, if @a type is AEAD algorithm like AES-GCM. + * @param[in] checksum Key checksum if available. If implementation doesn't support checksum, the value of + * this parameter is ignored. The system checks checksum against checksum of a currently + * available key before decrypting data to ensure we don't try to use a different key, + * then the one, that has been used during encryption. + * @param[in] iv Initialization vector. This vector must match have the same value, as the one used when + * encrypting data. + * @param[in] ciphertext Data to decrypt. + * @param[out] plaintext Decrypted data. This method appends data to @a plaintext. + * + * @return Boolean indicating operation success. If operation fails, the contents of @a plaintext is undefined. + */ + virtual bool decrypt( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& ciphertext, + DataBlock& plaintext) noexcept = 0; + + /** + * @brief Decrypts data block using authenticated decryption algorithm. + * + * Method decrypts data block using additional authenticated data and authentication tag (also known as Message + * Authentication Code/MAC). This method locates the key, checks if key type supports requested algorithm and has + * matching checksum (if checksum is supported), and performs decryption. + * + * @param[in] keyAlias Key alias. + * @param[in] type Algorithm type to use. The method will fail, if @a type is not AEAD algorithm like + * AES-GCM. + * @param[in] checksum Key checksum if available. If implementation doesn't support checksum, the value of this + * parameter is ignored. The system checks checksum against checksum of a currently + * available key before decrypting data to ensure we don't try to use a different key, then + * the one, that has been used during encryption. + * @param[in] iv Initialization vector. This vector must match have the same value, as the one used when + * encrypting data. + * @param[in] aad Additional authenticated data. This data must match AAD used when encrypting the + * content. Decryption will fail if the data doesn't match. + * @param[in] ciphertext Data to decrypt. + * @param[in] tag Authentication tag (also known as MAC). The algorithm uses tag from encryption + * algorithm to check if the data has been tampered. + * @param[in] plaintext Decrypted data. This method appends data to @a plaintext. + * + * @return Boolean indicating operation success. If operation fails, the contents of @a plaintext is undefined. + * + * @see #encryptAE() + */ + virtual bool decryptAD( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& aad, + const DataBlock& ciphertext, + const Tag& tag, + DataBlock& plaintext) noexcept = 0; + + /** + * @brief Returns default key alias. + * + * Get default key alias. Any component can have component-specific configuration or use default configuration. + * + * Default key alias is a platform configuration parameter, and may change over time. When the alias changes, + * implementation must use new alias to encrypt new data, and must use old alias to decrypt existing data as long + * as the old key exists. + * + * @param[out] keyAlias Reference to key alias. The method replaces contents of @a keyAlias. + * + * @return Returns true if main key alias is stored into @a keyAlias. Returns false on error. + */ + virtual bool getDefaultKeyAlias(std::string& keyAlias) noexcept = 0; +}; + +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_KEYSTOREINTERFACE_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/src/AlgorithmType.cpp b/core/Crypto/acsdkCryptoInterfaces/src/AlgorithmType.cpp new file mode 100644 index 0000000000..35142682fb --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/src/AlgorithmType.cpp @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCryptoInterfaces { + +std::ostream& operator<<(std::ostream& out, AlgorithmType value) { + const char* type; + switch (value) { + case AlgorithmType::AES_128_GCM: + type = "AES-128-GCM"; + break; + case AlgorithmType::AES_128_CBC: + type = "AES-128-CBC"; + break; + case AlgorithmType::AES_128_CBC_PAD: + type = "AES-128-CBC-PAD"; + break; + case AlgorithmType::AES_256_GCM: + type = "AES-256-GCM"; + break; + case AlgorithmType::AES_256_CBC: + type = "AES-256-CBC"; + break; + case AlgorithmType::AES_256_CBC_PAD: + type = "AES-256-CBC-PAD"; + break; + default: + return out << static_cast(value); + } + return out << type; +} + +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCryptoInterfaces/src/CMakeLists.txt b/core/Crypto/acsdkCryptoInterfaces/src/CMakeLists.txt new file mode 100644 index 0000000000..fb2325bed4 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/src/CMakeLists.txt @@ -0,0 +1,7 @@ + +add_library(acsdkCryptoInterfaces AlgorithmType.cpp DigestType.cpp) +target_compile_definitions(acsdkCryptoInterfaces PRIVATE ACSDK_LOG_MODULE=acsdkCryptoInterfaces) +target_include_directories(acsdkCryptoInterfaces PUBLIC ${acsdkCryptoInterfaces_SOURCE_DIR}/include) + +# install target +asdk_install() diff --git a/core/Crypto/acsdkCryptoInterfaces/src/DigestType.cpp b/core/Crypto/acsdkCryptoInterfaces/src/DigestType.cpp new file mode 100644 index 0000000000..3fc2a2d102 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/src/DigestType.cpp @@ -0,0 +1,31 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCryptoInterfaces { + +std::ostream& operator<<(std::ostream& out, DigestType value) { + switch (value) { + case DigestType::SHA_256: + return out << "SHA-256"; + default: + return out << static_cast(value); + } +} + +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkCryptoInterfaces/test/CMakeLists.txt b/core/Crypto/acsdkCryptoInterfaces/test/CMakeLists.txt new file mode 100644 index 0000000000..8fa7ff30b4 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/test/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +if (BUILD_TESTING) + add_library(acsdkCryptoInterfacesTestLib INTERFACE) + target_include_directories(acsdkCryptoInterfacesTestLib INTERFACE include) + target_link_libraries(acsdkCryptoInterfacesTestLib INTERFACE acsdkCryptoInterfaces) +endif() diff --git a/core/Crypto/acsdkCryptoInterfaces/test/doc/Namespaces.dox b/core/Crypto/acsdkCryptoInterfaces/test/doc/Namespaces.dox new file mode 100644 index 0000000000..b882661d35 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/test/doc/Namespaces.dox @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \namespace ::alexaClientSDK::acsdkCryptoInterfaces::test + * \brief Test stubs and mocks for \ref CryptoAPI + * \ingroup CryptoAPI + */ diff --git a/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoCodec.h b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoCodec.h new file mode 100644 index 0000000000..eb079bd6ae --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoCodec.h @@ -0,0 +1,84 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_TEST_MOCKCRYPTOCODEC_H_ +#define ACSDKCRYPTOINTERFACES_TEST_MOCKCRYPTOCODEC_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { +namespace test { + +/** + * Mock class for @c CryptoCodecInterface. + */ +class MockCryptoCodec : public CryptoCodecInterface { +public: + MOCK_METHOD2(_init, bool(const Key&, const IV&)); + MOCK_METHOD1(_processAAD, bool(const DataBlock&)); + MOCK_METHOD2(_processAAD, bool(const DataBlock::const_iterator&, const DataBlock::const_iterator&)); + MOCK_METHOD2(_process, bool(const DataBlock&, DataBlock&)); + MOCK_METHOD3(_process, bool(const DataBlock::const_iterator&, const DataBlock::const_iterator&, DataBlock&)); + MOCK_METHOD1(_finalize, bool(DataBlock&)); + MOCK_METHOD1(_getTag, bool(Tag&)); + MOCK_METHOD1(_setTag, bool(const Tag&)); + + bool init(const Key& key, const IV& iv) noexcept override; + bool processAAD(const DataBlock& dataIn) noexcept override; + bool processAAD(DataBlock::const_iterator dataInBegin, DataBlock::const_iterator dataInEnd) noexcept override; + bool process(const DataBlock& dataIn, DataBlock& dataOut) noexcept override; + bool process( + DataBlock::const_iterator dataInBegin, + DataBlock::const_iterator dataInEnd, + DataBlock& dataOut) noexcept override; + bool finalize(DataBlock& dataOut) noexcept override; + bool getTag(DataBlock& tag) noexcept override; + bool setTag(const DataBlock& tag) noexcept override; +}; + +inline bool MockCryptoCodec::init(const Key& key, const IV& iv) noexcept { + return _init(key, iv); +} + +inline bool MockCryptoCodec::process(const DataBlock& dataIn, DataBlock& dataOut) noexcept { + return _process(dataIn, dataOut, append); +} + +inline bool MockCryptoCodec::process( + DataBlock::const_iterator dataInBegin, + DataBlock::const_iterator dataInEnd, + DataBlock& dataOut) noexcept { + return _process(dataInBegin, dataInEnd, dataOut); +} + +inline bool MockCryptoCodec::finalize(DataBlock& dataOut) noexcept { + return _finalize(dataOut); +} + +inline bool MockCryptoCodec::getTag(Tag& tag) noexcept { + return _getTag(tag); +} + +inline bool MockCryptoCodec::setTag(const Tag& tag) noexcept { + return _setTag(tag); +} + +} // namespace test +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_TEST_MOCKCRYPTOCODEC_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoFactory.h b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoFactory.h new file mode 100644 index 0000000000..efa54cef75 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoFactory.h @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_TEST_MOCKCRYPTOFACTORY_H_ +#define ACSDKCRYPTOINTERFACES_TEST_MOCKCRYPTOFACTORY_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { +namespace test { + +/** + * Mock class for @c CryptoFactoryInterface. + */ +class MockCryptoFactory : public CryptoFactoryInterface { +public: + MOCK_METHOD1(_createEncoder, std::unique_ptr(AlgorithmType)); + MOCK_METHOD1(_createDecoder, std::unique_ptr(AlgorithmType)); + MOCK_METHOD1(_createDigest, std::unique_ptr(DigestType)); + MOCK_METHOD0(_getKeyFactory, std::shared_ptr()); + + std::unique_ptr createEncoder(AlgorithmType type) noexcept override; + std::unique_ptr createDecoder(AlgorithmType type) noexcept override; + std::unique_ptr createDigest(DigestType type) noexcept override; + std::shared_ptr getKeyFactory() noexcept override; +}; + +inline std::unique_ptr MockCryptoFactory::createEncoder(AlgorithmType type) noexcept { + return _createEncoder(type); +} + +inline std::unique_ptr MockCryptoFactory::createDecoder(AlgorithmType type) noexcept { + return _createDecoder(type); +} + +inline std::unique_ptr MockCryptoFactory::createDigest(DigestType type) noexcept { + return _createDigest(type); +} + +inline std::shared_ptr MockCryptoFactory::getKeyFactory() noexcept { + return _getKeyFactory(); +} + +} // namespace test +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_TEST_MOCKCRYPTOFACTORY_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockDigest.h b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockDigest.h new file mode 100644 index 0000000000..f8a3f7cca6 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockDigest.h @@ -0,0 +1,98 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_TEST_MOCKDIGEST_H_ +#define ACSDKCRYPTOINTERFACES_TEST_MOCKDIGEST_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { +namespace test { + +/** + * Mock class for @c DigestInterface. + */ +class MockDigest : public DigestInterface { +public: + MOCK_METHOD1(_process, bool(const DataBlock&)); + MOCK_METHOD2(_process, bool(const DataBlock::const_iterator, const DataBlock::const_iterator)); + MOCK_METHOD1(_processByte, bool(unsigned char)); + MOCK_METHOD1(_processUInt8, bool(uint8_t)); + MOCK_METHOD1(_processUInt16, bool(uint16_t)); + MOCK_METHOD1(_processUInt32, bool(uint32_t)); + MOCK_METHOD1(_processUInt64, bool(uint64_t)); + MOCK_METHOD1(_processString, bool(const std::string&)); + MOCK_METHOD1(_finalize, bool(DataBlock&)); + MOCK_METHOD0(_reset, bool()); + + bool process(const DataBlock& dataIn) noexcept override; + bool process(DataBlock::const_iterator begin, DataBlock::const_iterator end) noexcept override; + bool processByte(unsigned char value) noexcept override; + bool processUInt8(uint8_t value) noexcept override; + bool processUInt16(uint16_t value) noexcept override; + bool processUInt32(uint32_t value) noexcept override; + bool processUInt64(uint64_t value) noexcept override; + bool processString(const std::string& value) noexcept override; + bool finalize(DataBlock& dataOut) noexcept override; + bool reset() noexcept override; +}; + +bool MockDigest::process(const DataBlock& dataIn) noexcept { + return _process(dataIn); +} + +bool MockDigest::process(DataBlock::const_iterator begin, DataBlock::const_iterator end) noexcept { + return _process(begin, end); +} + +inline bool MockDigest::processByte(unsigned char value) noexcept { + return _processByte(value); +} + +inline bool MockDigest::processUInt8(uint8_t value) noexcept { + return _processUInt8(value); +} + +inline bool MockDigest::processUInt16(uint16_t value) noexcept { + return _processUInt16(value); +} + +inline bool MockDigest::processUInt32(uint32_t value) noexcept { + return _processUInt32(value); +} + +inline bool MockDigest::processUInt64(uint64_t value) noexcept { + return _processUInt64(value); +} + +inline bool MockDigest::processString(const std::string& value) noexcept { + return _processString(value); +} + +inline bool MockDigest::finalize(DataBlock& dataOut) noexcept { + return _finalize(dataOut); +} + +inline bool MockDigest::reset() noexcept { + return _reset(); +} + +} // namespace test +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_TEST_MOCKDIGEST_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyFactory.h b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyFactory.h new file mode 100644 index 0000000000..fe8d05b3dc --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyFactory.h @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_TEST_MOCKKEYFACTORY_H_ +#define ACSDKCRYPTOINTERFACES_TEST_MOCKKEYFACTORY_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { +namespace test { + +/** + * Mock class for @c KeyFactoryInterface. + */ +class MockKeyFactory : public KeyFactoryInterface { +public: + MOCK_METHOD2(_generateKey, bool(AlgorithmType type, Key& key)); + MOCK_METHOD2(_generateIV, bool(AlgorithmType type, IV& iv)); + + bool generateIV(AlgorithmType type, IV& iv) noexcept override; + bool generateKey(AlgorithmType type, Key& key) noexcept override; +}; + +inline bool MockKeyFactory::generateKey(AlgorithmType type, Key& key) noexcept { + return _generateKey(type, key); +} + +inline bool MockKeyFactory::generateIV(AlgorithmType type, IV& iv) noexcept { + return _generateIV(type, iv); +} + +} // namespace test +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_TEST_MOCKKEYFACTORY_H_ diff --git a/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyStore.h b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyStore.h new file mode 100644 index 0000000000..d926118513 --- /dev/null +++ b/core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyStore.h @@ -0,0 +1,160 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCRYPTOINTERFACES_TEST_MOCKKEYSTORE_H_ +#define ACSDKCRYPTOINTERFACES_TEST_MOCKKEYSTORE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCryptoInterfaces { +namespace test { + +/** + * Mock class for @c KeyStoreInterface. + */ +class MockKeyStore : public KeyStoreInterface { +public: + MOCK_METHOD6( + _encrypt, + bool( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext)); + MOCK_METHOD8( + _encryptAE, + bool( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& aad, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext, + DataBlock& tag)); + MOCK_METHOD6( + _decrypt, + bool( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& ciphertext, + DataBlock& plaintext)); + MOCK_METHOD8( + _decryptAD, + bool( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& aad, + const DataBlock& ciphertext, + const Tag& tag, + DataBlock& plaintext)); + MOCK_METHOD1(_getDefaultKeyAlias, bool(std::string&)); + + bool encrypt( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const KeyChecksum& plaintext, + DataBlock& checksum, + DataBlock& ciphertext) noexcept override; + bool encryptAE( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& aad, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext, + Tag& tag) noexcept override; + bool decrypt( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& ciphertext, + DataBlock& plaintext) noexcept override; + bool decryptAD( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& aad, + const DataBlock& ciphertext, + const Tag& tag, + DataBlock& plaintext) noexcept override; + bool getDefaultKeyAlias(std::string& keyAlias) noexcept override; +}; + +inline bool MockKeyStore::encrypt( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext) noexcept { + return _encrypt(keyAlias, type, iv, plaintext, checksum, ciphertext); +} + +inline bool MockKeyStore::encryptAE( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& aad, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext, + Tag& tag) noexcept { + return _encryptAE(keyAlias, type, iv, aad, plaintext, checksum, ciphertext, tag); +} + +inline bool MockKeyStore::decrypt( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& ciphertext, + DataBlock& plaintext) noexcept { + return _decrypt(keyAlias, type, checksum, iv, ciphertext, plaintext); +} + +inline bool MockKeyStore::decryptAD( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& aad, + const DataBlock& ciphertext, + const Tag& tag, + DataBlock& plaintext) noexcept { + return _decryptAD(keyAlias, type, checksum, iv, aad, ciphertext, tag, plaintext); +} + +inline bool MockKeyStore::getDefaultKeyAlias(std::string& keyAlias) noexcept { + return _getDefaultKeyAlias(keyAlias); +} + +} // namespace test +} // namespace acsdkCryptoInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCRYPTOINTERFACES_TEST_MOCKKEYSTORE_H_ diff --git a/core/Crypto/acsdkPkcs11/CMakeLists.txt b/core/Crypto/acsdkPkcs11/CMakeLists.txt new file mode 100644 index 0000000000..c3e3e3fe8d --- /dev/null +++ b/core/Crypto/acsdkPkcs11/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(acsdkPkcs11 LANGUAGES CXX) + +add_subdirectory("src") +if (BUILD_TESTING AND BUILD_SHARED_LIBS) + add_subdirectory("testStubs") + add_subdirectory("test") +endif() diff --git a/core/Crypto/acsdkPkcs11/doc/CryptoPKCS11.dox b/core/Crypto/acsdkPkcs11/doc/CryptoPKCS11.dox new file mode 100644 index 0000000000..a519f1097a --- /dev/null +++ b/core/Crypto/acsdkPkcs11/doc/CryptoPKCS11.dox @@ -0,0 +1,74 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \defgroup CryptoPKCS11 Hardware Security Module Functions + * @brief HSM functions for \ref CryptoAPI. + * + * \ref CryptoPKCS11 implements a subset of \ref CryptoAPI for hardware security module operations. Module provides + * access to data encryption and decryption functions using HSM-managed secrets. + * + * This module requires platform configuration that provides the following information: + * - Path to vendor-specific PKCS#11 interface library + * - Token name + * - User PIN for accessing key functions + * - Default main key alias. + * + * Vendor-specific PKCS#11 library provides low-level access to HSM functions. In production environment the + * configuration access must be restricted to a service user account, and library path must point to vendor-specific + * interface library. + * + * In test environment, a software emulation or interception library can be used for development and debugging, but + * this doesn't provide any additional security. + * + * The library provides a single method: + * \code{.cpp} + * #include + * + * auto metricRecorder = ...; + * auto factory = createKeyStoreFactory(metricRecorder); + * \endcode + * + * Metric recorder interface enables failure reporting in a form of metrics. The table summarizes activities: + * Activity | Description + * ---------------- | -------------------------- + * "PKCS11-ENCRYPT" | Data encryption operation. + * "PKCS11-DECRYPT" | Data decryption operation. + * + * The next table summarizes metric counters: + * Counter | Description + * -------------------- | --------------------------------------------------------------------------------------------- + * "FAILURE" | General purpose failure counter. This counter is always present if a failure occurrs. + * "DECRYPT_ERROR" | Decryption failure. This counter is present when decryption operation fails. + * "ENCRYPT_ERROR" | Encryption failure. This counter is present when encryption operation fails. + * "CHECKSUM_ERROR" | Checksum check error. This counter is present when supplied checksum doesn't match one in HSM. The failure indicates the key has been replaces. + * "GET_KEY_ERROR" | Key access failure. This counter indicates the key is no longer accessible. + * "GET_CHECKSUM_ERROR" | Checksum check error. This error indicates the checksum is not available. + * "EXTRACTABLE_KEY" | This counters indicate the key may have been compromized. + * + * \sa CryptoAPI + * \sa alexaClientSDK::acsdkPkcs11 + * \sa alexaClientSDK::acsdkPkcs11::test + * + * \namespace alexaClientSDK::acsdkPkcs11 + * \brief HSM interface implementation. + * \ingroup CryptoPKCS11 + * \sa CryptoPKCS11 + * + * \namespace alexaClientSDK::acsdkPkcs11::test + * \brief Test cases for \ref CryptoPKCS11 + * \ingroup CryptoPKCS11 + * \sa CryptoPKCS11 + */ diff --git a/core/Crypto/acsdkPkcs11/include/acsdkPkcs11/KeyStoreFactory.h b/core/Crypto/acsdkPkcs11/include/acsdkPkcs11/KeyStoreFactory.h new file mode 100644 index 0000000000..811b0aefc6 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/include/acsdkPkcs11/KeyStoreFactory.h @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_KEYSTOREFACTORY_H_ +#define ACSDKPKCS11_KEYSTOREFACTORY_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +using alexaClientSDK::avsCommon::utils::metrics::MetricRecorderInterface; + +/** + * @brief Create instance of @c KeyStoreInterface. + * + * Method creates key store factory instance backed by hardware security module. This method dynamically loads + * dependencies according to configuration. + * + * @param[in] metricRecorder Optional reference of @c MetricRecorderInterface for operational and error metrics. + * + * @return Key store reference or nullptr on error. + * + * @see alexaClientSDK::acsdkCryptoInterfaces::KeyStoreInterface + * @see alexaClientSDK::avsCommon::utils::metrics::MetricRecorderInterface + * @ingroup CryptoPKCS11 + */ +std::shared_ptr createKeyStore( + const std::shared_ptr& metricRecorder = nullptr) noexcept; + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +#endif // ACSDKPKCS11_KEYSTOREFACTORY_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/ErrorCleanupGuard.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/ErrorCleanupGuard.h new file mode 100644 index 0000000000..f5ca097b3b --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/ErrorCleanupGuard.h @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_ERRORCLEANUPGUARD_H_ +#define ACSDKPKCS11_PRIVATE_ERRORCLEANUPGUARD_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +/** + * @brief Error cleanup function on error. + * + * This object executes lambda on destruction only if success variable is false. + * + * @see alexaClientSDK::avsCommon::utils::error::FinallyGuard + */ +class ErrorCleanupGuard : public alexaClientSDK::avsCommon::utils::error::FinallyGuard { +public: + /** + * @brief Prepares lambda for execution. + * + * This method constructs @c FinallyGuard that will trigger @a successFlag check and optional @a cleanupFunction + * execution on destruction. + * + * @param[in] successFlag Flag to check if @a cleanupFunction needs to be executed. + * @param[in] cleanupFunction Function reference to execute if @a successFlag is false. + */ + inline ErrorCleanupGuard(bool& successFlag, std::function&& cleanupFunction) noexcept : + FinallyGuard{std::bind(executeCleanup, std::ref(successFlag), std::move(cleanupFunction))} { + } + +private: + /** + * @brief Executes lambda if flag is false. + * + * @param[in] successFlag Flag to check if @a cleanupFunction needs to be executed. + * @param[in] cleanupFunction Function reference to execute if @a successFlag is false. + */ + inline static void executeCleanup(bool& successFlag, const std::function& cleanupFunction) noexcept { + if (!successFlag) { + cleanupFunction(); + } + } +}; + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +#endif // ACSDKPKCS11_PRIVATE_ERRORCLEANUPGUARD_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/Logging.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/Logging.h new file mode 100644 index 0000000000..6ff0f0f75f --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/Logging.h @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_LOGGING_H_ +#define ACSDKPKCS11_PRIVATE_LOGGING_H_ + +#include + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + * @ingroup CryptoPKCS11 + * @private + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +#endif // ACSDKPKCS11_PRIVATE_LOGGING_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11API.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11API.h new file mode 100644 index 0000000000..55040e306b --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11API.h @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_PKCS11API_H_ +#define ACSDKPKCS11_PRIVATE_PKCS11API_H_ + +/// @addtogroup CryptoPKCS11 +/// @{ + +// Define platform-specific macros for PKCS#11 API (UNIX) +#define CK_PTR * +#define CK_DECLARE_FUNCTION(returnType, name) returnType name +#define CK_DECLARE_FUNCTION_POINTER(returnType, name) returnType(*name) +#define CK_CALLBACK_FUNCTION(returnType, name) returnType(*name) +#define NULL_PTR nullptr + +/// @} + +#ifdef _WIN32 +#pragma pack(push, cryptoki, 1) +#endif + +#include + +#ifdef _WIN32 +#pragma pack(pop, cryptoki) +#endif + +/// @brief Constant for object class initialization. +static constexpr CK_OBJECT_CLASS UNDEFINED_OBJECT_CLASS = static_cast(-1); + +/// @brief Constant for key type initialization. +static constexpr CK_KEY_TYPE UNDEFINED_KEY_TYPE = static_cast(-1); + +#endif // ACSDKPKCS11_PRIVATE_PKCS11API_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Config.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Config.h new file mode 100644 index 0000000000..cf678f90f6 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Config.h @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_PKCS11CONFIG_H_ +#define ACSDKPKCS11_PRIVATE_PKCS11CONFIG_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +class PKCS11Key; +class PKCS11Slot; +class PKCS11Session; + +/** + * @brief PKCS11 Platform Configuration. + * + * This class provides access to PKCS11 configuration. Te configuration file includes path to PKCS#11 module, token + * name, PIN, and default key alias to use with encryption function. + * + * The configuration file shall have restricted access to service account, that executes application. + * + * @sa CryptoPKCS11 + */ +class PKCS11Config { +public: + /** + * @brief Creates object. + * + * @return Object reference or nullptr on error. + */ + static std::shared_ptr create() noexcept; + + /** + * @brief Returns file path to PKCS11 library. + * + * @return Path to PKCS11 shared library object. + */ + std::string getLibraryPath() const noexcept; + + /** + * @brief Returns PIN for authentication. + * + * @return User PIN. + */ + std::string getUserPin() const noexcept; + + /** + * @brief Returns token name. + * + * @return Token name to use for key store operations. + */ + std::string getTokenName() const noexcept; + + /** + * @brief Returns main encryption key alias. + * + * @return Object name for the main encryption key to use. + */ + std::string getDefaultKeyName() const noexcept; + +private: + /// @brief Private constructor + PKCS11Config() noexcept; + + /** + * @brief Helper method to load configuration from platform settings. + * + * @return True if operation is successful. + */ + bool loadFromSettings() noexcept; + + /// Path to PKCS11 shared library object. + std::string m_libraryPath; + /// User PIN to use for token authentication + std::string m_userPin; + /// Token name + std::string m_tokenName; + /// Default main key name + std::string m_defaultKeyName; +}; + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +#endif // ACSDKPKCS11_PRIVATE_PKCS11CONFIG_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Functions.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Functions.h new file mode 100644 index 0000000000..537dd3a460 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Functions.h @@ -0,0 +1,129 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_PKCS11FUNCTIONS_H_ +#define ACSDKPKCS11_PRIVATE_PKCS11FUNCTIONS_H_ + +#include +#include +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +#include + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +class PKCS11Key; +class PKCS11Slot; +class PKCS11Session; +class PKCS11Token; + +/** + * @brief PKCS11 API Wrapper. + * + * This class manages library load, initialization, and slot operations. + * + * @ingroup CryptoPKCS11 + */ +class PKCS11Functions : public std::enable_shared_from_this { +public: + /** + * Creates object. + * @param libpath File path to PKCS11 library. + * @return + */ + static std::shared_ptr create(const std::string& libpath) noexcept; + + /** + * @brief Releases library. + */ + virtual ~PKCS11Functions() noexcept; + + /** + * @brief Lists available PKCS11 slots by type. + * + * @param[in] tokenPresent Flag, if list slots with tokens (when true), or all slots. + * @param[out] slots Discovered slot names. + * + * @return True if operation is successful. + */ + bool listSlots(bool tokenPresent, std::vector>& slots) noexcept; + + /** + * @brief Finds PKCS11 slot by name. + * + * @param[in] tokenName Token name. + * @param[out] slot Discovered slot or nullptr if slot with given name doesn't exist. + * + * @return True if operation is successful. If operation fails, the value of \a slot is unchanged. + */ + bool findSlotByTokenName(const std::string& tokenName, std::shared_ptr& slot) noexcept; + +private: + friend class PKCS11Key; + friend class PKCS11Session; + friend class PKCS11Token; + friend class PKCS11Slot; + + /// @brief Private constructor. + PKCS11Functions() noexcept; + + /** + * @brief Helper to initialize object and prepare for operations. + * + * @return True if opeation is successful. + */ + bool initialize() noexcept; + /** + * Helper to load PKCS11 library and discover function table. + * + * @param[in] libpath File path to PKCS11 interface library. + * + * @return True if operation is successful. + */ + bool loadLibraryAndGetFunctions(const std::string& libpath) noexcept; + + /** + * @brief Method to finalize operations and release PKCS11 module. + */ + void finalizeOperations() noexcept; + + /** + * @brief Helper to unload PKCS11 libary. + */ + void unloadLibrary() noexcept; + +#ifdef _WIN32 + /// @brief Loaded library handle. + HMODULE m_libraryHandle; +#else + /// @brief Loaded library handle. + void* m_libraryHandle; +#endif + /// @brief PKCS11 function table. + CK_FUNCTION_LIST* m_pkcs11Functions; +}; + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +#endif // ACSDKPKCS11_PRIVATE_PKCS11FUNCTIONS_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Key.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Key.h new file mode 100644 index 0000000000..44c5af76a6 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Key.h @@ -0,0 +1,139 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_PKCS11KEY_H_ +#define ACSDKPKCS11_PRIVATE_PKCS11KEY_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +class PKCS11Session; + +using alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType; + +/** + * @brief PKCS11 key object wrapper. + * + * This class wraps PKCS#11 key handle and related operations. + * @ingroup CryptoPKCS11 + */ +class PKCS11Key { +public: + /** + * @brief Create key object with parameters. + * + * @param[in] session Owner session. + * @param[in] keyHandle PKCS11 ket object handle. + */ + PKCS11Key(std::shared_ptr&& session, CK_OBJECT_HANDLE keyHandle) noexcept; + + /** + * @brief Method to check if key has a correct type and supports given algorithm type. + * + * @param[in] type Algorithm type. + * + * @return True if key supports given algorithm type, False on error or if key doesn't support the algorithm. + */ + bool isCompatible(AlgorithmType type) noexcept; + + /** + * @brief Method to query key attributes. + * + * This method queries key CKA_CHECKSUM (if it supported) and CKA_NEVER_EXTRACTABLE flags. + * + * @param[out] checksum Key checksum if it is available. The value can be empty if HSM doesn't support + * checksums. + * @param[out] neverExtractable Flag if the key has never been extracted. + * + * @return True on success, False on error. + */ + bool getAttributes(std::vector& checksum, bool& neverExtractable) noexcept; + + /** + * @brief Function to encrypt data with given parameters. + * + * @param[in] algorithmType Algorithm to use. + * @param[in] iv Initialization vector. + * @param[in] aad Additional authenticated data. + * @param[in] plaintext Unencrypted data. + * @param[out] ciphertext Encrypted data. + * @param[out] tag Message authentication code. + * + * @return True if operation is successful. + */ + bool encrypt( + AlgorithmType algorithmType, + const std::vector& iv, + const std::vector& aad, + const std::vector& plaintext, + std::vector& ciphertext, + std::vector& tag) noexcept; + + /** + * @brief Function to decrypt data with given parameters. + * + * @param[in] algorithmType Algorithm to use. + * @param[in] iv Initialization vector. + * @param[in] aad Additional authenticated data. + * @param[in] ciphertext Encrypted data. + * @param[in] tag Authentication tag. + * @param[out] plaintext Decrypted data. + * + * @return True if operation is successful. + */ + bool decrypt( + AlgorithmType algorithmType, + const std::vector& iv, + const std::vector& aad, + const std::vector& ciphertext, + const std::vector& tag, + std::vector& plaintext) noexcept; + + /** + * @brief Configure PKCS#11 mechanism according to parameters. + * + * @param[in] mechanismType Type of encryption. + * @param[in] iv Initialization vector. + * @param[in] aad Additional authenticaiton data. + * @param[out] params Mechanism parameters for PKCS#11 calls. + * @param[out] gcmParams GCM-specific parameters for PKCS#11 calls. + * + * @return True if operation is successful. + */ + bool configureMechanism( + CK_MECHANISM_TYPE mechanismType, + const std::vector& iv, + const std::vector& aad, + CK_MECHANISM& params, + CK_GCM_PARAMS& gcmParams) noexcept; + +private: + /// Owner session object. + std::shared_ptr m_session; + + /// PKCS11 key handle. + CK_OBJECT_HANDLE m_keyHandle; +}; + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +#endif // ACSDKPKCS11_PRIVATE_PKCS11KEY_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11KeyDescriptor.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11KeyDescriptor.h new file mode 100644 index 0000000000..ce9dff0116 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11KeyDescriptor.h @@ -0,0 +1,136 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_PKCS11KEYDESCRIPTOR_H_ +#define ACSDKPKCS11_PRIVATE_PKCS11KEYDESCRIPTOR_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +/** + * @brief Class to identify key object in HSM. + * + * HSM objects do not have unique parameters other than object ID. So several HSM objects may have the same label, but + * different types or the same label and type, and different size. + * + * This object provides criteria for looking up key objects in HSM. + */ +struct PKCS11KeyDescriptor { + /** + * @brief Key object label. + */ + std::string objectLabel; + + /** + * @brief Key object type. + * + * AES ciphers use @a CKK_AES. HMAC-SHA-256 digest may use @a CKK_GENERIC_SECRET or @a CKK_SHA256_HMAC. + */ + CK_KEY_TYPE keyType; + + /** + * @brief Key length in bytes. + */ + CK_ULONG keyLen; + + /** + * @brief Default move constructor. + * + * @param[in] arg Object to move values from. + */ + PKCS11KeyDescriptor(PKCS11KeyDescriptor&& arg) noexcept = default; + + /** + * @brief Create object with alias and encryption algorithm. + * + * @param[in] objectLabel Object label. + * @param[in] algorithmType Algorithm type. + */ + PKCS11KeyDescriptor(const std::string& objectLabel, acsdkCryptoInterfaces::AlgorithmType algorithmType) noexcept; + + /** + * @brief Create object with given parameters. + * + * @param[in] objectLabel Object label. + * @param[in] keyType PKCS#11 key type. + * @param[in] keyLen PKCS#11 key size. + */ + PKCS11KeyDescriptor(const std::string& objectLabel, CK_KEY_TYPE keyType, CK_ULONG keyLen) noexcept; + + /** + * @brief Maps algorithm type into key type and length + * + * @param[in] algorithmType Algorithm type. + * @param[out] keyType Key type + * @param[out] keyLen Key length. + * + * @return Returns true on success. + */ + static bool mapAlgorithmToKeyParams( + acsdkCryptoInterfaces::AlgorithmType algorithmType, + CK_KEY_TYPE& keyType, + CK_ULONG& keyLen); +}; + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +namespace std { + +/** + * @brief Hash support for PKCS11KeyDescriptor. + */ +template <> +struct hash { + /** + * @brief Compute hash code. + * + * @param[in] arg Key descriptor. + * + * @return Computed hash code. + */ + std::size_t operator()(const alexaClientSDK::acsdkPkcs11::PKCS11KeyDescriptor& arg) const noexcept; +}; + +/** + * @brief Comparison support PKCS11KeyDescriptor. + */ +template <> +struct equal_to { + /** + * @brief Compares two PKCS11KeyDescriptor instances. + * + * @param[in] arg1 First key descriptor to compare. + * @param[in] arg2 Second key descriptor to compare. + * + * @return True if instances are equal. + */ + bool operator()( + const alexaClientSDK::acsdkPkcs11::PKCS11KeyDescriptor& arg1, + const alexaClientSDK::acsdkPkcs11::PKCS11KeyDescriptor& arg2) const noexcept; +}; + +// Dumps PKCS11KeyDescriptor data into stream (for logging). +std::ostream& operator<<(std::ostream& out, const alexaClientSDK::acsdkPkcs11::PKCS11KeyDescriptor& arg) noexcept; + +} // namespace std + +#endif // ACSDKPKCS11_PRIVATE_PKCS11KEYDESCRIPTOR_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11KeyStore.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11KeyStore.h new file mode 100644 index 0000000000..8acbc17b4a --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11KeyStore.h @@ -0,0 +1,160 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_PKCS11KEYSTORE_H_ +#define ACSDKPKCS11_PRIVATE_PKCS11KEYSTORE_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +using alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType; +using alexaClientSDK::acsdkCryptoInterfaces::KeyStoreInterface; +using alexaClientSDK::avsCommon::utils::metrics::MetricRecorderInterface; + +/** + * @brief Key store implementation for PKCS11. + * + * This class implements features of @ref alexaClientSDK::acsdkCryptoInterfaces::KeyStoreInterface using PKCS#11 + * interface functions. + * + * @ingroup CryptoPKCS11 + */ +class PKCS11KeyStore : public KeyStoreInterface { +public: + /** + * @brief Creates key store. + * + * @param[in] metricRecorder Optional reference for metrics reporting. + * + * @return Object reference or nullptr on error. + */ + static std::shared_ptr create( + const std::shared_ptr& metricRecorder = nullptr) noexcept; + + /// @name KeyStoreInterface methods. + ///@{ + ~PKCS11KeyStore() noexcept override; + bool encrypt( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext) noexcept override; + bool encryptAE( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& aad, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext, + Tag& tag) noexcept override; + bool decrypt( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& ciphertext, + DataBlock& plaintext) noexcept override; + bool decryptAD( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& aad, + const DataBlock& ciphertext, + const Tag& tag, + DataBlock& plaintext) noexcept override; + bool getDefaultKeyAlias(std::string& keyAlias) noexcept override; + ///@} + +private: + /// @brief Private constructor. + PKCS11KeyStore(const std::shared_ptr& metricRecorder) noexcept; + + /// @brief Helper to initialize key store. + bool init() noexcept; + + /** + * @brief Locates key by given alias and type. + * + * @param[in] objectLabel Key alias. + * @param[in] type Key type. + * @return Key wrapper reference or nullptr on error. + */ + std::shared_ptr loadKey(const std::string& objectLabel, AlgorithmType type) noexcept; + + /** + * @brief Locates key by given criteria. + * + * HSM may contain several objects of the same type and label. Search criteria in @c PKCS11KeyDescriptor narrows + * down search results, so there are better chances to find what we actually look for. + * + * @param[in] descriptor Key descriptor with search criteria. + * + * @return Key wrapper reference or nullptr on error. + * + * @see PKCS11KeyDescriptor + */ + std::shared_ptr loadKeyLocked(PKCS11KeyDescriptor&& descriptor) noexcept; + + /** + * @brief Submit metrics. + * + * This method reports a counter increment for a given event name. Failure metrics increments two counters: one for + * given event, and one with a predefined name ("FAILURE"). + * + * @param[in] activity The activity name. + * @param[in] eventName The event name string. + * @param[in] count The metric value. + * @param[in] failure Flag, if FAILURE metric shall also be incremented. + */ + void submitMetric(const std::string& activity, const std::string& eventName, uint64_t count, bool failure) noexcept; + + /// Optional reference to metric recorder. + std::shared_ptr m_metricRecorder; + + /// PKCS11 functions reference + std::shared_ptr m_functions; + + /// PKCS11 session reference + std::shared_ptr m_session; + + /// Mutex to serialize key collection access. + std::mutex m_keysMutex; + + /// Map of key alias to PKCS11 key handles. + std::unordered_map> m_keys; + + /// Default key alias. + std::string m_defaultKeyAlias; +}; + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +#endif // ACSDKPKCS11_PRIVATE_PKCS11KEYSTORE_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Session.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Session.h new file mode 100644 index 0000000000..99e403c7f0 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Session.h @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_PKCS11SESSION_H_ +#define ACSDKPKCS11_PRIVATE_PKCS11SESSION_H_ + +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +class PKCS11Functions; +class PKCS11Key; +using alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType; + +/** + * @brief PKCS11 session wrapper. + * + * This class wraps PKCS#11 API session handle and related operations. + * @ingroup CryptoPKCS11 + */ +class PKCS11Session : public std::enable_shared_from_this { +public: + /// Public destructor (closes session). + ~PKCS11Session() noexcept; + + /** + * @brief Log into HSM. + * + * @param[in] userPin User PIN. + * + * @return True if operation is successful. + */ + bool logIn(const std::string& userPin) noexcept; + + /** + * @brief Log out from HSM. + * + * @return True if operation is successful. + */ + bool logOut() noexcept; + + /** + * @brief Loads existing key. + * + * @param[in] descriptor Key parameters. + * + * @return Key reference or nullptr on error. + */ + std::unique_ptr findKey(const PKCS11KeyDescriptor& descriptor) noexcept; + +private: + friend class PKCS11Key; + friend class PKCS11Slot; + + /** + * @brief Creates session object. + * + * @param[in] functions Owner object. + * @param[in] sessionHandle Session handle. + */ + PKCS11Session(const std::shared_ptr& functions, CK_SESSION_HANDLE sessionHandle) noexcept; + + /** + * @brief Closes session. + * + * @return True if operation is successful. + */ + bool closeSession() noexcept; + + /// Mutex object to ensure session calls are serialzied. + std::mutex m_mutex; + + /// Owner object reference. + std::shared_ptr m_functions; + + /// PKCS11 session handle. + CK_SESSION_HANDLE m_sessionHandle; +}; + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +#endif // ACSDKPKCS11_PRIVATE_PKCS11SESSION_H_ diff --git a/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Slot.h b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Slot.h new file mode 100644 index 0000000000..5ee29523ed --- /dev/null +++ b/core/Crypto/acsdkPkcs11/privateInclude/acsdkPkcs11/private/PKCS11Slot.h @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPKCS11_PRIVATE_PKCS11SLOT_H_ +#define ACSDKPKCS11_PRIVATE_PKCS11SLOT_H_ + +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +class PKCS11Functions; +class PKCS11Session; + +/** + * @brief PKCS11 slot wrapper. + * + * This class wraps PKCS#11 slot handle and related operations. + * @ingroup CryptoPKCS11 + */ +class PKCS11Slot { +public: + /** + * @brief Constructs slot object. + * + * @param[in] functions Owner with function table. + * @param[in] slotId PKCS11 slot identifier. + */ + PKCS11Slot(std::shared_ptr&& functions, CK_SLOT_ID slotId) noexcept; + + /** + * @brief Returns token name. + * + * @param[out] tokenName Token name. + * + * @return True if operation is successful. + */ + bool getTokenName(std::string& tokenName) noexcept; + + /** + * @brief Opens a new session for this slot. + * + * @return Session reference or nullptr on error. + */ + std::shared_ptr openSession() noexcept; + +private: + /// @brief Owner object. + std::shared_ptr m_functions; + + /// @brief Slot identifier + CK_SLOT_ID m_slotID; +}; + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +#endif // ACSDKPKCS11_PRIVATE_PKCS11SLOT_H_ diff --git a/core/Crypto/acsdkPkcs11/src/CMakeLists.txt b/core/Crypto/acsdkPkcs11/src/CMakeLists.txt new file mode 100644 index 0000000000..b38bfea407 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/CMakeLists.txt @@ -0,0 +1,30 @@ + +set(acsdkPkcs11_SOURCES + KeyStoreFactory.cpp + PKCS11Config.cpp + PKCS11Functions.cpp + PKCS11Key.cpp + PKCS11KeyDescriptor.cpp + PKCS11KeyStore.cpp + PKCS11Slot.cpp + PKCS11Session.cpp + ) + +if(WIN32) + list(APPEND acsdkPkcs11_SOURCES PKCS11FunctionsUwp.cpp) +else() + list(APPEND acsdkPkcs11_SOURCES PKCS11FunctionsPosix.cpp) +endif() + +add_library(acsdkPkcs11 ${acsdkPkcs11_SOURCES}) +target_compile_definitions(acsdkPkcs11 PRIVATE ACSDK_LOG_MODULE=acsdkPkcs11) +target_compile_definitions(acsdkPkcs11 PUBLIC HAS_ACSDK_PKCS11) +target_include_directories(acsdkPkcs11 PUBLIC "${acsdkPkcs11_SOURCE_DIR}/include") +target_include_directories(acsdkPkcs11 PRIVATE "${acsdkPkcs11_SOURCE_DIR}/privateInclude") +target_link_libraries(acsdkPkcs11 PUBLIC acsdkCryptoInterfaces AVSCommon) +target_link_libraries(acsdkPkcs11 PRIVATE pkcs11-api-2.40) +if(UNIX) + target_link_libraries(acsdkPkcs11 PRIVATE dl) +endif(UNIX) +# install target +asdk_install() diff --git a/core/Crypto/acsdkPkcs11/src/KeyStoreFactory.cpp b/core/Crypto/acsdkPkcs11/src/KeyStoreFactory.cpp new file mode 100644 index 0000000000..34725178ca --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/KeyStoreFactory.cpp @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkPkcs11 { + +using alexaClientSDK::avsCommon::utils::metrics::MetricRecorderInterface; + +std::shared_ptr createKeyStore( + const std::shared_ptr& metricRecorder) noexcept { + return PKCS11KeyStore::create(metricRecorder); +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/src/PKCS11Config.cpp b/core/Crypto/acsdkPkcs11/src/PKCS11Config.cpp new file mode 100644 index 0000000000..b2f5e2bc6d --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/PKCS11Config.cpp @@ -0,0 +1,89 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkPkcs11 { + +using namespace alexaClientSDK::avsCommon::utils::configuration; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"pkcs11::Config"}; + +/// @private +static const std::string PKCS11_MODULE_CONFIG_KEY{"pkcs11Module"}; + +std::shared_ptr PKCS11Config::create() noexcept { + std::shared_ptr config = std::shared_ptr(new PKCS11Config); + + if (!config->loadFromSettings()) { + ACSDK_ERROR(LX("configLoadingFailed")); + config.reset(); + } + + return config; +} + +PKCS11Config::PKCS11Config() noexcept { +} + +std::string PKCS11Config::getLibraryPath() const noexcept { + return m_libraryPath; +} + +std::string PKCS11Config::getUserPin() const noexcept { + return m_userPin; +} + +std::string PKCS11Config::getTokenName() const noexcept { + return m_tokenName; +} + +std::string PKCS11Config::getDefaultKeyName() const noexcept { + return m_defaultKeyName; +} + +bool PKCS11Config::loadFromSettings() noexcept { + auto configurationRoot = ConfigurationNode::getRoot()[PKCS11_MODULE_CONFIG_KEY]; + + if (!configurationRoot.getString("libraryPath", &m_libraryPath)) { + ACSDK_ERROR(LX("libraryPathMissing")); + return false; + } + + if (!configurationRoot.getString("userPin", &m_userPin)) { + ACSDK_ERROR(LX("userPinMissing")); + return false; + } + + if (!configurationRoot.getString("tokenName", &m_tokenName)) { + ACSDK_ERROR(LX("tokenNameMissing")); + return false; + } + + if (!configurationRoot.getString("defaultKeyName", &m_defaultKeyName)) { + ACSDK_ERROR(LX("defaultKeyNameMissing")); + return false; + } + + return true; +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/src/PKCS11Functions.cpp b/core/Crypto/acsdkPkcs11/src/PKCS11Functions.cpp new file mode 100644 index 0000000000..5f0a0e2836 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/PKCS11Functions.cpp @@ -0,0 +1,135 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkPkcs11 { + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"pkcs11::Functions"}; + +std::shared_ptr PKCS11Functions::create(const std::string& libpath) noexcept { + auto res = std::shared_ptr(new PKCS11Functions); + if (res->loadLibraryAndGetFunctions(libpath)) { + if (!res->initialize()) { + ACSDK_ERROR(LX("libraryInitFailed")); + res.reset(); + } + } else { + ACSDK_ERROR(LX("libraryLoadFailed").sensitive("path", libpath)); + res.reset(); + } + return res; +} + +PKCS11Functions::PKCS11Functions() noexcept : m_libraryHandle(nullptr), m_pkcs11Functions(nullptr) { +} + +PKCS11Functions::~PKCS11Functions() noexcept { + finalizeOperations(); + unloadLibrary(); +} + +bool PKCS11Functions::initialize() noexcept { + CK_C_INITIALIZE_ARGS initArgs; + memset(&initArgs, 0, sizeof(initArgs)); + + initArgs.flags = CKF_OS_LOCKING_OK; + + CK_RV rv = m_pkcs11Functions->C_Initialize(&initArgs); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("pkcs11InitFailed").d("CK_RV", rv)); + m_pkcs11Functions = nullptr; + return false; + } + + return true; +} + +void PKCS11Functions::finalizeOperations() noexcept { + if (m_pkcs11Functions) { + m_pkcs11Functions->C_Finalize(nullptr); + m_pkcs11Functions = nullptr; + } +} + +bool PKCS11Functions::listSlots(bool tokenPresent, std::vector>& slots) noexcept { + CK_BBOOL tokenPresentFlag = CK_FALSE; + CK_ULONG slotCount = 0; + CK_RV rv; + + if (tokenPresent) { + // CK_TRUE is defined as a signed constant, which causes a Fortify warning + tokenPresentFlag = static_cast(CK_TRUE); + } + + rv = m_pkcs11Functions->C_GetSlotList(tokenPresentFlag, nullptr, &slotCount); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("getSlotCountFailed").d("CK_RV", rv)); + return false; + } + + std::vector slotsIds; + slotsIds.resize(slotCount); + + rv = m_pkcs11Functions->C_GetSlotList(tokenPresentFlag, slotsIds.data(), &slotCount); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("getSlotListFailed").d("CK_RV", rv)); + return false; + } + + slots.clear(); + slots.reserve(slotCount); + + auto transform = [this](CK_SLOT_ID slotId) -> std::shared_ptr { + return std::shared_ptr(new PKCS11Slot(shared_from_this(), slotId)); + }; + std::transform(slotsIds.begin(), slotsIds.end(), std::back_inserter(slots), transform); + + return true; +} + +bool PKCS11Functions::findSlotByTokenName(const std::string& tokenName, std::shared_ptr& slot) noexcept { + std::vector> slots; + + if (!listSlots(true, slots)) { + ACSDK_ERROR(LX("listSlotsFailed")); + return false; + } + + auto name_matches = [&tokenName](const std::shared_ptr& slot) -> bool { + std::string name; + if (slot->getTokenName(name)) { + return name == tokenName; + } else { + return false; + } + }; + auto it = std::find_if(slots.begin(), slots.end(), name_matches); + if (it == slots.end()) { + ACSDK_ERROR(LX("slotNotFound").d("tokenName", tokenName)); + slot.reset(); + } else { + slot = *it; + } + return true; +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/src/PKCS11FunctionsPosix.cpp b/core/Crypto/acsdkPkcs11/src/PKCS11FunctionsPosix.cpp new file mode 100644 index 0000000000..3d55b98256 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/PKCS11FunctionsPosix.cpp @@ -0,0 +1,86 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"pkcs11::Functions"}; + +/** + * @brief Check if the path is absolute. + * + * @param[in] libPath Path to library. + * + * @return True if path is absolute. + * @private + */ +static bool isAbsolutePath(const std::string& libPath) noexcept { + return !libPath.empty() && '/' == libPath[0]; +} + +bool PKCS11Functions::loadLibraryAndGetFunctions(const std::string& libpath) noexcept { + if (libpath.empty()) { + m_libraryHandle = RTLD_DEFAULT; + } else { + if (!isAbsolutePath(libpath)) { + ACSDK_ERROR(LX("libraryLoadFailed").d("reason", "pathMustBeAbsolute")); + return false; + } + + m_libraryHandle = dlopen(libpath.c_str(), RTLD_NOW); + if (!m_libraryHandle) { + ACSDK_ERROR(LX("libraryLoadFailed").sensitive("libpath", libpath).d("errno", errno)); + return false; + } + } + bool success = false; + ErrorCleanupGuard unloadCleanup(success, std::bind(&PKCS11Functions::unloadLibrary, this)); + + CK_C_Initialize pFunc = reinterpret_cast(dlsym(m_libraryHandle, "C_GetFunctionList")); + if (!pFunc) { + ACSDK_ERROR(LX("functionListNotFound")); + return false; + } + + CK_RV rv = pFunc(&m_pkcs11Functions); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("functionListInitFailed").d("CK_RV", rv)); + m_pkcs11Functions = nullptr; + return false; + } + + success = true; + return success; +} + +void PKCS11Functions::unloadLibrary() noexcept { + if (m_libraryHandle) { + if (RTLD_DEFAULT != m_libraryHandle) { + dlclose(m_libraryHandle); + } + m_libraryHandle = nullptr; + } +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/src/PKCS11FunctionsUwp.cpp b/core/Crypto/acsdkPkcs11/src/PKCS11FunctionsUwp.cpp new file mode 100644 index 0000000000..7c910a35aa --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/PKCS11FunctionsUwp.cpp @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"pkcs11::Functions"}; + +/** + * @brief Check if the path is absolute. + * + * @param[in] libPath Path to library. + * + * @return True if path is absolute. + * @private + */ +static bool isAbsolutePath(const std::string& libpath) noexcept { + // File path is absolute, if it starts with a drive letter followed by colon, and then has path separator. + // Examples: c:/ or F:\. + return libpath.size() > 2u && ':' == libpath[1] && ('\\' == libpath[2] || '/' == libpath[2]); +} + +bool PKCS11Functions::loadLibraryAndGetFunctions(const std::string& libpath) noexcept { + if (!isAbsolutePath(libpath)) { + ACSDK_ERROR(LX("libraryLoadFailed").d("reason", "pathMustBeAbsolute")); + return false; + } +#if defined(UNICODE) + size_t wSize = 0; + if (::mbstowcs_s(&wSize, nullptr, 0, libpath.c_str(), libpath.size())) { + ACSDK_ERROR(LX("libraryLoadFailed").m("failedToEstimateSize")); + return false; + } + std::wstring libWPath; + libWPath.resize(wSize); + if (::mbstowcs_s(&wSize, &libWPath[0], libWPath.size(), libpath.c_str(), libpath.size())) { + ACSDK_ERROR(LX("libraryLoadFailed").m("failedToConvert")); + return false; + } +#endif +#if WINAPI_FAMILY == WINAPI_FAMILY_APP + m_libraryHandle = LoadPackagedLibrary(libWPath.c_str(), 0); +#elif defined(UNICODE) + m_libraryHandle = LoadLibraryEx(libWPath.c_str(), NULL, 0); +#else + m_libraryHandle = LoadLibraryEx(libpath.c_str(), NULL, 0); +#endif + if (!m_libraryHandle) { + ACSDK_ERROR(LX("libraryLoadFailed").sensitive("path", libpath).d("lastError", GetLastError())); + return false; + } + bool success = false; + ErrorCleanupGuard unloadCleanup(success, std::bind(&PKCS11Functions::unloadLibrary, this)); + + CK_C_Initialize pFunc = reinterpret_cast(GetProcAddress(m_libraryHandle, "C_GetFunctionList")); + if (!pFunc) { + ACSDK_ERROR(LX("functionListNotFound")); + return false; + } + + CK_RV rv = pFunc(&m_pkcs11Functions); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("functionListInitFailed").d("CK_RV", rv)); + m_pkcs11Functions = nullptr; + return false; + } + + success = true; + return success; +} + +void PKCS11Functions::unloadLibrary() noexcept { + if (m_libraryHandle) { + FreeLibrary(m_libraryHandle); + m_libraryHandle = nullptr; + } +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/src/PKCS11Key.cpp b/core/Crypto/acsdkPkcs11/src/PKCS11Key.cpp new file mode 100644 index 0000000000..4e13d67d7a --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/PKCS11Key.cpp @@ -0,0 +1,368 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"pkcs11::Key"}; + +using namespace alexaClientSDK::acsdkCryptoInterfaces; + +/// @private +static constexpr size_t AES_CBC_IV_SIZE = 16; +/// @private +static constexpr size_t AES_256_KEY_SIZE = 32; +/// @private +static constexpr size_t AES_128_KEY_SIZE = 16; +/// @private +static constexpr size_t AES_GCM_TAG_SIZE = 16; +/// @private +static constexpr size_t AES_GCM_IV_SIZE = 12; + +PKCS11Key::PKCS11Key(std::shared_ptr&& session, CK_OBJECT_HANDLE keyHandle) noexcept : + m_session(std::move(session)), + m_keyHandle(keyHandle) { +} + +bool PKCS11Key::isCompatible(AlgorithmType type) noexcept { + CK_OBJECT_CLASS actualObjectClass = UNDEFINED_OBJECT_CLASS; + CK_KEY_TYPE actualKeyType = UNDEFINED_KEY_TYPE; + CK_ULONG keyLengthBytes = 0; + + CK_ATTRIBUTE dataTemplate[] = { + {CKA_CLASS, &actualObjectClass, sizeof(actualObjectClass)}, + {CKA_KEY_TYPE, &actualKeyType, sizeof(actualKeyType)}, + {CKA_VALUE_LEN, &keyLengthBytes, sizeof(keyLengthBytes)}, + }; + + std::lock_guard lock(m_session->m_mutex); + + CK_RV rv = m_session->m_functions->m_pkcs11Functions->C_GetAttributeValue( + m_session->m_sessionHandle, m_keyHandle, dataTemplate, sizeof(dataTemplate) / sizeof(dataTemplate[0])); + + if (CKR_OK != rv) { + ACSDK_ERROR(LX("getAttributeValueFailed").d("CK_RV", rv)); + return false; + } + + ACSDK_INFO(LX("foundObject") + .d("objectClass", actualObjectClass) + .d("keyType", actualKeyType) + .d("valueLen", keyLengthBytes)); + + if (CKO_SECRET_KEY != actualObjectClass) { + ACSDK_ERROR(LX("objectClassMismatch").d("objectClass", actualObjectClass)); + return false; + } + + CK_KEY_TYPE expectedKeyType; + CK_ULONG expectedKeySize; + + switch (type) { + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_256_GCM: + expectedKeyType = CKK_AES; + expectedKeySize = AES_256_KEY_SIZE; + break; + case AlgorithmType::AES_128_CBC: + case AlgorithmType::AES_128_CBC_PAD: + case AlgorithmType::AES_128_GCM: + expectedKeyType = CKK_AES; + expectedKeySize = AES_128_KEY_SIZE; + break; + default: + return false; + } + + if (expectedKeyType != actualKeyType) { + ACSDK_ERROR(LX("keyTypeMismatch").d("keyType", actualKeyType)); + return false; + } + if (expectedKeySize != keyLengthBytes) { + ACSDK_ERROR(LX("keySizeMismatch").d("keySize", keyLengthBytes)); + return false; + } + + return true; +} + +bool PKCS11Key::getAttributes(std::vector& checksum, bool& neverExtractable) noexcept { + CK_BYTE actualChecksum[3] = {0, 0, 0}; + CK_BBOOL actualNeverExtractable = CK_FALSE; + CK_ATTRIBUTE dataTemplate[] = { + {CKA_CHECK_VALUE, &actualChecksum, sizeof(actualChecksum)}, + {CKA_NEVER_EXTRACTABLE, &actualNeverExtractable, sizeof(actualNeverExtractable)}, + }; + + std::lock_guard lock(m_session->m_mutex); + + CK_RV rv = m_session->m_functions->m_pkcs11Functions->C_GetAttributeValue( + m_session->m_sessionHandle, m_keyHandle, dataTemplate, sizeof(dataTemplate) / sizeof(dataTemplate[0])); + + if (CKR_OK != rv) { + ACSDK_ERROR(LX("getAttributeValueFailed").d("CK_RV", rv)); + return false; + } + + size_t checksumOffset = checksum.size(); + checksum.reserve(checksum.size() + checksumOffset); + checksum.insert(checksum.end(), actualChecksum, actualChecksum + sizeof(actualChecksum)); + neverExtractable = actualNeverExtractable != CK_FALSE; + + return true; +} + +bool PKCS11Key::encrypt( + AlgorithmType algorithmType, + const std::vector& iv, + const std::vector& aad, + const std::vector& plaintext, + std::vector& ciphertext, + std::vector& tag) noexcept { + std::lock_guard lock(m_session->m_mutex); + + size_t ivSize = 0; + CK_MECHANISM_TYPE mechanismType = 0; + bool useGcm = false; + + switch (algorithmType) { + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_128_CBC: + mechanismType = CKM_AES_CBC; + ivSize = AES_CBC_IV_SIZE; + break; + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_128_CBC_PAD: + mechanismType = CKM_AES_CBC_PAD; + ivSize = AES_CBC_IV_SIZE; + break; + case AlgorithmType::AES_256_GCM: + case AlgorithmType::AES_128_GCM: + mechanismType = CKM_AES_GCM; + ivSize = AES_GCM_IV_SIZE; + useGcm = true; + break; + default: + ACSDK_ERROR(LX("algorithmTypeError").d("algorithmType", algorithmType)); + return false; + } + if (iv.size() != ivSize) { + ACSDK_ERROR(LX("ivSizeError").d("size", iv.size())); + return false; + } + + CK_MECHANISM mechanism; + CK_GCM_PARAMS gcmParams; + + if (!configureMechanism(mechanismType, iv, aad, mechanism, gcmParams)) { + ACSDK_ERROR(LX("encryptFailed").d("reason", "configureError")); + return false; + } + + CK_RV rv; + + rv = m_session->m_functions->m_pkcs11Functions->C_EncryptInit(m_session->m_sessionHandle, &mechanism, m_keyHandle); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("encryptInitFailed").d("RC_RV", rv)); + return false; + } + + CK_ULONG ulEncryptedDataLen = 0; + rv = m_session->m_functions->m_pkcs11Functions->C_Encrypt( + m_session->m_sessionHandle, + const_cast(plaintext.data()), + static_cast(plaintext.size()), + nullptr, + &ulEncryptedDataLen); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("encryptGetSizeFailed").d("RC_RV", rv)); + return false; + } + + size_t ciphertextOffset = ciphertext.size(); + ciphertext.resize(ciphertextOffset + ulEncryptedDataLen); + + rv = m_session->m_functions->m_pkcs11Functions->C_Encrypt( + m_session->m_sessionHandle, + (CK_BYTE_PTR)plaintext.data(), + static_cast(plaintext.size()), + static_cast(ciphertext.data()) + ciphertextOffset, + &ulEncryptedDataLen); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("encryptFailed").d("RC_RV", rv)); + return false; + } + + if (useGcm && &ciphertext != &tag) { + // ciphertext and tag may be the same reference. If they are not, then remove tag into own reference. + + if (ciphertext.size() < AES_GCM_TAG_SIZE) { + ACSDK_ERROR(LX("encryptFailed").d("reason", "tooSmallCiphertext")); + return false; + } + + size_t tagOffset = tag.size(); + tag.reserve(tagOffset + AES_GCM_TAG_SIZE); + tag.insert(tag.end(), ciphertext.end() - AES_GCM_TAG_SIZE, ciphertext.end()); + ciphertext.resize(ciphertext.size() - AES_GCM_TAG_SIZE); + } + + return true; +} + +bool PKCS11Key::decrypt( + AlgorithmType algorithmType, + const std::vector& iv, + const std::vector& aad, + const std::vector& ciphertext, + const std::vector& tag, + std::vector& plaintext) noexcept { + std::lock_guard lock(m_session->m_mutex); + + size_t ivSize = 0; + CK_MECHANISM_TYPE mechanismType = 0; + bool useGcm = false; + + switch (algorithmType) { + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_128_CBC: + mechanismType = CKM_AES_CBC; + ivSize = AES_CBC_IV_SIZE; + break; + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_128_CBC_PAD: + mechanismType = CKM_AES_CBC_PAD; + ivSize = AES_CBC_IV_SIZE; + break; + case AlgorithmType::AES_256_GCM: + case AlgorithmType::AES_128_GCM: + mechanismType = CKM_AES_GCM; + ivSize = AES_GCM_IV_SIZE; + useGcm = true; + break; + default: + ACSDK_ERROR(LX("algorithmTypeError").d("algorithmType", algorithmType)); + return false; + } + if (iv.size() != ivSize) { + ACSDK_ERROR(LX("ivSizeError").d("size", iv.size())); + return false; + } + CK_MECHANISM mechanism = {mechanismType, const_cast(iv.data()), static_cast(iv.size())}; + CK_GCM_PARAMS gcmParams; + + if (!configureMechanism(mechanismType, iv, aad, mechanism, gcmParams)) { + ACSDK_ERROR(LX("decryptFailed").d("reason", "configureError")); + return false; + } + + CK_BYTE_PTR ciphertextPtr; + CK_ULONG ciphertextSize; + std::vector gcmTmp; + + if (useGcm) { + gcmTmp.reserve(ciphertext.size() + tag.size()); + gcmTmp.insert(gcmTmp.end(), ciphertext.cbegin(), ciphertext.cend()); + gcmTmp.insert(gcmTmp.end(), tag.cbegin(), tag.cend()); + ciphertextPtr = const_cast(gcmTmp.data()); + ciphertextSize = static_cast(gcmTmp.size()); + } else { + ciphertextPtr = const_cast(ciphertext.data()); + ciphertextSize = static_cast(ciphertext.size()); + } + + CK_ULONG decryptedSize = 0; + CK_RV rv; + + rv = m_session->m_functions->m_pkcs11Functions->C_DecryptInit(m_session->m_sessionHandle, &mechanism, m_keyHandle); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("decryptInitFailed").d("CK_RV", rv)); + return false; + } + + rv = m_session->m_functions->m_pkcs11Functions->C_Decrypt( + m_session->m_sessionHandle, ciphertextPtr, ciphertextSize, nullptr, &decryptedSize); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("decryptGetSizeFailed").d("CK_RV", rv)); + return false; + } + + size_t plaintextOffset = plaintext.size(); + plaintext.resize(plaintextOffset + decryptedSize); + + rv = m_session->m_functions->m_pkcs11Functions->C_Decrypt( + m_session->m_sessionHandle, + ciphertextPtr, + ciphertextSize, + static_cast(plaintext.data()) + plaintextOffset, + &decryptedSize); + + if (CKR_OK != rv) { + ACSDK_ERROR(LX("decryptFailed").d("CK_RV", rv)); + return false; + } + + plaintext.resize(plaintextOffset + decryptedSize); + + return true; +} + +bool PKCS11Key::configureMechanism( + CK_MECHANISM_TYPE mechanismType, + const std::vector& iv, + const std::vector& aad, + CK_MECHANISM& mechanism, + CK_GCM_PARAMS& gcmParams) noexcept { + std::memset(&mechanism, 0, sizeof(mechanism)); + if (CKM_AES_GCM == mechanismType) { + mechanism.mechanism = mechanismType; + mechanism.pParameter = &gcmParams; + mechanism.ulParameterLen = static_cast(sizeof(gcmParams)); + + std::memset(&gcmParams, 0, sizeof(gcmParams)); + gcmParams.pIv = const_cast(iv.data()); + gcmParams.ulIvLen = static_cast(iv.size()); + // Do not set ulIvBits as it not specified in API + // gcmParams.ulIvBits = iv.size() * CHAR_BIT; + gcmParams.pAAD = const_cast(aad.data()); + gcmParams.ulAADLen = static_cast(aad.size()); + gcmParams.ulTagBits = static_cast(AES_GCM_TAG_SIZE) * CHAR_BIT; + } else { + if (!aad.empty()) { + ACSDK_ERROR(LX("configureMechanismError").d("reason", "aadNotEmpty").d("mechanismType", mechanismType)); + return false; + } + + mechanism.mechanism = mechanismType; + mechanism.pParameter = const_cast(iv.data()); + mechanism.ulParameterLen = static_cast(iv.size()); + } + + return true; +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/src/PKCS11KeyDescriptor.cpp b/core/Crypto/acsdkPkcs11/src/PKCS11KeyDescriptor.cpp new file mode 100644 index 0000000000..8a6490889a --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/PKCS11KeyDescriptor.cpp @@ -0,0 +1,97 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkPkcs11 { + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"pkcs11::KeyDescriptor"}; + +using namespace alexaClientSDK::acsdkCryptoInterfaces; + +/// @private +static constexpr CK_ULONG AES_256_KEY_SIZE = 32; +/// @private +static constexpr CK_ULONG AES_128_KEY_SIZE = 16; + +bool PKCS11KeyDescriptor::mapAlgorithmToKeyParams(AlgorithmType algorithmType, CK_KEY_TYPE& keyType, CK_ULONG& keyLen) { + switch (algorithmType) { + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_256_GCM: + keyType = CKK_AES; + keyLen = AES_256_KEY_SIZE; + return true; + case AlgorithmType::AES_128_CBC: + case AlgorithmType::AES_128_CBC_PAD: + case AlgorithmType::AES_128_GCM: + keyType = CKK_AES; + keyLen = AES_128_KEY_SIZE; + return true; + default: + keyType = UNDEFINED_KEY_TYPE; + keyLen = 0; + return false; + } +} + +PKCS11KeyDescriptor::PKCS11KeyDescriptor(const std::string& objectLabel, AlgorithmType algorithmType) noexcept : + objectLabel{objectLabel} { + mapAlgorithmToKeyParams(algorithmType, keyType, keyLen); +} + +PKCS11KeyDescriptor::PKCS11KeyDescriptor( + const std::string& objectLabel, + CK_KEY_TYPE keyType, + CK_ULONG keyLen) noexcept : + objectLabel{objectLabel}, + keyType{keyType}, + keyLen{keyLen} { +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK + +namespace std { + +bool equal_to::operator()( + const alexaClientSDK::acsdkPkcs11::PKCS11KeyDescriptor& arg1, + const alexaClientSDK::acsdkPkcs11::PKCS11KeyDescriptor& arg2) const noexcept { + return &arg1 == &arg2 || + (arg1.keyType == arg2.keyType && arg1.keyLen == arg2.keyLen && arg1.objectLabel == arg2.objectLabel); +} + +std::size_t hash::operator()( + const alexaClientSDK::acsdkPkcs11::PKCS11KeyDescriptor& arg) const noexcept { + return hash()(arg.objectLabel) ^ hash()(arg.keyType) ^ hash()(arg.keyLen); +} + +std::ostream& operator<<(std::ostream& out, const alexaClientSDK::acsdkPkcs11::PKCS11KeyDescriptor& arg) noexcept { + out << arg.objectLabel << "/"; + switch (arg.keyType) { + case CKK_AES: + out << "AES"; + break; + default: + out << arg.keyType; + break; + } + return out << "/" << arg.keyLen; +} + +} // namespace std diff --git a/core/Crypto/acsdkPkcs11/src/PKCS11KeyStore.cpp b/core/Crypto/acsdkPkcs11/src/PKCS11KeyStore.cpp new file mode 100644 index 0000000000..02be425ff4 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/PKCS11KeyStore.cpp @@ -0,0 +1,283 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +using namespace alexaClientSDK::acsdkCryptoInterfaces; +using namespace alexaClientSDK::avsCommon::utils::metrics; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"pkcs11::KeyStore"}; + +/// The activity name for encryption. +/// @private +static const std::string ACTIVITY_ENCRYPT{"PKCS11-ENCRYPT"}; + +/// The activity name for decryption. +/// @private +static const std::string ACTIVITY_DECRYPT{"PKCS11-DECRYPT"}; + +/// Metric dimension for checksum errors. +/// @private +static const std::string CHECKSUM_ERROR{"CHECKSUM_ERROR"}; + +/// Metric dimension for get key errors. +/// @private +static const std::string GET_KEY_ERROR{"GET_KEY_ERROR"}; + +/// Metric dimension for get checksum errors. +/// @private +static const std::string GET_CHECKSUM_ERROR{"GET_CHECKSUM_ERROR"}; + +/// Metric dimension for decrypt errors. +/// @private +static const std::string DECRYPT_ERROR{"DECRYPT_ERROR"}; + +/// Metric dimension for encrypt errors. +/// @private +static const std::string ENCRYPT_ERROR{"ENCRYPT_ERROR"}; + +/// Metric dimension for failure metrics. +/// @private +static const std::string FAILURE{"FAILURE"}; + +/// Metric dimension for extractable key metrics. +/// @private +static const std::string EXTRACTABLE_KEY{"EXTRACTABLE_KEY"}; + +std::shared_ptr PKCS11KeyStore::create( + const std::shared_ptr& metricRecorder) noexcept { + ACSDK_INFO(LX("create")); + auto res = std::shared_ptr(new PKCS11KeyStore(metricRecorder)); + if (!res->init()) { + ACSDK_ERROR(LX("createFailed")); + res.reset(); + } + return res; +} + +PKCS11KeyStore::PKCS11KeyStore(const std::shared_ptr& metricRecorder) noexcept : + m_metricRecorder{metricRecorder} { +} + +bool PKCS11KeyStore::init() noexcept { + auto config = PKCS11Config::create(); + if (!config) { + ACSDK_ERROR(LX("configNull")); + return false; + } + m_defaultKeyAlias = config->getDefaultKeyName(); + + m_functions = PKCS11Functions::create(config->getLibraryPath()); + if (!m_functions) { + ACSDK_ERROR(LX("functionsLoadFailed")); + return false; + } + bool success = false; + ErrorCleanupGuard cleanupFunctions{success, [this]() -> void { m_functions.reset(); }}; + + std::shared_ptr slot; + if (!m_functions->findSlotByTokenName(config->getTokenName(), slot)) { + ACSDK_ERROR(LX("slotLookupFailed")); + return false; + } + if (!slot) { + ACSDK_ERROR(LX("slotIsNotFound")); + return false; + } + + m_session = slot->openSession(); + if (!m_session) { + ACSDK_ERROR(LX("openSessionFailed")); + return false; + } + + ErrorCleanupGuard cleanupSession{success, [this]() -> void { m_session.reset(); }}; + + if (!m_session->logIn(config->getUserPin())) { + ACSDK_ERROR(LX("logInFailed")); + return false; + } + success = true; + return success; +} + +PKCS11KeyStore::~PKCS11KeyStore() noexcept { +} + +std::shared_ptr PKCS11KeyStore::loadKey(const std::string& objectLabel, AlgorithmType type) noexcept { + std::lock_guard keyAccessLock(m_keysMutex); + return loadKeyLocked({objectLabel, type}); +} + +std::shared_ptr PKCS11KeyStore::loadKeyLocked(PKCS11KeyDescriptor&& descriptor) noexcept { + PKCS11KeyDescriptor localDescriptor{std::move(descriptor)}; + auto keyRef = m_keys.find(localDescriptor); + if (keyRef == m_keys.end()) { + auto key = m_session->findKey(localDescriptor); + if (!key) { + ACSDK_ERROR(LX("loadKeyLockedFailed").sensitive("descriptor", localDescriptor)); + return nullptr; + } + ACSDK_DEBUG0(LX("loadKeyLockedSuccess").sensitive("descriptor", localDescriptor)); + + std::shared_ptr result = std::move(key); + m_keys[std::move(localDescriptor)] = result; + return result; + } else { + return keyRef->second; + } +} + +bool PKCS11KeyStore::encrypt( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext) noexcept { + Tag tag; + DataBlock aad; + + return encryptAE(keyAlias, type, iv, aad, plaintext, checksum, ciphertext, tag); +} + +bool PKCS11KeyStore::encryptAE( + const std::string& keyAlias, + AlgorithmType type, + const IV& iv, + const DataBlock& aad, + const DataBlock& plaintext, + KeyChecksum& checksum, + DataBlock& ciphertext, + Tag& tag) noexcept { + auto key = loadKey(keyAlias, type); + if (!key) { + ACSDK_ERROR(LX("keyIsNotLoaded").sensitive("keyAlias", keyAlias)); + submitMetric(ACTIVITY_ENCRYPT, GET_KEY_ERROR, 1, true); + return false; + } + + bool neverExtractable; + if (!key->getAttributes(checksum, neverExtractable)) { + ACSDK_ERROR(LX("encryptFailed").sensitive("keyAlias", keyAlias).d("reason", "getAttributesFailed")); + submitMetric(ACTIVITY_ENCRYPT, GET_CHECKSUM_ERROR, 1, true); + return false; + } + + if (!neverExtractable) { + ACSDK_WARN(LX("encryptInsecure").sensitive("keyAlias", keyAlias).d("reason", "keyWasExtractable")); + submitMetric(ACTIVITY_ENCRYPT, EXTRACTABLE_KEY, 1, false); + } + + if (!key->encrypt(type, iv, aad, plaintext, ciphertext, tag)) { + submitMetric(ACTIVITY_ENCRYPT, ENCRYPT_ERROR, 1, true); + return false; + } + + return true; +} + +bool PKCS11KeyStore::decrypt( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& ciphertext, + DataBlock& plaintext) noexcept { + Tag tag; + DataBlock aad; + return decryptAD(keyAlias, type, checksum, iv, aad, ciphertext, tag, plaintext); +} + +bool PKCS11KeyStore::decryptAD( + const std::string& keyAlias, + AlgorithmType type, + const KeyChecksum& checksum, + const IV& iv, + const DataBlock& aad, + const DataBlock& ciphertext, + const Tag& tag, + DataBlock& plaintext) noexcept { + auto key = loadKey(keyAlias, type); + if (!key) { + ACSDK_ERROR(LX("keyIsNotLoaded").sensitive("keyAlias", keyAlias)); + submitMetric(ACTIVITY_DECRYPT, GET_KEY_ERROR, 1, true); + return false; + } + KeyChecksum keyChecksum; + bool neverExtractable = false; + if (!key->getAttributes(keyChecksum, neverExtractable)) { + ACSDK_ERROR(LX("decryptFailed").sensitive("keyAlias", keyAlias).d("reason", "getChecksumFailed")); + submitMetric(ACTIVITY_DECRYPT, GET_CHECKSUM_ERROR, 1, true); + return false; + } + if (!neverExtractable) { + ACSDK_WARN(LX("encryptInsecure").sensitive("keyAlias", keyAlias).d("reason", "keyWasExtractable")); + submitMetric(ACTIVITY_DECRYPT, EXTRACTABLE_KEY, 1, false); + } + if (checksum != keyChecksum) { + ACSDK_ERROR(LX("decryptFailed").sensitive("keyAlias", keyAlias).d("reason", "keyChecksumMismatch")); + submitMetric(ACTIVITY_DECRYPT, CHECKSUM_ERROR, 1, true); + return false; + } + + if (!key->decrypt(type, iv, aad, ciphertext, tag, plaintext)) { + submitMetric(ACTIVITY_DECRYPT, DECRYPT_ERROR, 1, true); + return false; + } + + return true; +} + +void PKCS11KeyStore::submitMetric( + const std::string& activity, + const std::string& eventName, + uint64_t count, + bool failure) noexcept { + if (!m_metricRecorder) { + return; + } + + auto metricEvent = MetricEventBuilder{} + .setActivityName(activity) + .addDataPoint(DataPointCounterBuilder{}.setName(eventName).increment(count).build()) + .addDataPoint(DataPointCounterBuilder{}.setName(FAILURE).increment(failure ? 1 : 0).build()) + .build(); + + if (!metricEvent) { + ACSDK_ERROR(LX("submitMetricFailed").d("reason", "metricEventNull")); + return; + } + + recordMetric(m_metricRecorder, metricEvent); +} + +bool PKCS11KeyStore::getDefaultKeyAlias(std::string& keyAlias) noexcept { + keyAlias = m_defaultKeyAlias; + return true; +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/src/PKCS11Session.cpp b/core/Crypto/acsdkPkcs11/src/PKCS11Session.cpp new file mode 100644 index 0000000000..aa69312e01 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/PKCS11Session.cpp @@ -0,0 +1,143 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkPkcs11 { + +using namespace alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"pkcs11::Session"}; + +PKCS11Session::PKCS11Session( + const std::shared_ptr& functions, + CK_SESSION_HANDLE sessionHandle) noexcept : + m_functions(functions), + m_sessionHandle(sessionHandle) { +} + +PKCS11Session::~PKCS11Session() noexcept { + closeSession(); +} + +bool PKCS11Session::logIn(const std::string& userPin) noexcept { + std::lock_guard lock(m_mutex); + + std::vector tmp; + tmp.reserve(userPin.size()); + std::transform(userPin.cbegin(), userPin.cend(), std::back_inserter(tmp), [](char ch) -> CK_UTF8CHAR { + return static_cast(ch); + }); + + CK_RV rv; + + rv = m_functions->m_pkcs11Functions->C_Login(m_sessionHandle, CKU_USER, &tmp[0], static_cast(tmp.size())); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("logInFailed").d("CK_RV", rv)); + return false; + } + + return true; +} + +bool PKCS11Session::logOut() noexcept { + std::lock_guard lock(m_mutex); + + CK_RV rc; + rc = m_functions->m_pkcs11Functions->C_Logout(m_sessionHandle); + + if (CKR_OK != rc) { + ACSDK_ERROR(LX("logOutFailed").d("CK_RV", rc)); + return false; + } + + return true; +} + +bool PKCS11Session::closeSession() noexcept { + std::lock_guard lock(m_mutex); + + if (CK_INVALID_HANDLE != m_sessionHandle) { + m_functions->m_pkcs11Functions->C_CloseSession(m_sessionHandle); + m_sessionHandle = CK_INVALID_HANDLE; + } + + return true; +} + +std::unique_ptr PKCS11Session::findKey(const PKCS11KeyDescriptor& descriptor) noexcept { + std::lock_guard lock(m_mutex); + + CK_RV rv; + + CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY; + std::vector objectMask; + objectMask.reserve(4); + objectMask.push_back({CKA_CLASS, &keyClass, static_cast(sizeof(keyClass))}); + objectMask.push_back({CKA_KEY_TYPE, + const_cast(&descriptor.keyType), + static_cast(sizeof(descriptor.keyType))}); + objectMask.push_back({CKA_LABEL, + const_cast(descriptor.objectLabel.data()), + static_cast(descriptor.objectLabel.size())}); + objectMask.push_back({CKA_VALUE_LEN, + const_cast(&descriptor.keyLen), + static_cast(sizeof(descriptor.keyLen))}); + + rv = m_functions->m_pkcs11Functions->C_FindObjectsInit( + m_sessionHandle, objectMask.data(), static_cast(objectMask.size())); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("findObjectsInitFailed").d("CK_RV", rv)); + return nullptr; + } + + CK_ULONG maxObjectCount = 1; + CK_ULONG objectCount = 1; + CK_OBJECT_HANDLE keyHandle = CK_INVALID_HANDLE; + + rv = m_functions->m_pkcs11Functions->C_FindObjects(m_sessionHandle, &keyHandle, maxObjectCount, &objectCount); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("findObjectsFailed").d("CK_RV", rv)); + rv = m_functions->m_pkcs11Functions->C_FindObjectsFinal(m_sessionHandle); + if (rv != CKR_OK) { + ACSDK_ERROR(LX("findObjectsFinalFailed").d("CK_RV", rv)); + } + return nullptr; + } + + rv = m_functions->m_pkcs11Functions->C_FindObjectsFinal(m_sessionHandle); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("findObjectsFinalFailed").d("CK_RV", rv)); + return nullptr; + } + + if (!objectCount) { + ACSDK_ERROR(LX("objectNotFound").sensitive("descriptor", descriptor)); + return nullptr; + } + + return std::unique_ptr(new PKCS11Key(shared_from_this(), keyHandle)); +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/src/PKCS11Slot.cpp b/core/Crypto/acsdkPkcs11/src/PKCS11Slot.cpp new file mode 100644 index 0000000000..32ee588eb6 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/src/PKCS11Slot.cpp @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"pkcs11::Slot"}; + +PKCS11Slot::PKCS11Slot(std::shared_ptr&& functions, CK_SLOT_ID slotId) noexcept : + m_functions(functions), + m_slotID(slotId) { +} + +bool PKCS11Slot::getTokenName(std::string& tokenName) noexcept { + CK_TOKEN_INFO tokenInfo; + CK_RV rv; + + rv = m_functions->m_pkcs11Functions->C_GetTokenInfo(m_slotID, &tokenInfo); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("getTokenInfoFailed").d("CK_RV", rv)); + return false; + } + + std::string tmp; + tmp.assign((const char*)&tokenInfo.label[0], (const char*)&tokenInfo.label[sizeof(tokenInfo.label)]); + + for (auto it = tmp.rbegin(); it != tmp.rend();) { + if (*it != ' ') { + break; + } else { + tmp.pop_back(); + it = tmp.rbegin(); + } + } + tokenName = tmp; + + return true; +} + +std::shared_ptr PKCS11Slot::openSession() noexcept { + CK_RV rv; + + CK_SESSION_HANDLE sessionHandle; + CK_FLAGS flags = CKF_SERIAL_SESSION | CKF_RW_SESSION; + + auto session = std::shared_ptr(new PKCS11Session(m_functions, CK_INVALID_HANDLE)); + + rv = m_functions->m_pkcs11Functions->C_OpenSession(m_slotID, flags, session.get(), nullptr, &sessionHandle); + if (CKR_OK != rv) { + ACSDK_ERROR(LX("openSessionFailed").d("CK_RV", rv)); + return nullptr; + } + session->m_sessionHandle = sessionHandle; + + return session; +} + +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/test/CMakeLists.txt b/core/Crypto/acsdkPkcs11/test/CMakeLists.txt new file mode 100644 index 0000000000..68621ddc4f --- /dev/null +++ b/core/Crypto/acsdkPkcs11/test/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +set(INCLUDE_PATH + "${acsdkPkcs11_SOURCE_DIR}/include" + "${acsdkPkcs11_SOURCE_DIR}/privateInclude" + "${PKCS11_SOURCE_DIR}" + ) + +add_definitions( + -DACSDK_LOG_MODULE=acsdkPkcs11Test + -DPKCS11_LIBRARY="${PKCS11_TEST_LIBRARY}" + -DPKCS11_PIN="${PKCS11_TEST_USER_PIN}" + -DPKCS11_KEY_NAME="${PKCS11_TEST_MAIN_KEY_ALIAS}" + -DPKCS11_TOKEN_NAME="${PKCS11_TEST_TOKEN_NAME}" +) + +discover_unit_tests("${INCLUDE_PATH}" "acsdkPkcs11;pkcs11-api;AVSCommon;acsdkCrypto;acsdkPkcs11Stubs" ".") diff --git a/core/Crypto/acsdkPkcs11/test/ErrorCleanupGuardTest.cpp b/core/Crypto/acsdkPkcs11/test/ErrorCleanupGuardTest.cpp new file mode 100644 index 0000000000..98cc1b064b --- /dev/null +++ b/core/Crypto/acsdkPkcs11/test/ErrorCleanupGuardTest.cpp @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkPkcs11 { +namespace test { + +using namespace ::testing; + +TEST(ErrorCleanupGuardTest, test_executeOnFailure) { + bool successFlag = false; + bool executed = false; + { + ErrorCleanupGuard guard(successFlag, [&]() -> void { executed = true; }); + ASSERT_FALSE(executed); + } + ASSERT_TRUE(executed); +} + +TEST(ErrorCleanupGuardTest, test_executeOnSuccess) { + bool successFlag = false; + bool executed = false; + { + ErrorCleanupGuard guard(successFlag, [&]() -> void { executed = true; }); + ASSERT_FALSE(executed); + successFlag = true; + } + ASSERT_FALSE(executed); +} + +} // namespace test +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/test/PKCS11ConfigTest.cpp b/core/Crypto/acsdkPkcs11/test/PKCS11ConfigTest.cpp new file mode 100644 index 0000000000..4bede69470 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/test/PKCS11ConfigTest.cpp @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::avsCommon::utils::configuration; + +/// @private +// clang-format off +static const std::string JSON_CONFIG = + "{" + "\"pkcs11Module\":{" + "\"libraryPath\":\"library.so\"," + "\"tokenName\":\"ACSDK\"," + "\"userPin\":\"9999\"," + "\"defaultKeyName\":\"mainKey\"" + "}" + "}"; +// clang-format on + +TEST(PKCS11ConfigTest, test_defaultConfig) { + ConfigurationNode::uninitialize(); + std::shared_ptr ss = std::make_shared(JSON_CONFIG); + ConfigurationNode::initialize({ss}); + + auto config = PKCS11Config::create(); + ASSERT_NE(nullptr, config); + ASSERT_EQ("mainKey", config->getDefaultKeyName()); + ASSERT_EQ("library.so", config->getLibraryPath()); + ASSERT_EQ("ACSDK", config->getTokenName()); + ASSERT_EQ("9999", config->getUserPin()); +} + +} // namespace test +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/test/PKCS11FunctionsTest.cpp b/core/Crypto/acsdkPkcs11/test/PKCS11FunctionsTest.cpp new file mode 100644 index 0000000000..792a4cced2 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/test/PKCS11FunctionsTest.cpp @@ -0,0 +1,72 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { +namespace test { + +using namespace ::testing; + +/// Test that the constructor with a nullptr doesn't segfault. +TEST(PKCS11FunctionsTest, test_badFunction) { + auto functions = PKCS11Functions::create("/lib_doesnt_exist.so"); + ASSERT_EQ(nullptr, functions); +} + +TEST(PKCS11FunctionsTest, test_initHsm) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); +} + +TEST(PKCS11FunctionsTest, test_listSlotsNoTokens) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::vector > slots; + ASSERT_TRUE(functions->listSlots(false, slots)); +} + +TEST(PKCS11FunctionsTest, test_listSlotsWithTokens) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::vector > slots; + ASSERT_TRUE(functions->listSlots(true, slots)); + ASSERT_FALSE(slots.empty()); +} + +TEST(PKCS11FunctionsTest, test_findTestSlot) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); +} + +TEST(PKCS11FunctionsTest, test_findOtherSlot) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName("ACSDK-ERR", slot)); + ASSERT_EQ(nullptr, slot); +} + +} // namespace test +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/test/PKCS11KeyStoreTest.cpp b/core/Crypto/acsdkPkcs11/test/PKCS11KeyStoreTest.cpp new file mode 100644 index 0000000000..7289234d89 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/test/PKCS11KeyStoreTest.cpp @@ -0,0 +1,124 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::avsCommon::utils::configuration; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// @private +static const KeyStoreInterface::IV + IV{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; +/// @private +static const KeyStoreInterface::DataBlock PLAINTEXT{ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'i', 'o', 'p', 'q', 'r', 's', 't', 'u', +}; +/// @private +static constexpr AlgorithmType BAD_ALGORITHM_TYPE = AlgorithmType(0); + +/// @private +// clang-format off +static const std::string JSON_TEST_CONFIG = + "{\"pkcs11Module\":{\"libraryPath\":\"" PKCS11_LIBRARY "\",\"tokenName\":\"" PKCS11_TOKEN_NAME + "\"," + "\"userPin\":\"" PKCS11_PIN "\",\"defaultKeyName\":\"" PKCS11_KEY_NAME "\"}}"; +// clang-format on + +/// @private +static void initConfig() { + ConfigurationNode::uninitialize(); + std::shared_ptr ss = std::make_shared(JSON_TEST_CONFIG); + ConfigurationNode::initialize({ss}); +} + +TEST(PKCS11KeyStoreTest, test_create) { + initConfig(); + auto keyStore = PKCS11KeyStore::create(); + ASSERT_NE(nullptr, keyStore); +} + +TEST(PKCS11KeyStoreTest, test_createBadConfig) { + ConfigurationNode::uninitialize(); + auto keyStore = PKCS11KeyStore::create(); + ASSERT_EQ(nullptr, keyStore); +} + +TEST(PKCS11KeyStoreTest, test_encryptDecrypt) { + initConfig(); + auto keyStore = PKCS11KeyStore::create(); + ASSERT_NE(nullptr, keyStore); + KeyStoreInterface::DataBlock ciphertext; + KeyStoreInterface::KeyChecksum checksum; + + ASSERT_TRUE(keyStore->encrypt(PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC, IV, PLAINTEXT, checksum, ciphertext)); + ASSERT_NE(PLAINTEXT, ciphertext); + KeyStoreInterface::DataBlock plaintext; + ASSERT_TRUE(keyStore->decrypt(PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC, checksum, IV, ciphertext, plaintext)); + ASSERT_EQ(PLAINTEXT, plaintext); +} + +TEST(PKCS11KeyStoreTest, test_encryptWithWrongAlgorithm) { + initConfig(); + auto keyStore = PKCS11KeyStore::create(); + ASSERT_NE(nullptr, keyStore); + KeyStoreInterface::DataBlock ciphertext; + KeyStoreInterface::KeyChecksum checksum; + ASSERT_FALSE(keyStore->encrypt(PKCS11_KEY_NAME, BAD_ALGORITHM_TYPE, IV, PLAINTEXT, checksum, ciphertext)); +} + +TEST(PKCS11KeyStoreTest, test_decryptWithWrongAlgorithm) { + initConfig(); + auto keyStore = PKCS11KeyStore::create(); + ASSERT_NE(nullptr, keyStore); + KeyStoreInterface::DataBlock ciphertext; + KeyStoreInterface::KeyChecksum checksum; + ASSERT_TRUE(keyStore->encrypt(PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC, IV, PLAINTEXT, checksum, ciphertext)); + + ASSERT_NE(PLAINTEXT, ciphertext); + KeyStoreInterface::DataBlock plaintext; + ASSERT_FALSE(keyStore->decrypt(PKCS11_KEY_NAME, BAD_ALGORITHM_TYPE, IV, checksum, ciphertext, plaintext)); +} + +TEST(PKCS11KeyStoreTest, test_createOrLoadKeyTwiceUsesTheSameKey) { + initConfig(); + auto keyStore = PKCS11KeyStore::create(); + ASSERT_NE(nullptr, keyStore); + KeyStoreInterface::DataBlock ciphertext1; + KeyStoreInterface::KeyChecksum checksum1; + ASSERT_TRUE(keyStore->encrypt(PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC, IV, PLAINTEXT, checksum1, ciphertext1)); + KeyStoreInterface::DataBlock ciphertext2; + KeyStoreInterface::KeyChecksum checksum2; + ASSERT_TRUE(keyStore->encrypt(PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC, IV, PLAINTEXT, checksum2, ciphertext2)); + ASSERT_EQ(ciphertext1, ciphertext2); + ASSERT_EQ(checksum1, checksum2); +} + +} // namespace test +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/test/PKCS11KeyTest.cpp b/core/Crypto/acsdkPkcs11/test/PKCS11KeyTest.cpp new file mode 100644 index 0000000000..5abf524387 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/test/PKCS11KeyTest.cpp @@ -0,0 +1,177 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// @private +static const KeyStoreInterface::IV + IV{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; +/// @private +static const KeyStoreInterface::IV IV_GCM{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}; +/// @private +static const KeyStoreInterface::DataBlock PLAINTEXT{ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'i', 'o', 'p', 'q', 'r', 's', 't', 'u', +}; + +TEST(PKCS11KeyTest, test_encryptDecryptAes256Cbc) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); + auto session = slot->openSession(); + ASSERT_NE(nullptr, session); + ASSERT_TRUE(session->logIn(PKCS11_PIN)); + + auto key = session->findKey({PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC}); + ASSERT_NE(nullptr, key); + + KeyStoreInterface::Tag tag; + KeyStoreInterface::DataBlock aad; + + // Encrypt + KeyStoreInterface::DataBlock ciphertext; + ASSERT_TRUE(key->encrypt(AlgorithmType::AES_256_CBC, IV, aad, PLAINTEXT, ciphertext, tag)); + + EXPECT_NE(PLAINTEXT, ciphertext); + + // Decrypt + KeyStoreInterface::DataBlock plaintext; + ASSERT_TRUE(key->decrypt(AlgorithmType::AES_256_CBC, IV, aad, ciphertext, tag, plaintext)); + + EXPECT_EQ(PLAINTEXT, plaintext); + key.reset(); + ASSERT_TRUE(session->logOut()); +} + +TEST(PKCS11KeyTest, test_encryptDecryptErrors) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); + auto session = slot->openSession(); + ASSERT_NE(nullptr, session); + ASSERT_TRUE(session->logIn(PKCS11_PIN)); + + auto key = session->findKey({PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC}); + ASSERT_NE(nullptr, key); + + KeyStoreInterface::Tag tag; + KeyStoreInterface::DataBlock aad; + + // Encrypt with bad IV + std::vector output; + ASSERT_FALSE(key->encrypt(AlgorithmType::AES_256_CBC, aad, {}, PLAINTEXT, output, tag)); + + // Decrypt with bad IV + ASSERT_FALSE(key->decrypt(AlgorithmType::AES_256_CBC, aad, {}, PLAINTEXT, output, tag)); +} + +TEST(PKCS11KeyTest, test_encryptDecryptAes256CbcPad) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); + auto session = slot->openSession(); + ASSERT_NE(nullptr, session); + ASSERT_TRUE(session->logIn(PKCS11_PIN)); + auto key = session->findKey({PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC_PAD}); + ASSERT_NE(nullptr, key); + + KeyStoreInterface::Tag tag; + KeyStoreInterface::DataBlock aad; + + // Encrypt + KeyStoreInterface::DataBlock ciphertext; + ASSERT_TRUE(key->encrypt(AlgorithmType::AES_256_CBC_PAD, IV, aad, PLAINTEXT, ciphertext, tag)); + EXPECT_NE(PLAINTEXT, ciphertext); + + // Decrypt + KeyStoreInterface::DataBlock plaintext; + ASSERT_TRUE(key->decrypt(AlgorithmType::AES_256_CBC_PAD, IV, aad, ciphertext, tag, plaintext)); + EXPECT_EQ(PLAINTEXT, plaintext); + + ASSERT_TRUE(session->logOut()); +} + +TEST(PKCS11KeyTest, test_encryptDecryptAes256Gcm) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); + auto session = slot->openSession(); + ASSERT_NE(nullptr, session); + ASSERT_TRUE(session->logIn(PKCS11_PIN)); + auto key = session->findKey({PKCS11_KEY_NAME, AlgorithmType::AES_256_GCM}); + ASSERT_NE(nullptr, key); + + KeyStoreInterface::Tag tag; + KeyStoreInterface::DataBlock aad{0, 1, 2}; + + // Encrypt + KeyStoreInterface::DataBlock ciphertext; + ASSERT_TRUE(key->encrypt(AlgorithmType::AES_256_GCM, IV_GCM, aad, PLAINTEXT, ciphertext, tag)); + EXPECT_NE(PLAINTEXT, ciphertext); + ASSERT_EQ(16u, tag.size()); + + // Decrypt + KeyStoreInterface::DataBlock plaintext; + ASSERT_TRUE(key->decrypt(AlgorithmType::AES_256_GCM, IV_GCM, aad, ciphertext, tag, plaintext)); + EXPECT_EQ(PLAINTEXT, plaintext); + + ASSERT_TRUE(session->logOut()); +} + +TEST(PKCS11KeyTest, test_getKeyAttributes) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); + auto session = slot->openSession(); + ASSERT_NE(nullptr, session); + ASSERT_TRUE(session->logIn(PKCS11_PIN)); + auto key = session->findKey({PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC_PAD}); + ASSERT_NE(nullptr, key); + KeyStoreInterface::KeyChecksum checksum; + bool neverExtractable = false; + ASSERT_TRUE(key->getAttributes(checksum, neverExtractable)); + ASSERT_EQ(3u, checksum.size()); + ASSERT_TRUE(neverExtractable); +} + +} // namespace test +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/test/PKCS11SessionTest.cpp b/core/Crypto/acsdkPkcs11/test/PKCS11SessionTest.cpp new file mode 100644 index 0000000000..acbc0c7d36 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/test/PKCS11SessionTest.cpp @@ -0,0 +1,65 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkPkcs11 { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// Test that the constructor with a nullptr doesn't segfault. +TEST(PKCS11SessionTest, test_loginLogout) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); + auto session = slot->openSession(); + ASSERT_NE(nullptr, session); + + ASSERT_TRUE(session->logIn(PKCS11_PIN)); + ASSERT_TRUE(session->logOut()); +} + +TEST(PKCS11SessionTest, test_keySessionOps) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); + auto session = slot->openSession(); + ASSERT_NE(nullptr, session); + + ASSERT_EQ(true, session->logIn(PKCS11_PIN)); + + // Delete test key if it exists + auto key = session->findKey({PKCS11_KEY_NAME, AlgorithmType::AES_256_CBC}); + ASSERT_NE(nullptr, key); + ASSERT_TRUE(session->logOut()); +} + +} // namespace test +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/test/PKCS11SlotTest.cpp b/core/Crypto/acsdkPkcs11/test/PKCS11SlotTest.cpp new file mode 100644 index 0000000000..cfb59a13cc --- /dev/null +++ b/core/Crypto/acsdkPkcs11/test/PKCS11SlotTest.cpp @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkPkcs11 { +namespace test { + +using namespace ::testing; + +/// Test that the constructor with a nullptr doesn't segfault. +TEST(PKCS11SlotTest, test_findSlot) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); + std::string tokenName; + ASSERT_TRUE(slot->getTokenName(tokenName)); + ASSERT_EQ(PKCS11_TOKEN_NAME, tokenName); +} + +TEST(PKCS11SlotTest, test_openSession) { + auto functions = PKCS11Functions::create(PKCS11_LIBRARY); + ASSERT_NE(nullptr, functions); + std::shared_ptr slot; + ASSERT_TRUE(functions->findSlotByTokenName(PKCS11_TOKEN_NAME, slot)); + ASSERT_NE(nullptr, slot); + auto session = slot->openSession(); + ASSERT_NE(nullptr, session); +} + +} // namespace test +} // namespace acsdkPkcs11 +} // namespace alexaClientSDK diff --git a/core/Crypto/acsdkPkcs11/testStubs/CMakeLists.txt b/core/Crypto/acsdkPkcs11/testStubs/CMakeLists.txt new file mode 100644 index 0000000000..0a69e8d225 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/testStubs/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(acsdkPkcs11Stubs LANGUAGES CXX) + +# PKCS11 requires code is loaded dynamically. +add_library(acsdkPkcs11Stubs SHARED src/Pkcs11Stubs.cpp) +target_compile_definitions(acsdkPkcs11Stubs PRIVATE ACSDK_LOG_MODULE=acsdkPkcs11Stubs) +target_link_libraries(acsdkPkcs11Stubs PRIVATE pkcs11-api-2.40 acsdkCrypto) + diff --git a/core/Crypto/acsdkPkcs11/testStubs/doc/CryptoPKCS11Stubs.dox b/core/Crypto/acsdkPkcs11/testStubs/doc/CryptoPKCS11Stubs.dox new file mode 100644 index 0000000000..b92c366ec5 --- /dev/null +++ b/core/Crypto/acsdkPkcs11/testStubs/doc/CryptoPKCS11Stubs.dox @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * @defgroup CryptoPKCS11Stubs Test Stubs for PKCS#11 Library + * @brief HSM function stubs for @a CryptoAPI. + * + * This module provide bare-bone stub functionality subset from PKCS#11 specification. Only few functions are + * implemented, and code can expect the following: + * - There is only one hardcoded slot with ACSDK token name. + * - User PIN is hardcoded to 1234 value. + * - There are two object that corresponds to AES-256 and AES-128 keys with TEST_KEY name. + * - Code supports AES encryption and decryption operations using @ref CryptoIMPL. + * + * This is a test support module, and must not be used either for integration tests or in production. + * + * @sa CryptoAPI + * @sa CryptoPKCS11 + * @sa CryptoIMPL + */ diff --git a/core/Crypto/acsdkPkcs11/testStubs/src/Pkcs11Stubs.cpp b/core/Crypto/acsdkPkcs11/testStubs/src/Pkcs11Stubs.cpp new file mode 100644 index 0000000000..880e7ae33c --- /dev/null +++ b/core/Crypto/acsdkPkcs11/testStubs/src/Pkcs11Stubs.cpp @@ -0,0 +1,1067 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + * @private + * @ingroup CryptoPKCS11Stubs + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// @addtogroup CryptoPKCS11Stubs +/// @{ +// Define platform-specific macros for PKCS#11 API (UNIX) +#define CK_PTR * +#define CK_DECLARE_FUNCTION(returnType, name) returnType name +#define CK_DECLARE_FUNCTION_POINTER(returnType, name) returnType(*name) +#define CK_CALLBACK_FUNCTION(returnType, name) returnType(*name) +#define NULL_PTR nullptr +/// @} + +#ifdef _WIN32 +#pragma pack(push, cryptoki, 1) +#endif +#include +#ifdef _WIN32 +#pragma pack(pop, cryptoki) +#endif + +#include +#include + +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// @addtogroup CryptoPKCS11Stubs +/// @{ + +/// @private +static const std::string TAG{"pkcs11:HSMStub"}; + +/** + * @brief PKCS11 function list table. + * + * This table is returned to PKCS11 client from @a C_Initialize call. + */ +static CK_FUNCTION_LIST FUNCTION_LIST{ + {2, 40}, + C_Initialize, // C_Initialize + C_Finalize, // C_Finalize + nullptr, // C_GetInfo + C_GetFunctionList, // C_GetFunctionList + C_GetSlotList, // C_GetSlotList + nullptr, // C_GetSlotInfo + C_GetTokenInfo, // C_GetTokenInfo + nullptr, // C_GetMechanismList + nullptr, // C_GetMechanismInfo + nullptr, // C_InitToken + nullptr, // C_InitPin + nullptr, // C_SetPin + C_OpenSession, // C_OpenSession + C_CloseSession, // C_CloseSession + nullptr, // C_CloseAllSessions + nullptr, // C_GetSessionInfo + nullptr, // C_GetOperationState + nullptr, // C_SetOperationState + C_Login, // C_Login + C_Logout, // C_Logout + nullptr, // C_CreateObject + nullptr, // C_CopyObject + nullptr, // C_DestroyObject + nullptr, // C_GetObjectSize + C_GetAttributeValue, // C_GetAttributeValue + nullptr, // C_SetAttributeValue + C_FindObjectsInit, // C_FindObjectsInit + C_FindObjects, // C_FindObjects + C_FindObjectsFinal, // C_FindObjectsFinal + C_EncryptInit, // C_EncryptInit + C_Encrypt, // C_Encrypt + nullptr, // C_EncryptUpdate + nullptr, // C_EncryptFinal + C_DecryptInit, // C_DecryptInit + C_Decrypt, // C_Decrypt + nullptr, // C_DecryptUpdate + nullptr, // C_DecryptFinal + nullptr, // C_DigestInit + nullptr, // C_Digest + nullptr, // C_DigestUpdate + nullptr, // C_DigestKey + nullptr, // C_DigestFinal + nullptr, // C_SignInit + nullptr, // C_Sign + nullptr, // C_SignUpdate + nullptr, // C_SignFinal + nullptr, // C_SignRecoverInit + nullptr, // C_SignRecover + nullptr, // C_VerifyInit + nullptr, // C_Verify + nullptr, // C_VerifyUpdate + nullptr, // C_VerifyFinal + nullptr, // C_VerifyRecoverInit + nullptr, // C_VerifyRecover + nullptr, // C_DigestEncryptUpdate + nullptr, // C_DecryptDigestUpdate + nullptr, // C_SignEncryptUpdate + nullptr, // C_DecryptVerifyUpdate + nullptr, // C_GenerateKey + nullptr, // C_GenerateKeyPair + nullptr, // C_WrapKey + nullptr, // C_UnwrapKey + nullptr, // C_DeriveKey + nullptr, // C_SeedRandom + nullptr, // C_GenerateRandom + nullptr, // C_GetFunctionStatus + nullptr, // C_CancelFunction + nullptr, // C_WaitForSlotEvent +}; + +/// Constant to indicate unspecified value for object class attribute. +static constexpr CK_OBJECT_CLASS UNSPECIFIED_OBJECT_CLASS = (CK_OBJECT_CLASS)-1; +/// Constant to indicate unspecified value for key type attribute. +static constexpr CK_KEY_TYPE UNSPECIFIED_KEY_TYPE = (CK_KEY_TYPE)-1; +/// Constant to indicate unspecified value for value length attribute. +static constexpr CK_ULONG UNSPECIFIED_VALUE_LEN = (CK_ULONG)-1; +/// Default slot id. +static constexpr CK_SLOT_ID DEFAULT_SLOT_ID = 1; +/// AES256 key object handle. +static constexpr CK_OBJECT_HANDLE AES256_KEY_OBJECT_HANDLE = 2; +/// AES128 key object handle. +static constexpr CK_OBJECT_HANDLE AES128_KEY_OBJECT_HANDLE = 3; +/// AES block size in bytes. +static constexpr CK_ULONG AES_BLOCK_SIZE = 16; +/// AES GCM tag size in bytes. +static constexpr int AES_GCM_TAG_SIZE = 16; +/// Key size in bytes for AES 256. +static constexpr CK_ULONG AES256_KEY_SIZE = 32; +/// Key size in bytes for AES 128. +static constexpr CK_ULONG AES128_KEY_SIZE = 16; + +/// Crypto factory to HSM function simulations +static std::shared_ptr c_cryptoFactory; +/// AES 256 key value. This stub generates key value on initialization. +static KeyFactoryInterface::Key c_aes256Key; +/// AES 128 key value. This stub generates key value on initialization. +static KeyFactoryInterface::Key c_aes128Key; +/// AES 256 key checksum. It is three bytes, that correspond to \a c_mainKey data. Unlike PKCS#11 spec, we use SHA-256 +/// algorithm to produce checksum instead of SHA-1. +static DigestInterface::DataBlock c_aes256Checksum; +/// AES 128 key checksum. +static DigestInterface::DataBlock c_aes128Checksum; +/// Counter to generate unique session handle values. +static CK_ULONG c_sessionCounter; + +/// Session state object. +/** + * This object contains session state essential for stub operations. + */ +struct SessionStub { + /// Flag if login has been performed. + bool m_login = false; + /// Flag if C_FindObjectsInit has been called. + bool m_findObjectsInit = false; + /// Encoder or decoder reference + std::unique_ptr m_cryptoCodec; + /// Algorithm type. + AlgorithmType m_algorithmType; + /// Filter for object lookup filter by object class. + CK_OBJECT_CLASS m_findObjectClass = UNSPECIFIED_OBJECT_CLASS; + /// Filter for object lookup filter by key type. + CK_KEY_TYPE m_findKeyType = UNSPECIFIED_KEY_TYPE; + /// Filter for object lookup filter by value length. + CK_ULONG m_findValueLen = UNSPECIFIED_VALUE_LEN; + /// Filter for object lookup filter by label. + std::string m_findLabel = ""; +}; +/// Session map. +static std::unordered_map> c_sessions; +/// Session map mutex +static std::mutex c_sessionsMutex; + +/// Helper to find session by handle +/** + * This method looks up session object in session map. + * + * @param sessionHandle Session handle. + * + * @return Session pointer or nullptr on error. + */ +static std::shared_ptr findSession(CK_SESSION_HANDLE sessionHandle) { + std::unique_lock sessionLock(c_sessionsMutex); + auto it = c_sessions.find(sessionHandle); + if (it == c_sessions.end()) { + return nullptr; + } else { + return it->second; + } +} + +/** + * @brief Provides function table. + * + * This method provides function table for PKCS#11 interface. + * + * @param[out] result Pointer to store function table. + * + * @return CRK_OK on success. + */ +CK_RV C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR result) { + ACSDK_DEBUG0(LX("C_GetFunctionList")); + if (!result) { + ACSDK_ERROR(LX("C_GetFunctionListFailed").d("reason", "resultNull")); + return CKR_ARGUMENTS_BAD; + } + *result = &FUNCTION_LIST; + return CKR_OK; +} + +// Helper to create key and compute checksum. +static void initializeKey(AlgorithmType type, CryptoCodecInterface::Key& key, DigestInterface::DataBlock& checksum) { + auto keyFactory = c_cryptoFactory->getKeyFactory(); + keyFactory->generateKey(type, key); + auto digest = c_cryptoFactory->createDigest(DigestType::SHA_256); + DigestInterface::DataBlock value; + digest->process(key); + digest->finalize(checksum); + checksum.resize(3); +} + +/** + * @brief Initializes module. + * + * This method generates a new unique key and computes key signature. + * + * @param reserved Unused parameter. + * + * @return CKR_OK on success, error code on failure. + */ +CK_RV C_Initialize(CK_VOID_PTR reserved) { + ACSDK_DEBUG0(LX("C_Initialize")); + + std::unique_lock sessionLock(c_sessionsMutex); + c_cryptoFactory = alexaClientSDK::acsdkCrypto::createCryptoFactory(); + initializeKey(AlgorithmType::AES_256_CBC, c_aes256Key, c_aes256Checksum); + initializeKey(AlgorithmType::AES_128_CBC, c_aes128Key, c_aes128Checksum); + return CKR_OK; +} + +/** + * @brief Releases module data. + * + * @param reserved Unused parameter. The value must be nullptr. + * + * @return CKR_OK on success, error code on failure. + */ +CK_RV C_Finalize(CK_VOID_PTR reserved) { + ACSDK_DEBUG0(LX("C_Finalize")); + if (reserved) { + return CKR_ARGUMENTS_BAD; + } + std::unique_lock sessionLock(c_sessionsMutex); + + c_aes256Key.clear(); + c_aes128Key.clear(); + c_aes256Checksum.clear(); + c_aes128Checksum.clear(); + c_cryptoFactory.reset(); + c_sessions.clear(); + return CKR_OK; +} + +/** + * @brief Provides slot list. + * + * This method returns a single hardcoded slot id. + * + * @param tokenPresent Flag if the slot must have token. + * @param[out] slotList Optional pointer for slot ids with at least \a slotListSize elements + * @param[in,out] slotListSize Number of elements in \a slotList. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_GetSlotList(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR slotList, CK_ULONG_PTR slotListSize) { + ACSDK_DEBUG0(LX("C_GetSlotList")); + if (!slotListSize) { + ACSDK_ERROR(LX("C_GetSlotListFailed").d("reason", "slotListSizeNull")); + return CKR_ARGUMENTS_BAD; + } + *slotListSize = 1; + if (slotList) { + slotList[0] = DEFAULT_SLOT_ID; + } + return CKR_OK; +} + +/** + * @brief Provide token info. + * + * Provides token info for supported slot. + * + * @param[in] slotID Slot id. + * @param[out] tokenInfo Token information. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR tokenInfo) { + ACSDK_DEBUG0(LX("C_GetTokenInfo")); + if (!tokenInfo) { + ACSDK_ERROR(LX("C_GetTokenInfoFailed").d("reason", "tokenInfoNull")); + return CKR_ARGUMENTS_BAD; + } + if (slotID == DEFAULT_SLOT_ID) { + std::memset(tokenInfo, 0, sizeof(*tokenInfo)); + std::memset(tokenInfo->label, ' ', sizeof(tokenInfo->label)); + std::memset(tokenInfo->manufacturerID, ' ', sizeof(tokenInfo->manufacturerID)); + std::memset(tokenInfo->serialNumber, ' ', sizeof(tokenInfo->serialNumber)); + std::memset(tokenInfo->model, ' ', sizeof(tokenInfo->model)); + std::memcpy(tokenInfo->label, "ACSDK", 5); + return CKR_OK; + } else { + ACSDK_ERROR(LX("C_GetTokenInfoFailed").d("reason", "badSlotId")); + return CKR_SLOT_ID_INVALID; + } +} + +/** + * @brief Opens a new session. + * + * Method allocates and registers new session object and provides session handle. + * + * @param[in] slotID Slot id. + * @param[in] flags Session flags. + * @param[in] application Optional application-specific pointer for callbacks. + * @param[in] notify Optional callback function. + * @param[out] sessionHandle Session handle. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_OpenSession( + CK_SLOT_ID slotID, + CK_FLAGS flags, + CK_VOID_PTR application, + CK_NOTIFY notify, + CK_SESSION_HANDLE_PTR sessionHandle) { + ACSDK_DEBUG0(LX("C_OpenSession")); + if (slotID != DEFAULT_SLOT_ID) { + ACSDK_ERROR(LX("C_OpenSessionFailed").d("reason", "badSlotId")); + return CKR_SLOT_ID_INVALID; + } + std::unique_lock sessionLock(c_sessionsMutex); + + *sessionHandle = ++c_sessionCounter; + c_sessions[*sessionHandle] = std::make_shared(); + return CKR_OK; +} + +/** + * @brief Terminates session. + * + * @param[in] sessionHandle Session handle. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_CloseSession(CK_SESSION_HANDLE sessionHandle) { + ACSDK_DEBUG0(LX("C_CloseSession")); + std::unique_lock sessionsLock(c_sessionsMutex); + auto it = c_sessions.find(sessionHandle); + if (it == c_sessions.end()) { + ACSDK_ERROR(LX("C_CloseSessionFailed").d("reason", "sessionNull")); + return CKR_SESSION_HANDLE_INVALID; + } + + c_sessions.erase(it); + return CKR_OK; +} + +/** + * @brief Performs login. + * + * @param[in] sessionHandle Session handle. + * @param[in] type Login type. + * @param[in] pin User pin. + * @param[in] pinLen Length of user pin. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_Login(CK_SESSION_HANDLE sessionHandle, CK_USER_TYPE type, CK_UTF8CHAR_PTR pin, CK_ULONG pinLen) { + ACSDK_DEBUG0(LX("C_Logout")); + auto session = findSession(sessionHandle); + if (!session) { + ACSDK_ERROR(LX("C_LoginFailed").d("reason", "sessionNull")); + return CKR_SESSION_HANDLE_INVALID; + } + if (session->m_login) { + ACSDK_ERROR(LX("C_LoginFailed").d("reason", "alreadyLoggedIn")); + return CKR_USER_ALREADY_LOGGED_IN; + } + if (CKU_USER != type) { + ACSDK_ERROR(LX("C_LoginFailed").d("reason", "soLoginUnsupported")); + return CKR_GENERAL_ERROR; + } + if (!pin) { + ACSDK_ERROR(LX("C_LoginFailed").d("reason", "pinNull")); + return CKR_ARGUMENTS_BAD; + } + if (4 != pinLen || std::memcmp(pin, "1234", 4)) { + ACSDK_ERROR(LX("C_LoginFailed").d("reason", "pinError")); + return CKR_PIN_INCORRECT; + } + + session->m_login = true; + return CKR_OK; +} + +/** + * Performs logout. + * + * @param[in] sessionHandle Session handle. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_Logout(CK_SESSION_HANDLE sessionHandle) { + ACSDK_DEBUG0(LX("C_Logout")); + auto session = findSession(sessionHandle); + if (!session) { + ACSDK_ERROR(LX("C_LogoutFailed").d("reason", "sessionNull")); + return CKR_SESSION_HANDLE_INVALID; + } + if (!session->m_login) { + ACSDK_ERROR(LX("C_LogoutFailed").d("reason", "notLoggedIn")); + return CKR_USER_NOT_LOGGED_IN; + } + session->m_login = false; + return CKR_OK; +} + +/** + * Method returns object attributes. This implementation supports only subset of attributes. + * + * @param[in] sessionHandle Session handle. + * @param[in] objectHandle Object handle. + * @param[in,out] attributes Attributes to query. + * @param[in] attributeCount Number of attributes to query. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_GetAttributeValue( + CK_SESSION_HANDLE sessionHandle, + CK_OBJECT_HANDLE objectHandle, + CK_ATTRIBUTE_PTR attributes, + CK_ULONG attributeCount) { + ACSDK_DEBUG0(LX("C_GetAttributeValue")); + auto session = findSession(sessionHandle); + if (!session) { + ACSDK_ERROR(LX("C_GetAttributeValueFailed").d("reason", "sessionNull")); + return CKR_SESSION_HANDLE_INVALID; + } + + DigestInterface::DataBlock* checksum = nullptr; + CK_ULONG keySize = 0; + + switch (objectHandle) { + case AES128_KEY_OBJECT_HANDLE: + checksum = &c_aes128Checksum; + keySize = AES128_KEY_SIZE; + break; + case AES256_KEY_OBJECT_HANDLE: + checksum = &c_aes256Checksum; + keySize = AES256_KEY_SIZE; + break; + default: + ACSDK_ERROR(LX("C_GetAttributeValueFailed").d("reason", "badObjectHandle")); + return CKR_OBJECT_HANDLE_INVALID; + } + + for (CK_ULONG i = 0; i < attributeCount; ++i) { + switch (attributes[i].type) { + case CKA_NEVER_EXTRACTABLE: + if (attributes[i].ulValueLen != sizeof(CK_BBOOL)) { + ACSDK_ERROR(LX("C_GetAttributeValueFailed") + .d("reason", "badAttributeSize") + .d("attr", "CKA_NEVER_EXTRACTABLE")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } else { + *(CK_BBOOL*)attributes[i].pValue = CK_TRUE; + } + break; + case CKA_CHECK_VALUE: + if (attributes[i].ulValueLen != checksum->size()) { + ACSDK_ERROR( + LX("C_GetAttributeValueFailed").d("reason", "badAttributeSize").d("attr", "CKA_CHECK_VALUE")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } else { + std::memcpy(attributes[i].pValue, checksum->data(), checksum->size()); + } + break; + case CKA_CLASS: + if (attributes[i].ulValueLen != sizeof(CK_OBJECT_CLASS)) { + ACSDK_ERROR(LX("C_GetAttributeValueFailed").d("reason", "badAttributeSize").d("attr", "CKA_CLASS")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } else { + *(CK_OBJECT_CLASS*)attributes[i].pValue = CKO_SECRET_KEY; + } + break; + case CKA_KEY_TYPE: + if (attributes[i].ulValueLen != sizeof(CK_KEY_TYPE)) { + ACSDK_ERROR( + LX("C_GetAttributeValueFailed").d("reason", "badAttributeSize").d("attr", "CKA_KEY_TYPE")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } else { + *(CK_KEY_TYPE*)attributes[i].pValue = CKK_AES; + } + break; + case CKA_VALUE_LEN: + if (attributes[i].ulValueLen != sizeof(CK_ULONG)) { + ACSDK_ERROR( + LX("C_GetAttributeValueFailed").d("reason", "badAttributeSize").d("attr", "CKA_VALUE_LEN")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } else { + *(CK_ULONG*)attributes[i].pValue = keySize; + } + break; + default: + ACSDK_ERROR( + LX("C_GetAttributeValueFailed").d("reason", "unsupportedAttribute").d("type", attributes[i].type)); + return CKR_ATTRIBUTE_TYPE_INVALID; + } + } + + return CKR_OK; +} + +/** + * @brief Initializes object search. + * + * This method configures object search parametrs. + * + * @param[in] sessionHandle Session handle. + * @param[in] attributes Attributes to match. + * @param[in] attributeCount Number of attributes to query. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_FindObjectsInit(CK_SESSION_HANDLE sessionHandle, CK_ATTRIBUTE_PTR attributes, CK_ULONG attributeCount) { + ACSDK_DEBUG0(LX("C_FindObjectsInit")); + auto session = findSession(sessionHandle); + if (!session) { + ACSDK_ERROR(LX("C_FindObjectsInitFailed").d("reason", "sessionNull")); + return CKR_SESSION_HANDLE_INVALID; + } + + session->m_findObjectsInit = false; + session->m_findObjectClass = UNSPECIFIED_OBJECT_CLASS; + session->m_findKeyType = UNSPECIFIED_KEY_TYPE; + session->m_findValueLen = UNSPECIFIED_VALUE_LEN; + + for (CK_ULONG i = 0; i < attributeCount; ++i) { + if (!attributes[i].pValue) { + ACSDK_ERROR(LX("C_FindObjectsInitFailed").d("reason", "pValueNull")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } + switch (attributes[i].type) { + case CKA_CLASS: + if (attributes[i].ulValueLen == sizeof(CK_OBJECT_CLASS)) { + session->m_findObjectClass = *(CK_OBJECT_CLASS_PTR)attributes[i].pValue; + } else { + ACSDK_ERROR(LX("C_FindObjectsInitFailed").d("reason", "classSizeInvalid")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } + break; + case CKA_KEY_TYPE: + if (attributes[i].ulValueLen == sizeof(CK_KEY_TYPE)) { + session->m_findKeyType = *(CK_KEY_TYPE*)attributes[i].pValue; + } else { + ACSDK_ERROR(LX("C_FindObjectsInitFailed").d("reason", "keyTypeSizeInvalid")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } + break; + case CKA_VALUE_LEN: + if (attributes[i].ulValueLen == sizeof(CK_ULONG)) { + session->m_findValueLen = *(CK_ULONG_PTR)attributes[i].pValue; + } else { + ACSDK_ERROR(LX("C_FindObjectsInitFailed").d("reason", "valueLenSizeInvalid")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } + break; + case CKA_LABEL: + if (attributes[i].ulValueLen < 128) { + session->m_findLabel.assign( + (CK_BYTE_PTR)attributes[i].pValue, + (CK_BYTE_PTR)attributes[i].pValue + attributes[i].ulValueLen); + } else { + ACSDK_ERROR(LX("C_FindObjectsInitFailed").d("reason", "valueLenSizeInvalid")); + return CKR_ATTRIBUTE_VALUE_INVALID; + } + break; + default: + ACSDK_ERROR( + LX("C_FindObjectsInitFailed").d("reason", "unsupportedAttribute").d("type", attributes[i].type)); + return CKR_ATTRIBUTE_TYPE_INVALID; + } + } + + session->m_findObjectsInit = true; + return CKR_OK; +} + +/** + * @brief Finds objects matching search criteria. + * + * This method provides object handles that match search criteria. + * + * @param[in] sessionHandle Session handle. + * @param[out] objectHandles Discovered object handles. + * @param[in] maxObjectCount Maximum number of objects to locate. + * @param[out] objectCount Number of objects located. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_FindObjects( + CK_SESSION_HANDLE sessionHandle, + CK_OBJECT_HANDLE_PTR objectHandles, + CK_ULONG maxObjectCount, + CK_ULONG_PTR objectCount) { + ACSDK_DEBUG0(LX("C_FindObjects")); + auto session = findSession(sessionHandle); + if (!session) { + ACSDK_ERROR(LX("C_FindObjectsFailed").d("reason", "sessionNull")); + return CKR_SESSION_HANDLE_INVALID; + } + if (!session->m_findObjectsInit) { + ACSDK_ERROR(LX("C_FindObjectsFailed").d("reason", "findNotInitialized")); + return CKR_FUNCTION_REJECTED; + } + + session->m_findObjectsInit = false; + + if (!session->m_login) { + ACSDK_ERROR(LX("C_FindObjectsFailed").d("reason", "notLoggedIn")); + return CKR_USER_NOT_LOGGED_IN; + } + + if (maxObjectCount < 1) { + ACSDK_ERROR(LX("C_FindObjectsFailed").d("reason", "bufferTooSmall")); + return CKR_BUFFER_TOO_SMALL; + } + + if (!session->m_findLabel.empty() && session->m_findLabel != "TEST_KEY") { + *objectCount = 0; + return CKR_OK; + } + + if ((session->m_findObjectClass == UNSPECIFIED_OBJECT_CLASS || session->m_findObjectClass == CKO_SECRET_KEY) && + (session->m_findKeyType == UNSPECIFIED_KEY_TYPE || session->m_findKeyType == CKK_AES)) { + if (session->m_findValueLen == UNSPECIFIED_VALUE_LEN) { + constexpr CK_ULONG totalKeys = 2u; + *objectCount = std::min(maxObjectCount, totalKeys); + objectHandles[0] = AES256_KEY_OBJECT_HANDLE; + if (*objectCount > 1u) { + objectHandles[1] = AES128_KEY_OBJECT_HANDLE; + } + return CKR_OK; + } else if (session->m_findValueLen == AES128_KEY_SIZE) { + *objectCount = 1u; + objectHandles[0] = AES128_KEY_OBJECT_HANDLE; + return CKR_OK; + } else if (session->m_findValueLen == AES256_KEY_SIZE) { + *objectCount = 1u; + objectHandles[0] = AES256_KEY_OBJECT_HANDLE; + return CKR_OK; + } + } + + *objectCount = 0; + return CKR_OK; +} + +/** + * @brief Finishes object search. + * + * @param[in] sessionHandle Session handle. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_FindObjectsFinal(CK_SESSION_HANDLE sessionHandle) { + ACSDK_DEBUG0(LX("C_FindObjectsFinal")); + auto session = findSession(sessionHandle); + if (!session) { + ACSDK_ERROR(LX("C_FindObjectsFinalFailed").d("reason", "sessionNull")); + return CKR_SESSION_HANDLE_INVALID; + } + return CKR_OK; +} + +/** + * @brief Initializes encryption operation. + * + * @param[in] sessionHandle Session handle. + * @param[in] mechanism Encryption parameters. + * @param[in] keyHandle Key handle. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_EncryptInit(CK_SESSION_HANDLE sessionHandle, CK_MECHANISM_PTR mechanism, CK_OBJECT_HANDLE keyHandle) { + ACSDK_DEBUG0(LX("C_EncryptInit")); + auto session = findSession(sessionHandle); + if (!session) { + ACSDK_ERROR(LX("C_EncryptInitFailed").d("reason", "sessionNotFound")); + return CKR_SESSION_HANDLE_INVALID; + } + if (!session->m_login) { + ACSDK_ERROR(LX("C_EncryptInitFailed").d("reason", "notLoggedIn")); + return CKR_USER_NOT_LOGGED_IN; + } + KeyFactoryInterface::Key* key; + bool use256Key; + switch (keyHandle) { + case AES128_KEY_OBJECT_HANDLE: + use256Key = false; + key = &c_aes128Key; + break; + case AES256_KEY_OBJECT_HANDLE: + use256Key = true; + key = &c_aes256Key; + break; + default: + ACSDK_ERROR(LX("C_EncryptInitFailed").d("reason", "keyHandleInvalid")); + return CKR_KEY_HANDLE_INVALID; + } + + switch (mechanism->mechanism) { + case CKM_AES_CBC: + session->m_algorithmType = use256Key ? AlgorithmType::AES_256_CBC : AlgorithmType::AES_128_CBC; + break; + case CKM_AES_CBC_PAD: + session->m_algorithmType = use256Key ? AlgorithmType::AES_256_CBC_PAD : AlgorithmType::AES_128_CBC_PAD; + break; + case CKM_AES_GCM: + session->m_algorithmType = use256Key ? AlgorithmType::AES_256_GCM : AlgorithmType::AES_128_GCM; + break; + default: + ACSDK_ERROR(LX("C_EncryptInitFailed").d("reason", "mechanismInvalid")); + return CKR_MECHANISM_INVALID; + } + + session->m_cryptoCodec = c_cryptoFactory->createEncoder(session->m_algorithmType); + + if (CKM_AES_GCM == mechanism->mechanism) { + const CK_GCM_PARAMS& gcmParams = *(const CK_GCM_PARAMS*)(mechanism->pParameter); + + ACSDK_DEBUG5(LX("C_EncryptInit").d("ivLen", gcmParams.ulIvLen).d("aadLen", gcmParams.ulAADLen)); + + CryptoCodecInterface::IV iv{gcmParams.pIv, gcmParams.pIv + gcmParams.ulIvLen}; + if (!session->m_cryptoCodec->init(*key, iv)) { + ACSDK_ERROR(LX("C_EncryptInitFailed").d("reason", "codecInitError")); + return CKR_GENERAL_ERROR; + } + CryptoCodecInterface::DataBlock aad{gcmParams.pAAD, gcmParams.pAAD + gcmParams.ulAADLen}; + if (!session->m_cryptoCodec->processAAD(aad)) { + ACSDK_ERROR(LX("C_EncryptInitFailed").d("reason", "codecProcessAADError")); + return CKR_GENERAL_ERROR; + } + } else { + CryptoCodecInterface::IV iv{ + (CryptoCodecInterface::IV::const_pointer)mechanism->pParameter, + (CryptoCodecInterface::IV::const_pointer)mechanism->pParameter + mechanism->ulParameterLen}; + if (!session->m_cryptoCodec->init(*key, iv)) { + ACSDK_ERROR(LX("C_EncryptInitFailed").d("reason", "codecInitError")); + return CKR_GENERAL_ERROR; + } + } + + return CKR_OK; +} + +/** + * @brief Performs encryption. + * + * Method encrypts data block or signal an error. Any result except CKR_BUFFER_TOO_SMALL terminates encryption + * operation. + * + * @param[in] sessionHandle Session handle. + * @param[in] plaintext Plaintext data. + * @param[in] plaintextLen Size of \a plaintext. + * @param[out] ciphertext Optional ciphertext output. + * @param[in,out] ciphertextLen Size of \a ciphertext buffer on input, and required size on output. + * + * @return CKR_OK on success, or error code on failure. + * @retval CKR_BUFFER_TOO_SMALL Indicates the \a ciphertextLen was too small. + */ +CK_RV C_Encrypt( + CK_SESSION_HANDLE sessionHandle, + CK_BYTE_PTR plaintext, + CK_ULONG plaintextLen, + CK_BYTE_PTR ciphertext, + CK_ULONG_PTR ciphertextLen) { + ACSDK_DEBUG0(LX("C_Encrypt").d("mode", ciphertext ? "estimate" : "encrypt")); + + auto session = findSession(sessionHandle); + if (!session) { + ACSDK_ERROR(LX("C_EncryptFailed").d("reason", "sessionHandleInvalid")); + return CKR_SESSION_HANDLE_INVALID; + } + + CK_ULONG estSize; + bool useGcm; + switch (session->m_algorithmType) { + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_128_CBC: + estSize = plaintextLen; + if (plaintextLen % AES_BLOCK_SIZE) { + ACSDK_ERROR(LX("C_EncryptFailed").d("reason", "inputBlockSize")); + return CKR_DATA_INVALID; + } + useGcm = false; + break; + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_128_CBC_PAD: + estSize = plaintextLen + AES_BLOCK_SIZE - plaintextLen % AES_BLOCK_SIZE; + useGcm = false; + break; + case AlgorithmType::AES_256_GCM: + case AlgorithmType::AES_128_GCM: + estSize = plaintextLen + AES_GCM_TAG_SIZE; + useGcm = true; + break; + default: + ACSDK_ERROR(LX("C_EncryptFailed").d("reason", "unknownAlgorithmType").d("type", session->m_algorithmType)); + return CKR_GENERAL_ERROR; + } + + if (ciphertext) { + if (*ciphertextLen < estSize) { + ACSDK_ERROR(LX("C_EncryptFailed").d("reason", "bufferTooSmall")); + return CKR_BUFFER_TOO_SMALL; + } + CryptoCodecInterface::DataBlock in; + in.assign(plaintext, plaintext + plaintextLen); + CryptoCodecInterface::DataBlock res; + if (!session->m_cryptoCodec->process(in, res)) { + ACSDK_ERROR(LX("C_EncryptFailed").d("reason", "codecProcessError")); + return CKR_GENERAL_ERROR; + } + if (!session->m_cryptoCodec->finalize(res)) { + ACSDK_ERROR(LX("C_EncryptFailed").d("reason", "codecFinalizeError")); + return CKR_GENERAL_ERROR; + } + if (useGcm) { + CryptoCodecInterface::Tag tag; + if (!session->m_cryptoCodec->getTag(tag)) { + ACSDK_ERROR(LX("C_EncryptFailed").d("reason", "codecGetTagError")); + return CKR_GENERAL_ERROR; + } + res.reserve(res.size() + tag.size()); + res.insert(res.end(), tag.cbegin(), tag.cend()); + } + *ciphertextLen = res.size(); + ::memcpy(ciphertext, res.data(), res.size()); + + session->m_cryptoCodec.reset(); + } else { + *ciphertextLen = estSize; + } + + return CKR_OK; +} + +/** + * @brief Initializes decryption operation. + * + * @param[in] sessionHandle Session handle. + * @param[in] mechanism Encryption parameters. + * @param[in] keyHandle Key handle. + * + * @return CKR_OK on success, or error code on failure. + */ +CK_RV C_DecryptInit(CK_SESSION_HANDLE sessionHandle, CK_MECHANISM_PTR mechanism, CK_OBJECT_HANDLE keyHandle) { + ACSDK_DEBUG0(LX("C_DecryptInit")); + auto session = findSession(sessionHandle); + if (!session) { + ACSDK_ERROR(LX("C_DecryptInitFailed").d("reason", "sessionHandleInvalid")); + return CKR_SESSION_HANDLE_INVALID; + } + if (!session->m_login) { + ACSDK_ERROR(LX("C_DecryptInitFailed").d("reason", "notLoggedIn")); + return CKR_USER_NOT_LOGGED_IN; + } + + KeyFactoryInterface::Key* key; + bool use256Key; + switch (keyHandle) { + case AES128_KEY_OBJECT_HANDLE: + use256Key = false; + key = &c_aes128Key; + break; + case AES256_KEY_OBJECT_HANDLE: + use256Key = true; + key = &c_aes256Key; + break; + default: + ACSDK_ERROR(LX("C_DecryptInitFailed").d("reason", "unknownHandle").d("handle", keyHandle)); + return CKR_KEY_HANDLE_INVALID; + } + switch (mechanism->mechanism) { + case CKM_AES_CBC: + session->m_algorithmType = use256Key ? AlgorithmType::AES_256_CBC : AlgorithmType::AES_128_CBC; + break; + case CKM_AES_CBC_PAD: + session->m_algorithmType = use256Key ? AlgorithmType::AES_256_CBC_PAD : AlgorithmType::AES_128_CBC_PAD; + break; + case CKM_AES_GCM: + session->m_algorithmType = use256Key ? AlgorithmType::AES_256_GCM : AlgorithmType::AES_128_GCM; + break; + default: + ACSDK_ERROR(LX("C_DecryptInitFailed").d("reason", "unknownMechanism").d("type", mechanism->mechanism)); + return CKR_MECHANISM_INVALID; + } + + session->m_cryptoCodec = c_cryptoFactory->createDecoder(session->m_algorithmType); + if (!session->m_cryptoCodec) { + ACSDK_ERROR(LX("C_DecryptInitFailed").d("reason", "createDecoderFailed").d("type", session->m_algorithmType)); + return CKR_GENERAL_ERROR; + } + + if (CKM_AES_GCM == mechanism->mechanism) { + const CK_GCM_PARAMS& gcmParams = *(const CK_GCM_PARAMS*)(mechanism->pParameter); + ACSDK_DEBUG5(LX("C_DecryptInit").d("ivLen", gcmParams.ulIvLen).d("aadLen", gcmParams.ulAADLen)); + CryptoCodecInterface::IV iv{gcmParams.pIv, gcmParams.pIv + gcmParams.ulIvLen}; + if (!session->m_cryptoCodec->init(*key, iv)) { + ACSDK_ERROR(LX("C_DecryptInitFailed").d("reason", "codecInitFailed")); + return CKR_GENERAL_ERROR; + } + CryptoCodecInterface::DataBlock aad{gcmParams.pAAD, gcmParams.pAAD + gcmParams.ulAADLen}; + if (!session->m_cryptoCodec->processAAD(aad)) { + ACSDK_ERROR(LX("C_DecryptInitFailed").d("reason", "codecProcessAADFailed")); + return CKR_GENERAL_ERROR; + } + } else { + CryptoCodecInterface::IV iv{ + (CryptoCodecInterface::IV::const_pointer)mechanism->pParameter, + (CryptoCodecInterface::IV::const_pointer)mechanism->pParameter + mechanism->ulParameterLen}; + if (!session->m_cryptoCodec->init(*key, iv)) { + ACSDK_ERROR(LX("C_DecryptInitFailed").d("reason", "codecInitFailed")); + return CKR_GENERAL_ERROR; + } + } + + return CKR_OK; +} + +/** + * @brief Performs decryption. + * + * Method decrypts data block or signal an error. Any result except CKR_BUFFER_TOO_SMALL terminates decryption + * operation. + * + * @param[in] sessionHandle Session handle. + * @param[in] ciphertext Optional ciphertext output. + * @param[in] ciphertextLen Size of \a ciphertext buffer on input, and required size on output. + * @param[out] plaintext Plaintext data. + * @param[in,out] plaintextLen Size of \a plaintext. + * + * @return CKR_OK on success, or error code on failure. + * @retval CKR_BUFFER_TOO_SMALL Indicates the \a plaintextLen was too small. + */ +CK_RV C_Decrypt( + CK_SESSION_HANDLE sessionHandle, + CK_BYTE_PTR ciphertext, + CK_ULONG ciphertextLen, + CK_BYTE_PTR plaintext, + CK_ULONG_PTR plaintextLen) { + ACSDK_DEBUG0(LX("C_Decrypt").d("mode", plaintext ? "decrypt" : "estimate")); + + auto session = findSession(sessionHandle); + if (!session) { + return CKR_SESSION_HANDLE_INVALID; + } + if (!session->m_cryptoCodec) { + return CKR_ACTION_PROHIBITED; + } + + CK_ULONG estSize; + bool useGcm; + switch (session->m_algorithmType) { + case AlgorithmType::AES_256_CBC: + case AlgorithmType::AES_128_CBC: + case AlgorithmType::AES_256_CBC_PAD: + case AlgorithmType::AES_128_CBC_PAD: + // Overestimate the size when PKCS#7 padding is used + estSize = ciphertextLen; + useGcm = false; + break; + case AlgorithmType::AES_256_GCM: + case AlgorithmType::AES_128_GCM: + estSize = ciphertextLen - AES_GCM_TAG_SIZE; + useGcm = true; + break; + default: + return CKR_GENERAL_ERROR; + } + + if (!useGcm && (ciphertextLen % AES_BLOCK_SIZE)) { + return CKR_ENCRYPTED_DATA_INVALID; + } + + if (plaintext) { + if (*plaintextLen < estSize) { + return CKR_BUFFER_TOO_SMALL; + } + CryptoCodecInterface::DataBlock res; + + if (useGcm) { + CK_ULONG actualCiphertextLen = ciphertextLen - AES_GCM_TAG_SIZE; + CryptoCodecInterface::DataBlock in; + in.assign(ciphertext, ciphertext + actualCiphertextLen); + if (!session->m_cryptoCodec->process(in, res)) { + ACSDK_ERROR(LX("C_DecryptFailed").d("reason", "codecProcessFailed")); + return CKR_GENERAL_ERROR; + } + CryptoCodecInterface::Tag tag; + tag.assign(ciphertext + actualCiphertextLen, ciphertext + ciphertextLen); + if (!session->m_cryptoCodec->setTag(tag)) { + ACSDK_ERROR(LX("C_DecryptFailed").d("reason", "codecSetTagFailed")); + return CKR_GENERAL_ERROR; + } + } else { + CryptoCodecInterface::DataBlock in; + in.assign(ciphertext, ciphertext + ciphertextLen); + if (!session->m_cryptoCodec->process(in, res)) { + ACSDK_ERROR(LX("C_DecryptFailed").d("reason", "codecProcessFailed")); + return CKR_GENERAL_ERROR; + } + } + + if (!session->m_cryptoCodec->finalize(res)) { + ACSDK_ERROR(LX("C_DecryptFailed").d("reason", "codecFinalizeFailed")); + return CKR_GENERAL_ERROR; + } + session->m_cryptoCodec.reset(); + std::memcpy(plaintext, res.data(), res.size()); + *plaintextLen = res.size(); + } else { + *plaintextLen = estSize; + } + + return CKR_OK; +} + +/// @} diff --git a/core/Properties/CMakeLists.txt b/core/Properties/CMakeLists.txt new file mode 100644 index 0000000000..116c89bade --- /dev/null +++ b/core/Properties/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.1) + +add_subdirectory("acsdkPropertiesInterfaces") +add_subdirectory("acsdkProperties") diff --git a/core/Properties/acsdkProperties/CMakeLists.txt b/core/Properties/acsdkProperties/CMakeLists.txt new file mode 100644 index 0000000000..c85f240667 --- /dev/null +++ b/core/Properties/acsdkProperties/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(acsdkProperties LANGUAGES CXX) + +add_subdirectory("src") +add_subdirectory("test") +if (PKCS11 AND BUILD_SHARED_LIBS) + add_subdirectory("testCrypto") +endif() diff --git a/core/Properties/acsdkProperties/doc/Namespaces.dox b/core/Properties/acsdkProperties/doc/Namespaces.dox new file mode 100644 index 0000000000..f80d89ecc2 --- /dev/null +++ b/core/Properties/acsdkProperties/doc/Namespaces.dox @@ -0,0 +1,24 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \namespace ::alexaClientSDK::acsdkProperties + * \brief Properties Implementation. + * \ingroup PropertiesIMPL + * + * \namespace ::alexaClientSDK::acsdkProperties::test + * \brief Test cases for \ref PropertiesIMPL + * \ingroup PropertiesIMPL + */ diff --git a/core/Properties/acsdkProperties/doc/PropertiesIMPL.dox b/core/Properties/acsdkProperties/doc/PropertiesIMPL.dox new file mode 100644 index 0000000000..76255a54d2 --- /dev/null +++ b/core/Properties/acsdkProperties/doc/PropertiesIMPL.dox @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \defgroup PropertiesIMPL Properties Implementation + * @brief Implementations for \ref PropertiesAPI + * + * PropertiesIMPL enables users to use PropertiesAPI instead of lower level MiscStorageInterface and SQLiteDatabase. In + * addition, this module offers data at rest protection using hardware security module. + * + * To use unencrypted adapter for \ref alexaClientSDK::acsdkProperties::MiscStorageInterface: + * \code{.cpp} + * #include + * + * std::shared_ptr miscStorage = ...; + * auto factory = createPropertiesFactory(miscStorage); + * auto properties = propertiesFactory->getProperties("componentName", "configNamespace"); + * properties->putString("propertyName", "stringValue"); + * \endcode + * + * The following example demonstrates how to use encrypted properties: + * \code{.cpp} + * #include + * + * std::shared_ptr miscStorage = ...; + * std::shared_ptr cryptoFactory = ...; + * std::shared_ptr keyStore = ...; + * + * auto factory = createEncryptedPropertiesFactory(cryptoFactory, keyStore, miscStorage); + * auto properties = propertiesFactory->getProperties("componentName", "configNamespace"); + * properties->putString("propertyName", "stringValue"); + * \endcode + * + * Encryption at rest requires that CryptoAPI support is available and the platform has correctly configured + * hardware security module. + * + * \sa CryptoIMPL how to obtain \ref alexaClientSDK::acsdkCryptoInterfaces::CryptoFactoryInterface. + * \sa PKCS11IMPL how to obtain \ref alexaClientSDK::acsdkCryptoInterfaces::KeyStoreInterface and configure HSM. + * + * \sa alexaClientSDK::acsdkProperties + * \sa alexaClientSDK::acsdkProperties::test + */ diff --git a/core/Properties/acsdkProperties/include/acsdkProperties/EncryptedPropertiesFactories.h b/core/Properties/acsdkProperties/include/acsdkProperties/EncryptedPropertiesFactories.h new file mode 100644 index 0000000000..636abb8b52 --- /dev/null +++ b/core/Properties/acsdkProperties/include/acsdkProperties/EncryptedPropertiesFactories.h @@ -0,0 +1,90 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_ENCRYPTEDPROPERTIESFACTORIES_H_ +#define ACSDKPROPERTIES_ENCRYPTEDPROPERTIESFACTORIES_H_ + +#include + +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +using alexaClientSDK::acsdkCryptoInterfaces::CryptoFactoryInterface; +using alexaClientSDK::acsdkCryptoInterfaces::KeyStoreInterface; +using alexaClientSDK::acsdkPropertiesInterfaces::PropertiesFactoryInterface; +using alexaClientSDK::avsCommon::sdkInterfaces::storage::MiscStorageInterface; + +/** + * @brief Creates properties factory with encryption support by wrapping a factory without encryption support. + * + * Encrypted properties factory protects all values using AES-256 cipher. The data key is stored as one of the + * underlying properties with reserved name "$acsdkEncryption$" in encrypted form. Hardware security module is used for + * storing the main encryption key and wrapping/unwrapping data keys. + * + * When client code accesses @c PropertiesInterface through encrypted @c PropertiesFactoryInterface, all existing + * data is automatically converted into encrypted form. + * + * @param[in] innerFactory Properties factory without encryption support. + * @param[in] cryptoFactory Crypto factory reference. This parameter must not be nullptr. + * @param[in] keyStore Key store factory reference. This parameter must not be nullptr. + * + * @return Properties factory reference or nullptr on error. + */ +std::shared_ptr createEncryptedPropertiesFactory( + const std::shared_ptr& innerFactory, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept; + +/** + * @brief Creates properties factory with encryption support by wrapping a @c MiscStorageInterface. + * + * Encrypted properties factory protects all values using AES-256 cipher. The data key is stored as one of the + * underlying properties with reserved name "$acsdkEncryption$" in encrypted form. Hardware security module is used for + * storing the main encryption key and wrapping/unwrapping data keys. + * + * When client code accesses @c PropertiesInterface through encrypted @c PropertiesFactoryInterface, all existing + * data is automatically converted into encrypted form. + * + * The method automatically creates database if it is not created. When user creates \c PropertiesInterface, the + * implementation automatically creates corresponding table. + * + * As all encrypted property values are in binary form, the implementation uses base64 encoding to store values. + * + * @param[in] innerStorage Storage reference. This parameter must not be nullptr. + * @param[in] uriMapper URI mapper reference. + * @param[in] cryptoFactory Crypto factory reference. This parameter must not be nullptr. + * @param[in] keyStore Key store factory reference. This parameter must not be nullptr. + * + * @return Properties factory reference or nullptr on error. + * + * @ingroup PropertiesIMPL + */ +std::shared_ptr createEncryptedPropertiesFactory( + const std::shared_ptr& innerStorage, + const std::shared_ptr& uriMapper, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_ENCRYPTEDPROPERTIESFACTORIES_H_ diff --git a/core/Properties/acsdkProperties/include/acsdkProperties/ErrorCallbackInterface.h b/core/Properties/acsdkProperties/include/acsdkProperties/ErrorCallbackInterface.h new file mode 100644 index 0000000000..ae95467934 --- /dev/null +++ b/core/Properties/acsdkProperties/include/acsdkProperties/ErrorCallbackInterface.h @@ -0,0 +1,128 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_ERRORCALLBACKINTERFACE_H_ +#define ACSDKPROPERTIES_ERRORCALLBACKINTERFACE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +/** + * @brief Possible error causes. + * + * This enumeration defines supported error reasons for properties open operation. + * + * @sa ErrorCallbackInterface + * @ingroup PropertiesIMPL + */ +enum class StatusCode { + SUCCESS = 1, ///< Status code indicating no error. For internal use only. + UNKNOWN_ERROR = 2, ///< Any error, that doesn't fit into other categories. + HSM_ERROR = 3, ///< HSM API Error. + CRYPTO_ERROR = 4, ///< Crypto API Error. + DIGEST_ERROR = 5, ///< Data corruption error. + INNER_PROPERTIES_ERROR = 6, ///< Underlying properties error. +}; + +/** + * @brief Error action. + * + * This enumeration defines possible actions when properties framework encounters an error. + * + * @sa ErrorCallbackInterface + * @ingroup PropertiesIMPL + */ +enum class Action { + CONTINUE = 1, ///< Continue with default behaviour. + FAIL = 2, ///< Fail operation. Do not delete data. + CLEAR_DATA = 3, ///< Continue operation, delete data. + RETRY = 4, ///< Retry operation. +}; + +/** + * @brief Callback interface to handle errors. + * + * When framework has a callback handler installed, the handler may override default framework actions on error + * situations. + * + * @sa setErrorCallback() + * @ingroup PropertiesIMPL + */ +class ErrorCallbackInterface { +public: + //// Default constructor. + virtual ~ErrorCallbackInterface() noexcept = default; + + /** + * @brief Handler of open properties error. + * + * This handler is invoked when open properties call encounters an error. + * + * @param[in] status Status code. Handler must be able to handle unknown error codes. + * @param[in] configUri Configuration URI for the properties container. + * + * @return Preferred action to continue. + * + * @retval Action::CONTINUE Execute default action. The framework decides what to do. + * @retval Action::FAIL Fails the call. The framework aborts the operation and returns an error code to + * caller. + * @retval Action::CLEAR_DATA Signals to framework to clear all container's data and continue normally. + * @retval Action::RETRY Signals to framework to retry failed operation. + */ + virtual Action onOpenPropertiesError(StatusCode status, const std::string& configUri) noexcept = 0; + + /** + * @brief Handler of get property errors. + * + * This handler is invoked when getting string or binary property call encounters an error. + * + * @param[in] status Status code. Handler must be able to handle unknown error codes. + * @param[in] configUri Configuration URI for the properties container. + * + * @return Preferred action to continue. + * @retval Action::DEFAULT Execute default action. The framework decides what to do. + * @retval Action::FAIL Fails the call. The framework aborts the operation and returns an error code to + * caller. + * @retval Action::CLEAR_DATA Signals to framework to clear the property value and continue normally. The caller + * will get an error as a result. + * @retval Action::RETRY Signals to framework to retry failed operation. + */ + virtual Action onGetPropertyError(StatusCode status, const std::string& configUri) noexcept = 0; + + /** + * @brief Handler of put property errors. + * + * This handler is invoked when setting string or binary property call encounters an error. + * + * @param[in] status Status code. Handler must be able to handle unknown error codes. + * @param[in] configUri Configuration URI for the properties container. + * + * @return Preferred action to continue. + * @retval Action::CONTINUE Execute default action. The framework decides what to do. + * @retval Action::FAIL Fails the call. The framework aborts the operation and returns an error code to + * caller. + * @retval Action::CLEAR_DATA Signals to framework to clear the property value and continue normally. + * @retval Action::RETRY Signals to framework to retry failed operation. + */ + virtual Action onPutPropertyError(StatusCode status, const std::string& configUri) noexcept = 0; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_ERRORCALLBACKINTERFACE_H_ diff --git a/core/Properties/acsdkProperties/include/acsdkProperties/ErrorCallbackSetter.h b/core/Properties/acsdkProperties/include/acsdkProperties/ErrorCallbackSetter.h new file mode 100644 index 0000000000..fc52f62949 --- /dev/null +++ b/core/Properties/acsdkProperties/include/acsdkProperties/ErrorCallbackSetter.h @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_ERRORCALLBACKSETTER_H_ +#define ACSDKPROPERTIES_ERRORCALLBACKSETTER_H_ + +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +/** + * @brief Default number of retries when using error callback interface. + * + * Number of retries limits number of error handling attempts when implementation encounters a recoverable error. If + * retry callback requests more than the given number of retries, the operation is considered as failed. + * + * @sa setErrorCallback() + */ +static constexpr uint32_t DEFAULT_MAX_RETRIES = 16u; + +/** + * @brief Unlimited number of retries when using error callback interface. + * + * If this value is used when setting error callback, the implementation will never give up on retries unless callback + * tell to do so. + * + * @sa setErrorCallback() + */ +static constexpr uint32_t UNLIMITED_RETRIES = UINT32_MAX; + +/** + * @brief Sets an error callback. + * + * This method can both set a new callback or clear existing one if \a callback is nullptr. Changing callback affects + * error handling of Property API methods that are called after the callback is changed. + * + * @param[in] callback New callback reference or nullptr to remove callback. + * @param[in] maxRetries Maximum number of retries to use with this callback. If implementation encounters more + * errors, than number of \a maxRetries plus one, the operation fails. If @ref + * UNLIMITED_RETRIES value is specified, the implementation executes unlimited number of + * retries until operation succeeds or \a callback indicates that operation must stop. + * @param[out] previous Optional pointer to store previous callback. + * + * @return Boolean indicating operation success. On failure, contents of *previous is undefined and false is returned. + * + * @ingroup PropertiesIMPL + */ +bool setErrorCallback( + const std::weak_ptr& callback, + uint32_t maxRetries = DEFAULT_MAX_RETRIES, + std::weak_ptr* previous = nullptr) noexcept; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_ERRORCALLBACKSETTER_H_ diff --git a/core/Properties/acsdkProperties/include/acsdkProperties/MiscStorageAdapter.h b/core/Properties/acsdkProperties/include/acsdkProperties/MiscStorageAdapter.h new file mode 100644 index 0000000000..537e98368e --- /dev/null +++ b/core/Properties/acsdkProperties/include/acsdkProperties/MiscStorageAdapter.h @@ -0,0 +1,129 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_MISCSTORAGEADAPTER_H_ +#define ACSDKPROPERTIES_MISCSTORAGEADAPTER_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +using alexaClientSDK::acsdkPropertiesInterfaces::PropertiesFactoryInterface; +using alexaClientSDK::avsCommon::sdkInterfaces::storage::MiscStorageInterface; + +/** + * @brief Interface to map properties config URI into component name and table name. + * + * This interface connects \ref PropertiesAPI and @c MiscStorageInterface. + * + * @c PropertiesFactoryInterface uses configuration URI to open properties container. When working with @c + * MiscStorageInterface this URI must be mapped into @a componentName and @a tableName parameters. + * + * @sa PropertiesAPI + * @sa alexaClientSDK::acsdkPropertiesInterfaces::PropertiesFactoryInterface + * @sa alexaClientSDK::avsCommon::sdkInterfaces::storage::MiscStorageInterface + * @ingroup PropertiesIMPL + */ +class MiscStorageUriMapperInterface { +public: + /// @brief Default destructor. + virtual ~MiscStorageUriMapperInterface() noexcept = default; + + /** + * @brief Extracts component name and table name from configuration URI. + * + * This method maps configuration URI from @c PropertiesFactoryInterface into component name and table name for @c + * MiscStorageInterface. + * + * This method must be idempotent, and always return the same result for the same input. + * + * @param[in] configUri Configuration URI. + * @param[out] componentName Component. + * @param[out] tableName Table name. + * + * @return True if operation succeeds. If false is returned the state of \a componentName and \a tableName is + * undefined. + */ + virtual bool extractComponentAndTableName( + const std::string& configUri, + std::string& componentName, + std::string& tableName) noexcept = 0; +}; + +/** + * @brief Generic URI mapper for MiscStorageInterface adapter. + * + * This object converts configuration URI into component name and table name. The object expects that the URI contains + * only component name and table name separated by a single character. For example, when parsing "component/tableName" + * URI and using '/' as a separator, the object will return "component" as a component name, and "tableName" as a table + * name. + * + * @sa createPropertiesFactory() + * @ingroup PropertiesIMPL + */ +class SimpleMiscStorageUriMapper : public MiscStorageUriMapperInterface { +public: + /** + * @brief Creates mapper instance. + * + * @param[in] sep Separator character. + * + * @return New object reference or nullptr on error. + */ + static std::shared_ptr create(char sep = '/') noexcept; + + /// @name MiscStorageUriMapperInterface methods + /// @{ + bool extractComponentAndTableName( + const std::string& configUri, + std::string& componentName, + std::string& tableName) noexcept override; + /// @} +private: + SimpleMiscStorageUriMapper(char separator) noexcept; + + const char m_separator; +}; + +/** + * @brief Creates @c PropertiesFactoryInterface from @c MiscStorageInterface. + * + * The method automatically creates database if it is not created. When user creates \c PropertiesInterface, the + * implementation automatically creates corresponding table. + * + * Because underlying interface supports only string properties, the implementation uses base64 encoding to store + * all binary properties. This may cause side effects, as when content is decoded using base64, the result may contain + * additional padding 0 bytes, and client code must work correctly in this case. + * + * @param[in] innerStorage Storage reference. This parameter must not be nullptr. + * @param[in] nameMapper Name mapper interface. This interface will be used to map configuration URI into table name + * and component name values when accessing \ref MiscStorageInterface API. + * + * @return Factory reference or nullptr on error. + * @ingroup PropertiesIMPL + */ +std::shared_ptr createPropertiesFactory( + const std::shared_ptr& innerStorage, + const std::shared_ptr& nameMapper = SimpleMiscStorageUriMapper::create()) noexcept; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_MISCSTORAGEADAPTER_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Asn1Helper.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Asn1Helper.h new file mode 100644 index 0000000000..4e53bdd315 --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Asn1Helper.h @@ -0,0 +1,169 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_ASN1HELPER_H_ +#define ACSDKPROPERTIES_PRIVATE_ASN1HELPER_H_ + +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +using acsdkCryptoInterfaces::AlgorithmType; +using acsdkCryptoInterfaces::DigestType; + +/** + * @brief Helper for ASN.1 operations. + * + * @ingroup PropertiesIMPL + */ +struct Asn1Helper { + /// @brief Byte vector type. + typedef std::vector Bytes; + + /** + * @brief Sets optional integer value. + * + * Sets optional integer value (with default) to ASN.1 container. If the value doesn't match the + * default value, and the container pointer is nullptr, a new container is allocated. If the value + * matches the default, and the container pointer is not nullptr, the memory is released. + * + * The underlying ASN.1 library implementation doesn't have a notion of "default value", so the + * entry must be nullptr, as otherwise it will be added to the output, which is against DER + * specification. + * + * @param[in] asn1Integer Reference to container pointer. The method may release or allocate memory + * and change pointer depending if the value matches the default one. + * @param[in] value Value to set. + * @param[in] defaultValue Default value to check against. + * + * @return True if operation is successful. + */ + static bool setOptInt(ASN1_INTEGER*& asn1Integer, int64_t value, int64_t defaultValue) noexcept; + + /** + * @brief Gets optional integer value. + * + * Gets optional integer value. If the container memory is not allocated, a default value is returned. + * + * @param[in] asn1Integer Reference to container pointer. If the pointer is nullptr, the default value is + * returned. + * @param[out] value Value destination. + * @param[in] defaultValue Default value to use if container pointer is nullptr. + * + * @return True if operation is successful. + */ + static bool getOptInt(ASN1_INTEGER*& asn1Integer, int64_t& value, int64_t defaultValue) noexcept; + + /** + * @brief Sets UTF8 string container value. + * + * @param asn1String Reference to container pointer. If pointer is nullptr, a new memory is allocated. + * @param value Value to set. + * @return True if operation is successful. + */ + static bool setStr(ASN1_UTF8STRING*& asn1String, const std::string& value) noexcept; + + /** + * @brief Gets UTF8 string from container. + * + * @param[in] asn1String Reference to container pointer. If pointer is nullptr, the operation fails. + * @param[out] value Value destination. + * + * @return True if operation is successful. + */ + static bool getStr(ASN1_UTF8STRING*& asn1String, std::string& value) noexcept; + + /** + * @brief Sets binary data container value. + * + * @param[in] asn1String Reference to container pointer. If pointer is nullptr, a new memory is allocated. + * @param[out] value Value destination. + * + * @return True if operation is successful. + */ + static bool setData(ASN1_OCTET_STRING*& asn1String, const Bytes& value) noexcept; + + /** + * @brief Gets binary data from container. + * + * @param[in] asn1String Reference to container pointer. If pointer is nullptr, the operation fails. + * @param[out] value Value destination. + * + * @return True if operation is successful. + */ + static bool getData(ASN1_OCTET_STRING*& asn1String, Bytes& value) noexcept; + + /** + * @brief Maps algorithm type into ASN.1 value. + * + * Maps Crypto API cipher algorithm type value into ASN.1 value. The method fails, if it doesn't + * recognize algorithm type. + * + * @param[in] type Algorithm type. + * @param[out] asn1Type ASN.1 value. + * + * @return True if operation is successful. + */ + static bool convertAlgTypeToAsn1(AlgorithmType type, int64_t& asn1Type) noexcept; + + /** + * @brief Maps ASN.1 value into algorithm type. + * + * Maps ASN.1 constant into Crypto API cipher algorithm type. The method fails, if it doesn't + * recognize algorithm type. + * + * @param[in] asn1Type ASN.1 constant. + * @param[out] type Destination for algorithm type. + * + * @return True if operation is successful. + */ + static bool convertAlgTypeFromAsn1(int64_t asn1Type, AlgorithmType& type) noexcept; + + /** + * @brief Maps digest type into ASN.1. + * + * Maps Crypto API digest algorithm type value into ASN.1 value. The method fails, if it doesn't + * recognize algorithm type. + * @param[in] type Algorithm type. + * @param[out] asn1Type ASN.1 constant. + * + * @return True if operation is successful. + */ + static bool convertDigTypeToAsn1(DigestType type, int64_t& asn1Type) noexcept; + + /** + * @brief Maps ASN.1 into digest type. + * + * Maps ASN.1 constant into Crypto API digest algorithm type. The method fails, if it doesn't + * recognize algorithm type. + * + * @param[in] asn1Type ASN.1 constant. + * @param[out] type Destination for algorithm type. + * + * @return True if operation is successful. + */ + static bool convertDigTypeFromAsn1(int64_t asn1Type, DigestType& type) noexcept; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_ASN1HELPER_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Asn1Types.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Asn1Types.h new file mode 100644 index 0000000000..d370a6ca0a --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Asn1Types.h @@ -0,0 +1,136 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_ASN1TYPES_H_ +#define ACSDKPROPERTIES_PRIVATE_ASN1TYPES_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +/// @addtogroup PropertiesIMPL +/// @{ + +/*- + * EncryptionDataVersion ::= INTEGER { v1(1) } + */ +constexpr int64_t ACSDK_DATA_KEY_VER_V1 = 1; +/*- + * DataVersion ::= INTEGER { v1(1) } + */ +constexpr int64_t ACSDK_DATA_VER_V1 = 1; + +/*- + * CipherAlgorithm ::= INTEGER { aes_256_gcm(1) } + */ +constexpr int64_t ACSDK_CIP_ALG_AES_256_GCM = 1; + +/*- + * DigestAlgorithm ::= INTEGER { sha_256(1) } + */ +constexpr int64_t ACSDK_DIG_ALG_SHA_256 = 1; + +/*- + * EncryptionData ::= SEQUENCE { + * version [0] EncryptionDataVersion DEFAULT v1, + * mainKeyAlias OCTET STRING, + * mainKeyChecksum OCTET STRING, + * dataKeyAlgorithm [1] CipherAlgorithm DEFAULT aes_256_gcm, + * dataKeyIV OCTET STRING, + * dataKeyCiphertext OCTET STRING, + * dataAlgorithm [2] CipherAlgorithm DEFAULT aes_256_gcm + * dataKeyTag [3] OCTET STRING, + * } + */ +/// Data structure to produce and parse DER for encryption key property data. +typedef struct EncryptionInfo { + /// @brief Default value for optional version. + constexpr static int64_t DEF_VER = ACSDK_DATA_KEY_VER_V1; + /// @brief Default value for optional data key encryption algorithm. + constexpr static int64_t DEF_DATA_KEY_ALG = ACSDK_CIP_ALG_AES_256_GCM; + /// @brief Default value for optional data encryption algorithm. + constexpr static int64_t DEF_DATA_ALG = ACSDK_CIP_ALG_AES_256_GCM; + + ASN1_INTEGER* version; // Optional + ASN1_UTF8STRING* mainKeyAlias; + ASN1_OCTET_STRING* mainKeyChecksum; + ASN1_INTEGER* dataKeyAlgorithm; // Optional + ASN1_OCTET_STRING* dataKeyIV; + ASN1_OCTET_STRING* dataKeyCiphertext; + ASN1_OCTET_STRING* dataKeyTag; + ASN1_INTEGER* dataAlgorithm; // Optional +} ACSDK_ENC_INFO; +DECLARE_ASN1_FUNCTIONS(ACSDK_ENC_INFO); + +/*- + * EncryptionProperty ::= SEQUENCE { + * encryptionData EncryptionData, + * digestAlgorithm [0] DigestAlgorithm DEFAULT sha_256, + * digest OCTET STRING + * } + */ +/// Data structure to produce and parse DER for encryption key property data. +typedef struct EncryptionProperty { + constexpr static int DEF_DIG_ALG = ACSDK_DIG_ALG_SHA_256; + + ACSDK_ENC_INFO* encryptionInfo; + ASN1_INTEGER* digestAlgorithm; // Optional + ASN1_OCTET_STRING* digest; +} ACSDK_ENC_PROP; +DECLARE_ASN1_FUNCTIONS(ACSDK_ENC_PROP); + +/*- + * DataInfo ::= SEQUENCE { + * version [0] DataVersion DEFAULT v1, + * dataIV OCTET STRING, + * dataCiphertext OCTET STRING, + * dataTag [1] OCTET STRING + * } + */ +/// Data structure to produce and parse DER for encrypted property data. +typedef struct DataInfo { + /// Default value for optional version. + constexpr static int64_t DEF_VER = ACSDK_DATA_VER_V1; + + ASN1_INTEGER* version; // Optional + ASN1_OCTET_STRING* dataIV; + ASN1_OCTET_STRING* dataCiphertext; + ASN1_OCTET_STRING* dataTag; +} ACSDK_DATA_INFO; +DECLARE_ASN1_FUNCTIONS(ACSDK_DATA_INFO); + +/*- + * DataProperty ::= SEQUENCE { + * dataInfo DataInfo, + * digestAlgorithm [0] DigestAlgorithm DEFAULT sha_256, + * digest OCTET STRING + * } + */ +/// Data structure to produce and parse DER for encrypted property data. +typedef struct DataProperty { + constexpr static int DEF_DIG_ALG = ACSDK_DIG_ALG_SHA_256; + ACSDK_DATA_INFO* dataInfo; + ASN1_INTEGER* digestAlgorithm; // Optional + ASN1_OCTET_STRING* digest; +} ACSDK_DATA_PROP; +DECLARE_ASN1_FUNCTIONS(ACSDK_DATA_PROP); + +/// @} + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_ASN1TYPES_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/DataPropertyCodec.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/DataPropertyCodec.h new file mode 100644 index 0000000000..ba37a974eb --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/DataPropertyCodec.h @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_DATAPROPERTYCODEC_H_ +#define ACSDKPROPERTIES_PRIVATE_DATAPROPERTYCODEC_H_ + +#include +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +/** + * @brief ASN.1 Encoder/Decoder for encrypted property value. + * + * This class provides top-level functions to encode encryption property value into DER format or decode it from DER + * format. + * + * @sa DataPropertyCodecState + * @ingroup PropertiesIMPL + */ +struct DataPropertyCodec { + /// Initialization vector data type. + typedef acsdkCryptoInterfaces::CryptoCodecInterface::IV IV; + /// Byte vector data type. + typedef acsdkCryptoInterfaces::CryptoCodecInterface::DataBlock DataBlock; + /// Tag data type. + typedef acsdkCryptoInterfaces::CryptoCodecInterface::Tag Tag; + + /** + * @brief Encodes encrypted property value into DER form. + * + * @param[in] cryptoFactory Crypto factory for digest operations. Must not be nullptr. + * @param[in] dataIV Initialization vector for encrypted data. + * @param[in] dataCiphertext Encrypted data. + * @param[in] dataTag Data tag. + * @param[out] derEncoded Reference to output data buffer. + * + * @return True if operation is successful. + */ + static bool encode( + const std::shared_ptr& cryptoFactory, + const IV& dataIV, + const DataBlock& dataCiphertext, + const Tag& dataTag, + DataBlock& derEncoded) noexcept; + + /** + * @brief Decodes encrypted property value from DER form. + * + * @param[in] cryptoFactory Crypto factory for digest operations. Must not be nullptr. + * @param[in] derEncoded DER-encoded property value. + * @param[out] dataIV Reference to container for initialization vector of encrypted data. + * @param[out] dataCiphertext Reference to container for encrypted data. + * @param[out] dataTag Reference to container for data tag. + * @param[out] digestDecoded Reference to container for decoded digest (from the DER message). + * @param[out] digestActual Reference to container for actual (recomputed) digest. + * + * @return True if operation is successful. + */ + static bool decode( + const std::shared_ptr& cryptoFactory, + const std::vector& derEncoded, + IV& dataIV, + DataBlock& dataCiphertext, + Tag& dataTag, + DataBlock& digestDecoded, + DataBlock& digestActual) noexcept; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_DATAPROPERTYCODEC_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/DataPropertyCodecState.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/DataPropertyCodecState.h new file mode 100644 index 0000000000..ff189d9f39 --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/DataPropertyCodecState.h @@ -0,0 +1,219 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_DATAPROPERTYCODECSTATE_H_ +#define ACSDKPROPERTIES_PRIVATE_DATAPROPERTYCODECSTATE_H_ + +#include +#include +#include +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +using acsdkCryptoInterfaces::DigestType; + +/** + * @brief Helper state for holding ASN.1 structures of DER codec for encrypted property value. + * + * @sa DataPropertyCodec + * @ingroup PropertiesIMPL + */ +class DataPropertyCodecState { +public: + /// @brief Initialization vector data type. + typedef acsdkCryptoInterfaces::CryptoCodecInterface::IV IV; + + /// @brief Byte vector data type. + typedef acsdkCryptoInterfaces::CryptoCodecInterface::DataBlock DataBlock; + + /// @brief Data tag type. + typedef acsdkCryptoInterfaces::CryptoCodecInterface::Tag Tag; + + /// @brief Object constructor + DataPropertyCodecState() noexcept; + + /// @brief Releases internal structures. + ~DataPropertyCodecState() noexcept; + + /** + * @brief Prepares structure for encoding operations. + * + * This method allocates internal structures and must be called before any setter method. + * + * @return True if operation is successful. + */ + bool prepareForEncode() noexcept; + + /** + * @brief Sets encoding version property. + * + * This method must be called after #prepareForEncode(). + * + * @param[in] version Version number. + * + * @return True if operation is successful. + */ + bool setVersion(int64_t version) noexcept; + + /** + * @brief Sets initialization vector property. + * + * This method must be called after #prepareForEncode(). + * + * @param[in] dataIV Initialization vector data. + * + * @return True if operation is successful. + */ + bool setDataIV(const IV& dataIV) noexcept; + + /** + * @brief Sets data ciphertext property. + * + * This method must be called after #prepareForEncode(). + * + * @param[in] dataCiphertext Ciphertext data. + * + * @return True if operation is successful. + */ + bool setDataCiphertext(const DataBlock& dataCiphertext) noexcept; + + /** + * @brief Sets data tag property. + * + * This method must be called after #prepareForEncode(). + * + * @param[in] dataTag Tag data. + * + * @return True if operation is successful. + */ + bool setDataTag(const Tag& dataTag) noexcept; + + /** + * @brief Sets digest type property. + * + * This method must be called after #prepareForEncode(). + * + * @param[in] type Digest type. + * + * @return True if operation is successful. + */ + bool setDigestType(DigestType type) noexcept; + + /** + * @brief Sets digest property. + * + * This method must be called after #prepareForEncode(). + * + * @param[in] digest Digest data. + * + * @return True if operation is successful. + */ + bool setDigest(const DataBlock& digest) noexcept; + + /** + * @brief Produces DER format according to stored properties. + * + * @param[out] der Reference to store DER-encoded data. + * + * @return True if operation is successful. + */ + bool encode(DataBlock& der) noexcept; + + /** + * @brief Method provides encoding version property. + * + * @param[out] version Reference to store encoding version result. + * + * @return True if operation is successful. + */ + bool getVersion(int64_t& version) noexcept; + + /** + * @brief Method provides data initialization vector. + * + * @param[out] dataIV Reference to store data initialization vector result. + * + * @return True if operation is successful. + */ + bool getDataIV(IV& dataIV) noexcept; + + /** + * @brief Method provides data ciphertext. + * + * @param[out] dataCiphertext Reference to store data ciphertext result. + * + * @return True if operation is successful. + */ + bool getDataCiphertext(DataBlock& dataCiphertext) noexcept; + + /** + * @brief Method provides data tag. + * + * @param[out] dataTag Reference to store data tag result. + * + * @return True if operation is successful. + */ + bool getDataTag(Tag& dataTag) noexcept; + + /** + * @brief Method provides digest type. + * + * @param[out] type Reference to store digest type. + * + * @return True if operation is successful. + */ + bool getDigestType(DigestType& type) noexcept; + + /** + * @brief Method provides digest value. + * + * @param[out] digest Reference to store digest value. + * @return True if operation is successful. + */ + bool getDigest(DataBlock& digest) noexcept; + + /** + * @brief Method to decode property fields from DER-encoded input. + * + * @param[in] der Der-encoded input. + * + * @return True if operation is successful. + */ + bool decode(const DataBlock& der) noexcept; + + /** + * Method encodes payload sequence for computing digest. DER-specification doesn't let multiple ways + * to encode the same data set, so the result will depend only on supplied values (either from setters + * or from decoding result). + * + * @param[out] der Reference to store result. + * @return True if operation is successful. + */ + bool encodeEncInfo(DataBlock& der) noexcept; + +private: + DataProperty* m_asn1Data; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_DATAPROPERTYCODECSTATE_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptedProperties.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptedProperties.h new file mode 100644 index 0000000000..af48871859 --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptedProperties.h @@ -0,0 +1,163 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_ENCRYPTEDPROPERTIES_H_ +#define ACSDKPROPERTIES_PRIVATE_ENCRYPTEDPROPERTIES_H_ + +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +/** + * @brief Properties adapter with field encryption. + * + * This class wraps underlying PropertiesInterface with encryption support. All property values are encrypted on save + * and decrypted on load. When this adapter initializes for the first time, it automatically encrypts all fields. To + * manage encryption key, additional data is stored with '$acsdkEncryption$' property name. This property contains + * algorithms to use and encrypted data key. The data key itself is encrypted using HSM key store. + * + * This class is thread safe and can be shared between multiple consumers. + * + * @ingroup PropertiesIMPL + */ +class EncryptedProperties : public alexaClientSDK::acsdkPropertiesInterfaces::PropertiesInterface { +public: + static std::shared_ptr create( + const std::string& configUri, + const std::shared_ptr& innerProperties, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept; + + /// @name PropertiesInterface methods. + /// @{ + bool getString(const std::string& key, std::string& value) noexcept override; + bool putString(const std::string& key, const std::string& value) noexcept override; + bool getBytes(const std::string& key, Bytes& value) noexcept override; + bool putBytes(const std::string& key, const Bytes& value) noexcept override; + bool remove(const std::string& key) noexcept override; + bool getKeys(std::unordered_set& valueContainer) noexcept override; + bool clear() noexcept override; + /// @} + +protected: + typedef acsdkCryptoInterfaces::KeyStoreInterface::IV IV; + typedef acsdkCryptoInterfaces::KeyStoreInterface::DataBlock DataBlock; + typedef acsdkCryptoInterfaces::KeyStoreInterface::KeyChecksum KeyChecksum; + typedef acsdkCryptoInterfaces::CryptoCodecInterface::Key Key; + typedef acsdkCryptoInterfaces::CryptoCodecInterface::Tag Tag; + + // Constructor. + EncryptedProperties( + const std::string& configUri, + const std::shared_ptr& innerProperties, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept; + + // Container initialization. + bool init() noexcept; + // Encryption upgrade. + StatusCode upgradeEncryption(RetryExecutor& executor, const std::unordered_set& keys) noexcept; + // Prepare data key + StatusCode loadAndDecryptDataKey(RetryExecutor& executor) noexcept; + + // Data property operations. + bool encryptAndEncodePropertyValue( + const std::string& key, + const Bytes& plaintext, + Bytes& encodedCiphertext) noexcept; + bool decodeAndDecryptPropertyValue( + const std::string& key, + const Bytes& encodedCiphertext, + Bytes& plaintext) noexcept; + bool encryptAndPutInternal(const std::string& key, const Bytes& plaintext) noexcept; + bool getAndDecryptInternal(const std::string& key, Bytes& plaintext) noexcept; + + // Encryption property operations. + StatusCode generateAndStoreDataKeyWithRetries(RetryExecutor& executor) noexcept; + + // Inner properties operations + bool loadKeysWithRetries(RetryExecutor& executor, std::unordered_set& keys) noexcept; + bool storeValueWithRetries( + RetryExecutor& executor, + const std::string& key, + const Bytes& data, + bool canDrop) noexcept; + bool loadValueWithRetries(RetryExecutor& executor, const std::string& key, Bytes& data) noexcept; + bool deleteValueWithRetries(RetryExecutor& executor, const std::string& key) noexcept; + bool clearAllValuesWithRetries(RetryExecutor& executor) noexcept; + bool executeKeyOperationWithRetries( + RetryExecutor& executor, + const std::string& operationName, + const std::string& key, + const std::function& operation) noexcept; + + /** + * @brief Generate new data key. + * + * Method generates new data key and stores it in this instance. If there is an error, the method attempts to + * do retries. + * + * @param[in] helper Executor for perform operation with retries. + * @return True if operation succeeds, false otherwise. + */ + bool generateDataKeyWithRetries(RetryExecutor& executor) noexcept; + bool encryptAndEncodeDataKeyWithRetries(RetryExecutor& executor, Bytes& encoded) noexcept; + StatusCode decodeAndDecryptDataKey(const Bytes& encoded) noexcept; + bool encryptDataKey( + std::string& mainKeyAlias, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType& algorithmType, + KeyChecksum& mainKeyChecksum, + IV& dataKeyIV, + DataBlock& dataKeyCiphertext, + Tag& dataKeyTag) noexcept; + bool decryptDataKey( + const std::string& mainKeyAlias, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType dataKeyAlgorithm, + const KeyChecksum& mainKeyChecksum, + const IV& dataKeyIV, + const DataBlock& keyCiphertext, + const Tag& dataKeyTag) noexcept; + + bool doClear(RetryExecutor& helper) noexcept; + + /// Configuration namespace (for error callbacks). + const std::string m_configUri; + + /// Underlying storage interface. + const std::shared_ptr m_innerProperties; + + /// Cryptography service factory. + const std::shared_ptr m_cryptoFactory; + + /// HSM keystore interface. + const std::shared_ptr m_keyStore; + + /// Actual algorithm type in use. + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType m_dataAlgorithmType; + + /// Data key in use + Key m_dataKey; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_ENCRYPTEDPROPERTIES_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptedPropertiesFactory.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptedPropertiesFactory.h new file mode 100644 index 0000000000..aad999455c --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptedPropertiesFactory.h @@ -0,0 +1,85 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_ENCRYPTEDPROPERTIESFACTORY_H_ +#define ACSDKPROPERTIES_PRIVATE_ENCRYPTEDPROPERTIESFACTORY_H_ + +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +/** + * @brief Properties factory wrapper to encrypt all properties. + * + * This factory works with @name EncryptedProperties class to ensure all property values are stored in encrypted form + * in the underlying storage. + * + * @ingroup PropertiesIMPL + */ +class EncryptedPropertiesFactory : public alexaClientSDK::acsdkPropertiesInterfaces::PropertiesFactoryInterface { +public: + /** + * @brief Creates properties factory using given dependencies. + * + * @param[in] innerFactory Internal factory for accessing properties in plain text manner. + * @param[in] cryptoFactory Encryption facilities factory. + * @param[in] keyStore HSM key store. + * + * @return Reference to factory or nullptr on error. + */ + static std::shared_ptr create( + const std::shared_ptr& innerFactory, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept; + + /// @name PropertiesFactoryInterface methods + ///@{ + std::shared_ptr getProperties( + const std::string& configUri) noexcept override; + ///@} + +private: + /** + * @brief Constructs factory. + * + * @param[in] innerFactory Internal factory for accessing properties in plain text manner. + * @param[in] cryptoFactory Encryption facilities factory. + * @param[in] keyStore HSM key store. + */ + EncryptedPropertiesFactory( + const std::shared_ptr& innerFactory, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept; + + bool init() noexcept; + + /// Nested unencrypted properties factory. + const std::shared_ptr m_storage; + /// Cryptography service factory. + const std::shared_ptr m_cryptoFactory; + /// HSM keystore interface. + const std::shared_ptr m_keyStore; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_ENCRYPTEDPROPERTIESFACTORY_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptionKeyPropertyCodec.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptionKeyPropertyCodec.h new file mode 100644 index 0000000000..a7afc61f65 --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptionKeyPropertyCodec.h @@ -0,0 +1,113 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_ENCRYPTIONKEYPROPERTYCODEC_H_ +#define ACSDKPROPERTIES_PRIVATE_ENCRYPTIONKEYPROPERTYCODEC_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +/** + * @brief ASN.1 Codec API for Encryption Key Property Encoding. + * + * This class provides top-level functions to encode encryption key property into DER format or decode it from DER + * format. + * + * @sa EncryptionKeyPropertyCodecState + * @ingroup PropertiesIMPL + */ +struct EncryptionKeyPropertyCodec { + typedef acsdkCryptoInterfaces::KeyStoreInterface::KeyChecksum KeyChecksum; + typedef acsdkCryptoInterfaces::KeyStoreInterface::DataBlock DataBlock; + typedef acsdkCryptoInterfaces::KeyStoreInterface::IV IV; + typedef acsdkCryptoInterfaces::CryptoCodecInterface::Tag Tag; + typedef acsdkPropertiesInterfaces::PropertiesInterface::Bytes Bytes; + + /** + * @brief Produces encryption key property in DER form. + * + * @param[in] cryptoFactory Crypto API factory. + * @param[in] mainKeyAlias Main key alias. + * @param[in] mainKeyChecksum Main key checksum. + * @param[in] dataKeyAlgorithm Algorithm used to wrap data key. + * @param[in] dataKeyIV Initialization vector used to wrap data key. + * @param[in] dataKeyCiphertext Wrapped data key. + * @param[in] dataKeyTag Data key tag. + * @param[in] dataAlgorithm Algorithm for data encryption. + * + * @param[out] derEncoded Encoded properties in DER format. + * + * @return True on success. + */ + static bool encode( + const std::shared_ptr& cryptoFactory, + const std::string& mainKeyAlias, + const KeyChecksum& mainKeyChecksum, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType dataKeyAlgorithm, + const IV& dataKeyIV, + const DataBlock& dataKeyCiphertext, + const Tag& dataKeyTag, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType dataAlgorithm, + Bytes& derEncoded) noexcept; + + /** + * @brief Decode encryption key property. + * + * This method parses DER input and extracts encoding fields. Method also computes actual digest for digest + * verification. + * + * @param[in] cryptoFactory Crypto API factory. + * @param[in] derEncoded DER-encoded properties. + * @param[out] mainKeyAlias Parsed main key alias. + * @param[out] mainKeyChecksum Parsed main key checksum. + * @param[out] dataKeyAlgorithm Parsed algorithm for data key unwrapping. + * @param[out] dataKeyIV Parsed initialization vector for data key unwrapping. + * @param[out] dataKeyCiphertext Parsed wrapped data key. + * @param[out] dataKeyTag Parsed data key tag. + * @param[out] dataAlgorithm Parsed algorithm to encrypt/decrypt data. + * @param[out] digestDecoded Parsed digest. + * @param[out] digestActual Actual (recomputed) digest. + * + * @return True on success. + */ + static bool decode( + const std::shared_ptr& cryptoFactory, + const Bytes& derEncoded, + std::string& mainKeyAlias, + KeyChecksum& mainKeyChecksum, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType& dataKeyAlgorithm, + IV& dataKeyIV, + DataBlock& dataKeyCiphertext, + Tag& dataKeyTag, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType& dataAlgorithm, + DataBlock& digestDecoded, + DataBlock& digestActual) noexcept; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_ENCRYPTIONKEYPROPERTYCODEC_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptionKeyPropertyCodecState.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptionKeyPropertyCodecState.h new file mode 100644 index 0000000000..93d5cfa3cd --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/EncryptionKeyPropertyCodecState.h @@ -0,0 +1,281 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_ENCRYPTIONKEYPROPERTYCODECSTATE_H_ +#define ACSDKPROPERTIES_PRIVATE_ENCRYPTIONKEYPROPERTYCODECSTATE_H_ + +#include +#include +#include +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +using acsdkCryptoInterfaces::AlgorithmType; +using acsdkCryptoInterfaces::DigestType; + +/** + * @brief ASN.1 Codec state for encryption key property. + * + * This class contains data for DER encoding and decoding of encryption key property. + * + * @sa EncryptionKeyPropertyCodec + * @ingroup PropertiesIMPL + */ +class EncryptionKeyPropertyCodecState { +public: + /// Initialization vector data type. + typedef acsdkCryptoInterfaces::KeyStoreInterface::IV IV; + /// Key checksum data type. + typedef acsdkCryptoInterfaces::KeyStoreInterface::KeyChecksum KeyChecksum; + /// Byte vector data type. + typedef acsdkCryptoInterfaces::KeyStoreInterface::DataBlock DataBlock; + /// Data tag type. + typedef acsdkCryptoInterfaces::CryptoCodecInterface::Tag Tag; + + EncryptionKeyPropertyCodecState() noexcept; + ~EncryptionKeyPropertyCodecState() noexcept; + + /** + * @brief Prepares object for encoding. + * + * This method allocates internal structures to hold properties before they are set for encoding. This method + * must be called before any setter method. + * + * @return True on success. + */ + bool prepareForEncoding() noexcept; + + /** + * @brief Sets version property for encoding. + * + * @param[in] version Version value. + * + * @return True on success. + */ + bool setVersion(int64_t version) noexcept; + + /** + * @brief Get version property after decoding. + * + * @param[out] version Version value. + * + * @return True on success. + */ + bool getVersion(int64_t& version) noexcept; + + /** + * @brief Sets main key alias for encoding. + * + * @param[in] mainKeyAlias Main key alias. + * + * @return True on success. + */ + bool setMainKeyAlias(const std::string& mainKeyAlias) noexcept; + + /** + * @brief Get main key alias after decoding. + * + * @param[out] mainKeyAlias Main key alias. + * + * @return True on success. + */ + bool getMainKeyAlias(std::string& mainKeyAlias) noexcept; + + /** + * @brief Set main key checksum for encoding. + * + * @param[in] mainKeyChecksum Main key checksum. + * + * @return True on success. + */ + bool setMainKeyChecksum(const KeyChecksum& mainKeyChecksum) noexcept; + + /** + * @brief Get main key checksum after decoding. + * + * @param[out] mainKeyChecksum Main key checksum. + * + * @return True on success. + */ + bool getMainKeyChecksum(KeyChecksum& mainKeyChecksum) noexcept; + + /** + * @brief Set data key wrapping algorithm for encoding. + * + * @param[in] type Data key wrapping algorithm. + * + * @return True on success. + */ + bool setDataKeyAlgorithm(AlgorithmType type) noexcept; + + /** + * @brief Get data key wrapping algorithm after decoding. + * + * @param[out] type Data key wrapping algorithm. + * + * @return True on success. + */ + bool getDataKeyAlgorithm(AlgorithmType& type) noexcept; + + /** + * @brief Set data key IV for encoding. + * + * @param[in] dataKeyIV Initialization vector to unwrap data key. + * + * @return True on success. + */ + bool setDataKeyIV(const IV& dataKeyIV) noexcept; + + /** + * @brief Set data key IV for encoding. + * + * @param[out] dataKeyIV Initialization vector to unwrap data key. + * + * @return True on success. + */ + bool getDataKeyIV(IV& dataKeyIV) noexcept; + + /** + * @brief Set data key ciphertext for encoding. + * + * @param[in] dataKeyCiphertext Wrapped data key. + * + * @return True on success. + */ + bool setDataKeyCiphertext(const DataBlock& dataKeyCiphertext) noexcept; + + /** + * @brief Get data ciphertext after decoding. + * + * @param[out] dataKeyCiphertext Wrapped data key. + * + * @return True on success. + */ + bool getDataKeyCiphertext(DataBlock& dataKeyCiphertext) noexcept; + + /** + * @brief Set data key tag for encoding. + * + * @param[in] dataKeyTag Data key tag. + * + * @return True on success. + */ + bool setDataKeyTag(const Tag& dataKeyTag) noexcept; + + /** + * @brief Get data key tag after decoding. + * + * @param[out] dataKeyTag Data key tag. + * + * @return True on success. + */ + bool getDataKeyTag(Tag& dataKeyTag) noexcept; + + /** + * @brief Set data algorithm for encoding. + * + * @param[in] type Data encryption algorithm. + * + * @return True on success. + */ + bool setDataAlgorithm(AlgorithmType type) noexcept; + + /** + * @brief Get data algorithm after decoding. + * + * @param[in] type Data encryption algorithm. + * + * @return True on success. + */ + bool getDataAlgorithm(AlgorithmType& type) noexcept; + + /** + * @brief Set digest type for encoding. + * + * @param[in] type Digest type. + * + * @return True on success. + */ + bool setDigestType(DigestType type) noexcept; + + /** + * @brief Get digest type after decoding. + * + * @param[out] type Digest type. + * + * @return True on success. + */ + bool getDigestType(DigestType& type) noexcept; + + /** + * @brief Set digest for encoding. + * + * @param[in] digest Digest value. + * + * @return True on success. + */ + bool setDigest(const DataBlock& digest) noexcept; + + /** + * @brief Get digest after decoding. + * + * @param[out] digest Digest value. + * @return True on success. + */ + bool getDigest(DataBlock& digest) noexcept; + + /** + * @brief Encodes data block for digest computation. + * + * This method encodes data block (payload without digest fields) for digest computation. Digest is computed using + * DER-encoded input. + * + * @param[out] der Encoded data block. + * @return True on success. + */ + bool encodeEncInfo(DataBlock& der) noexcept; + + /** + * @brief Encodes stored properties in DER form. + * + * @param[out] der DER-encoded properties. + * + * @return True on success. + */ + bool encode(DataBlock& der) noexcept; + + /** + * @brief Decodes DER input and internally stores decoded properties. + * + * @param[in] der DER-encoded properties. + * + * @return True on success. + */ + bool decode(const DataBlock& der) noexcept; + +private: + EncryptionProperty* m_asn1Data; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_ENCRYPTIONKEYPROPERTYCODECSTATE_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Logging.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Logging.h new file mode 100644 index 0000000000..2e62f520a3 --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/Logging.h @@ -0,0 +1,93 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_LOGGING_H_ +#define ACSDKPROPERTIES_PRIVATE_LOGGING_H_ + +#include + +/** + * @brief Create a LogEntry. + * + * Create a LogEntry using TAG constant and the specified event string. + * + * @param[in] event The event string for this @c LogEntry. + * @private + * @ingroup acsdkProperties + */ +#define LX(event) ::alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/** + * @brief Create a LogEntry for configuration event. + * + * @param[in] event The event string for this @c LogEntry. + * @param[in] configUri Configuration URI. + * @private + * @ingroup acsdkProperties + */ +#define LX_CFG(event, configUri) LX(event).d(::alexaClientSDK::acsdkProperties::CONFIG_URI, configUri) + +/** + * @brief Create a LogEntry for configuration event. + * + * @param[in] event The event string for this @c LogEntry. + * @param[in] configUri Configuration URI. + * @param[in] key Key name. + * @private + * @ingroup acsdkProperties + */ +#define LX_CFG_KEY(event, configUri, key) LX_CFG(event, configUri).d(::alexaClientSDK::acsdkProperties::KEY, key) + +/** + * @brief Macro to help compiler happy when variable is unused. + * + * When variables are passed for logging events, compiler complains when logging is disabled (as variables become + * unused. This macro marks variable as used to suppress build errors. + * + * @param[in] var Variable name. + * + * @private + * @ingroup acsdkProperties + */ +#define ACSDK_UNUSED_VARIABLE(var) (void)var + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace logger { + +template <> +LogEntry& LogEntry::d(const char* key, const std::vector& value); + +} // namespace logger +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +namespace alexaClientSDK { +namespace acsdkProperties { + +/// String to identify config URI. +/// @private +extern const std::string CONFIG_URI; + +/// String to identify key. +/// @private +extern const std::string KEY; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_LOGGING_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/MiscStorageProperties.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/MiscStorageProperties.h new file mode 100644 index 0000000000..69bbb77f62 --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/MiscStorageProperties.h @@ -0,0 +1,116 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_MISCSTORAGEPROPERTIES_H_ +#define ACSDKPROPERTIES_PRIVATE_MISCSTORAGEPROPERTIES_H_ + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +/** + * @brief Properties for MiscStorageInterface. + * + * This class adapts @c MiscStorageInterface into @c PropertiesInterface. + * + * This class is thread safe and can be shared between multiple consumers. + * + * @see alexaClientSDK::acsdkPropertiesInterfaces::PropertiesInterface + * @see alexaClientSDK::avsCommon::sdkInterfaces::storage::MiscStorageInterface + * + * @ingroup PropertiesIMPL + */ +class MiscStorageProperties : public alexaClientSDK::acsdkPropertiesInterfaces::PropertiesInterface { +public: + /** + * @brief Factory method. + * + * This method creates a new object instance to access configuration properties. + * + * @param storage Interface for data access. + * @param configUri Configuration URI. + * @param componentName Component name for data access calls. + * @param tableName Table name for data access calls. + * + * @return New object reference or nullptr on error. + */ + static std::shared_ptr create( + const std::shared_ptr& storage, + const std::string& configUri, + const std::string& componentName, + const std::string& tableName); + + /// @name PropertiesInterface methods + /// @{ + bool getString(const std::string& key, std::string& value) noexcept override; + bool putString(const std::string& key, const std::string& value) noexcept override; + bool getBytes(const std::string& key, Bytes& value) noexcept override; + bool putBytes(const std::string& key, const Bytes& value) noexcept override; + bool remove(const std::string& key) noexcept override; + bool getKeys(std::unordered_set& valueContainer) noexcept override; + bool clear() noexcept override; + /// @} + +private: + // Constructor. + MiscStorageProperties( + const std::shared_ptr& storage, + const std::string& configUri, + const std::string& componentName, + const std::string& tableName); + + /** + * @brief Initialization helper. + * + * This method ensures the underlying table is present. + * + * @return true on success, false on error. + */ + bool init(); + + // Inner properties operations + bool loadKeysWithRetries(RetryExecutor& executor, std::unordered_set& keys) noexcept; + bool executeRetryableKeyAction( + RetryExecutor& executor, + const std::string& actionName, + const std::string& key, + const std::function& action, + bool canCleanup, + bool failOnCleanup) noexcept; + bool deleteValueWithRetries(RetryExecutor& executor, const std::string& key) noexcept; + bool clearAllValuesWithRetries(RetryExecutor& executor) noexcept; + + /// Inner storage interface for data access. + const std::shared_ptr m_storage; + + /// Configuration URI. + const std::string m_configUri; + + /// Component name for data access API. + const std::string m_componentName; + + /// Table name for data access API. + const std::string m_tableName; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_MISCSTORAGEPROPERTIES_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/MiscStoragePropertiesFactory.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/MiscStoragePropertiesFactory.h new file mode 100644 index 0000000000..fb9fd8af22 --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/MiscStoragePropertiesFactory.h @@ -0,0 +1,77 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_MISCSTORAGEPROPERTIESFACTORY_H_ +#define ACSDKPROPERTIES_PRIVATE_MISCSTORAGEPROPERTIESFACTORY_H_ + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +using alexaClientSDK::acsdkPropertiesInterfaces::PropertiesFactoryInterface; +using alexaClientSDK::acsdkPropertiesInterfaces::PropertiesInterface; +using alexaClientSDK::avsCommon::sdkInterfaces::storage::MiscStorageInterface; + +/** + * @brief Properties factory for MiscStorageInterface. + * + * This class adapts \ref MiscStorageInterface into \ref PropertiesFactoryInterface. + * + * @ingroup PropertiesIMPL + */ +class MiscStoragePropertiesFactory : public PropertiesFactoryInterface { +public: + static std::shared_ptr create( + const std::shared_ptr& storage, + const std::shared_ptr& uriMapper) noexcept; + + /// @name PropertiesFactoryInterface methods + /// @{ + std::shared_ptr getProperties(const std::string& configURI) noexcept override; + /// @} + +private: + MiscStoragePropertiesFactory( + const std::shared_ptr& storage, + const std::shared_ptr& uriMapper) noexcept; + + /// Helper to initialize the factory. + bool init() noexcept; + + /// Helper to cleanup \a m_openProperties from expired references. + void dropNullReferences() noexcept; + + /// Inner storage reference. + const std::shared_ptr m_storage; + + /// URI mapper to determine component name and table name. + const std::shared_ptr m_uriMapper; + + /// Mutex to serialize access to properties cache. + std::mutex m_stateMutex; + + /// Properties cache to return the same object reference as long as it is in use. + std::unordered_map> m_openProperties; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_MISCSTORAGEPROPERTIESFACTORY_H_ diff --git a/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/RetryExecutor.h b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/RetryExecutor.h new file mode 100644 index 0000000000..f02b89921e --- /dev/null +++ b/core/Properties/acsdkProperties/privateInclude/acsdkProperties/private/RetryExecutor.h @@ -0,0 +1,251 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIES_PRIVATE_RETRYEXECUTOR_H_ +#define ACSDKPROPERTIES_PRIVATE_RETRYEXECUTOR_H_ + +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkProperties { + +/** + * @brief Tracked operation types for error callbacks. + */ +enum class OperationType { + Open, ///< Failed call to open properties container. + Get, ///< Failed call to get property. + Put, ///< Failed call to put property. + Other, ///< Failed call to other operation. +}; + +/** + * @brief Operation result from a retryable operation. + * + * Retryable operation completes with one of three outcomes: success, failure, or retryable failure. If result is + * retryable failure, the executor may restart the operation or fail it. + * + * @sa RetryExecutor + * @sa StatusCodeWithRetry + */ +enum class RetryableOperationResult { + Success, ///< Operation completed with success. + Failure, ///< Operation has failed. + Cleanup, ///< Operation has failed and cleanup is requested. +}; + +/** + * @brief Status code with a retry flag. + * + * A combination of a status code with retry flag. Retry executor uses status code to propagate to error callback, and + * retry flag to determine if operation is actually retyable. + * + * @sa RetryExecutor + * @sa RetryableOperationResult + */ +typedef std::pair StatusCodeWithRetry; + +/** + * @brief Helper class to execute with retries. + * + * This class handles operation errors and retries. Whenever operation fails, an error callback is notified, and then + * decision is made to retry operation, fail it, or mark operation for cleanup action. + * + * Executor isn't aware of operation specifics, but it gets \ref OperationType and namespace URI when constructed, + * executes given operation, and works with \ref RetryableOperationResult. For improved debugging, the classes. + * + * This class also provides functions to set (change) error callback interface to use whenever an operation encounters + * an error. The number of retries can be limited, and when the retry limit is reached, the class marks operation as + * failed even if error callback requests a retry. + * + * The class uses the same retry counter for all invocations, so if any of the operations fail, this reduces total + * number of retry attempts. + * + * The class shall be used as follows: + * @code + * RetryExecutor executor(OperationType::Open, "namespaceUri"); + * auto action = execute("actionName", []() -> RetryableOperationResult { + * .. do something + * if (success) { + * return RetryExecutor::SUCCESS; + * } else { + * // Indicate the operation has failed, but the failure is retryable. + * return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + * } + * }, Action::FAIL); + * + * // now follow up on result + * switch (action} { + * when Action::SUCCESS: + * // Handle success. + * ... + * break; + * when Action::FAIL: + * // Handle failure. + * ... + * break; + * when Action::CLEAR_DATA: + * // Clear data and continue. + * ... + * break; + * @endcode + * + * @sa ErrorCallbackInterface + * @sa setErrorCallback() + * + * @ingroup PropertiesIMPL + */ +class RetryExecutor { +public: + /** + * @brief Retryable operation. + * + * \ref RetryExecutor invokes retryable operation and checks the result. If the result is success, it is propagated + * to the caller, otherwise error callback is invoked, and operation may be retried. + * + * sa #execute() + */ + typedef std::function RetryableOperation; + + /// @brief Success result. + static const StatusCodeWithRetry SUCCESS; + /// @brief Retryable cryptography error. + static const StatusCodeWithRetry RETRYABLE_CRYPTO_ERROR; + /// @brief Non-retryable cryptography error. + static const StatusCodeWithRetry NON_RETRYABLE_CRYPTO_ERROR; + /// @brief Retryable HSM error. + static const StatusCodeWithRetry RETRYABLE_HSM_ERROR; + /// @brief Retryable inner properties interface error. + static const StatusCodeWithRetry RETRYABLE_INNER_PROPERTIES_ERROR; + /// @brief Non-retryable inner properties interface error. + static const StatusCodeWithRetry NON_RETRYABLE_INNER_PROPERTIES_ERROR; + + /** + * @brief Sets an error callback. + * + * This method can both set a new callback or clear existing one if \a callback is nullptr. Changing callback + * affects error handling of Property API methods that are called after the callback is changed. + * + * @param[in] callback New callback reference or nullptr to remove callback. + * @param[in] maxRetries Maximum number of retries to use with this callback. If implementation encounters more + * errors, than number of \a maxRetries plus one, the operation fails. If \ref UNLIMITED_RETRIES value is specified, + * the implementation executes unlimited number of retries until operation succeeds or \a callback indicates that + * operation must stop. + * @param[out] previous Optional pointer to store previous callback. + * + * @return Boolean indicating operation success. On failure, contents of *previous is undefined and false is + * returned. + * + * @ingroup PropertiesIMPL + */ + static bool setErrorCallback( + const std::weak_ptr& callback, + uint32_t maxRetries, + std::weak_ptr* previous = nullptr) noexcept; + + /** + * @brief Constructs helper object. + * + * This method atomically captures configured callback interface and maximum retry count, so that all retries will + * user the same callback interface and retry limit parameters. + * + * @param[in] operationType Operation type. + * @param[in] configUri Configuration URI. + */ + RetryExecutor(OperationType operationType, const std::string& configUri); + + /** + * @brief Execute retryable operation. + * + * This method executes operation until it returns OperationResult::Success, OperationResult::Failure, or there + * are no more retry attempts left. + * + * If operation execution fails with error, an error callback is called. If callback returns Action::CONTINUE, then + * value of \a continueAction is used. If operation result was retryable, and desired action is Action::RETRY, + * executor retries operation unless there were too many execution attempts. + * + * This method doesn't rest retry counter, so when this method is called for the same instance, the number of + * retries left decreases. + * + * @param[in] actionName Operation name for logging. + * @param[in] operation Operation to execute. The operation must return status code, and flag, if the operation + * may be retried. + * @param[in] continueAction Default action to use if error callback returns Action::CONTINUE. This parameter must + * not be Action::CONTINUE. + * + * @return Status code from the last attempted execution of \a operation. + */ + RetryableOperationResult execute( + const std::string& actionName, + const RetryableOperation& operation, + Action continueAction) noexcept; + +private: + /** + * @brief Helper to invoke error callback for failed operation. + * + * This method increments retry counter, and then invokes error callback. If retry counter exceed maximum value, + * the method returns Action::FAIL. + * + * @param[in] status Error type. + * + * @return Error callback invocation result or Action::DEFAULT_ACTION if error callback is not configured. If the + * number of retries exceed the predefined limit, the method returns Action::FAIL. + * + * @sa ErrorCallbackInterface#onOpenPropertiesError + */ + Action invokeErrorCallback(StatusCode status) noexcept; + + /** + * @brief Check if action value is valid as a default one. + * + * The method checks if action is one of Action::FAIL, Action::RETRY, or Action::CLEAR_DATA. + * + * @param[in] continueAction Action value to test. + * @return True, if \a continueAction is one of Action::FAIL, Action::RETRY, or Action::CLEAR_DATA. Returns false + * otherwise. + */ + bool isValidContinueAction(Action continueAction) noexcept; + + /// Mutex for controlling access to error callback reference. + static std::mutex c_stateMutex; + + /// Maximum number of retries. + static volatile uint32_t c_maxRetries; + + /// Error callback reference. + static std::weak_ptr c_callback; + + /// Operation type for selecting callback method. + const OperationType m_operationType; + + /// Config URI for callbacks. + const std::string m_configUri; + + /// Retry counter to prevent infinite loops. + uint32_t m_retryCounter; + + /// Instance-specific callback reference. + std::shared_ptr m_callback; +}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIES_PRIVATE_RETRYEXECUTOR_H_ diff --git a/core/Properties/acsdkProperties/src/Asn1Helper.cpp b/core/Properties/acsdkProperties/src/Asn1Helper.cpp new file mode 100644 index 0000000000..324e25adb2 --- /dev/null +++ b/core/Properties/acsdkProperties/src/Asn1Helper.cpp @@ -0,0 +1,190 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +/** + * @brief Macro for cutting off OpenSSL features introduced before 1.1.0 release. + * @private + * @ingroup PropertiesIMPL + */ +#define OPENSSL_VERSION_NUMBER_1_1_0 0x10100000L + +namespace alexaClientSDK { +namespace acsdkProperties { + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"Asn1Helper"}; + +/// OK status code for some OpenSSL operations. +/// @private +static constexpr int OPENSSL_OK = 1; + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_NUMBER_1_1_0 +/// ERROR status code for some OpenSSL operations. +/// @private +static constexpr int OPENSSL_ERROR = -1; +#endif + +bool Asn1Helper::setOptInt(ASN1_INTEGER*& asn1Integer, int64_t value, int64_t defaultValue) noexcept { + if (value == defaultValue) { + // If we set optional value to default, we actually need to remove item from DER output. + if (asn1Integer) { + ASN1_INTEGER_free(asn1Integer); + asn1Integer = nullptr; + } + return true; + } else { + if (!asn1Integer && !(asn1Integer = ASN1_INTEGER_new())) { + // Failed to allocate memory for ASN.1 integer type. + ACSDK_ERROR(LX("setOptIntFailed").m("newIntegerFailed")); + return false; + } else { +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_NUMBER_1_1_0 + return OPENSSL_OK == ASN1_INTEGER_set_int64(asn1Integer, value); +#else + if (value <= LONG_MAX && value >= LONG_MIN) { + return OPENSSL_OK == ASN1_INTEGER_set(asn1Integer, (long)value); + } else { + return false; + } +#endif + } + } +} + +bool Asn1Helper::getOptInt(ASN1_INTEGER*& asn1Integer, int64_t& value, int64_t defaultValue) noexcept { + if (!asn1Integer) { + value = defaultValue; + return true; + } else { +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_NUMBER_1_1_0 + return OPENSSL_OK == ASN1_INTEGER_get_int64(&value, asn1Integer); +#else + long tmp = ASN1_INTEGER_get(asn1Integer); + if (OPENSSL_ERROR != tmp) { + value = tmp; + return true; + } else { + return false; + } +#endif + } +} + +bool Asn1Helper::setStr(ASN1_UTF8STRING*& asn1String, const std::string& value) noexcept { + if (!asn1String && !(asn1String = ASN1_UTF8STRING_new())) { + ACSDK_ERROR(LX("setStrFailed").m("newStringFailed")); + return false; + } else { + return OPENSSL_OK == ASN1_STRING_set(asn1String, value.c_str(), value.size()); + } +} + +bool Asn1Helper::getStr(ASN1_UTF8STRING*& asn1String, std::string& value) noexcept { + if (!asn1String) { + return false; + } else { + int size = ASN1_STRING_length(asn1String); +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_NUMBER_1_1_0 + const unsigned char* data = ASN1_STRING_get0_data(asn1String); +#else + const unsigned char* data = ASN1_STRING_data(asn1String); +#endif + value.assign((const char*)data, size); + return true; + } +} + +bool Asn1Helper::setData(ASN1_OCTET_STRING*& asn1String, const Bytes& value) noexcept { + if (!asn1String && !(asn1String = ASN1_OCTET_STRING_new())) { + ACSDK_ERROR(LX("setDataFailed").m("newOctetStringFailed")); + return false; + } else { + return OPENSSL_OK == ASN1_OCTET_STRING_set(asn1String, value.data(), value.size()); + } +} + +bool Asn1Helper::getData(ASN1_OCTET_STRING*& asn1String, Bytes& value) noexcept { + if (!asn1String) { + return false; + } else { + int size = ASN1_STRING_length(asn1String); +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_NUMBER_1_1_0 + const unsigned char* data = ASN1_STRING_get0_data(asn1String); +#else + const unsigned char* data = ASN1_STRING_data(asn1String); +#endif + value.resize(size); + memcpy(value.data(), data, size); + return true; + } +} + +bool Asn1Helper::convertAlgTypeToAsn1(AlgorithmType type, int64_t& asn1Type) noexcept { + switch (type) { + case AlgorithmType::AES_256_GCM: + asn1Type = ACSDK_CIP_ALG_AES_256_GCM; + break; + default: + ACSDK_ERROR(LX("convertAlgTypeToAsn1Failed").d("type", type)); + return false; + } + return true; +} + +bool Asn1Helper::convertAlgTypeFromAsn1(int64_t asn1Type, AlgorithmType& type) noexcept { + switch (asn1Type) { + case ACSDK_CIP_ALG_AES_256_GCM: + type = AlgorithmType::AES_256_GCM; + break; + default: + ACSDK_ERROR(LX("convertAlgTypeFromAsn1Failed").d("asn1Type", asn1Type)); + return false; + } + return true; +} + +bool Asn1Helper::convertDigTypeToAsn1(DigestType type, int64_t& asn1Type) noexcept { + switch (type) { + case DigestType::SHA_256: + asn1Type = ACSDK_DIG_ALG_SHA_256; + break; + default: + ACSDK_ERROR(LX("convertDigTypeToAsn1Failed").d("type", type)); + return false; + } + return true; +} + +bool Asn1Helper::convertDigTypeFromAsn1(int64_t asn1Type, DigestType& type) noexcept { + switch (asn1Type) { + case ACSDK_DIG_ALG_SHA_256: + type = DigestType::SHA_256; + break; + default: + ACSDK_ERROR(LX("convertDigTypeFromAsn1Failed").d("asn1Type", asn1Type)); + return false; + } + return true; +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/Asn1Types.cpp b/core/Properties/acsdkProperties/src/Asn1Types.cpp new file mode 100644 index 0000000000..0765a0bcb8 --- /dev/null +++ b/core/Properties/acsdkProperties/src/Asn1Types.cpp @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +ASN1_SEQUENCE(ACSDK_ENC_INFO) = { + ASN1_EXP_OPT(ACSDK_ENC_INFO, version, ASN1_INTEGER, 0), + ASN1_SIMPLE(ACSDK_ENC_INFO, mainKeyAlias, ASN1_UTF8STRING), + ASN1_SIMPLE(ACSDK_ENC_INFO, mainKeyChecksum, ASN1_OCTET_STRING), + ASN1_EXP_OPT(ACSDK_ENC_INFO, dataKeyAlgorithm, ASN1_INTEGER, 1), + ASN1_SIMPLE(ACSDK_ENC_INFO, dataKeyIV, ASN1_OCTET_STRING), + ASN1_SIMPLE(ACSDK_ENC_INFO, dataKeyCiphertext, ASN1_OCTET_STRING), + ASN1_SIMPLE(ACSDK_ENC_INFO, dataKeyTag, ASN1_OCTET_STRING), + ASN1_EXP_OPT(ACSDK_ENC_INFO, dataAlgorithm, ASN1_INTEGER, 2), +} ASN1_SEQUENCE_END(ACSDK_ENC_INFO); +IMPLEMENT_ASN1_FUNCTIONS(ACSDK_ENC_INFO); + +ASN1_SEQUENCE(ACSDK_ENC_PROP) = { + ASN1_SIMPLE(ACSDK_ENC_PROP, encryptionInfo, ACSDK_ENC_INFO), + ASN1_EXP_OPT(ACSDK_ENC_PROP, digestAlgorithm, ASN1_INTEGER, 0), + ASN1_SIMPLE(ACSDK_ENC_PROP, digest, ASN1_OCTET_STRING), +} ASN1_SEQUENCE_END(ACSDK_ENC_PROP); +IMPLEMENT_ASN1_FUNCTIONS(ACSDK_ENC_PROP); + +ASN1_SEQUENCE(ACSDK_DATA_INFO) = { + ASN1_EXP_OPT(ACSDK_DATA_INFO, version, ASN1_INTEGER, 0), + ASN1_SIMPLE(ACSDK_DATA_INFO, dataIV, ASN1_OCTET_STRING), + ASN1_SIMPLE(ACSDK_DATA_INFO, dataCiphertext, ASN1_OCTET_STRING), + ASN1_SIMPLE(ACSDK_DATA_INFO, dataTag, ASN1_OCTET_STRING), +} ASN1_SEQUENCE_END(ACSDK_DATA_INFO); +IMPLEMENT_ASN1_FUNCTIONS(ACSDK_DATA_INFO); + +ASN1_SEQUENCE(ACSDK_DATA_PROP) = { + ASN1_SIMPLE(ACSDK_DATA_PROP, dataInfo, ACSDK_DATA_INFO), + ASN1_EXP_OPT(ACSDK_DATA_PROP, digestAlgorithm, ASN1_INTEGER, 0), + ASN1_SIMPLE(ACSDK_DATA_PROP, digest, ASN1_OCTET_STRING), +} ASN1_SEQUENCE_END(ACSDK_DATA_PROP); +IMPLEMENT_ASN1_FUNCTIONS(ACSDK_DATA_PROP); + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/CMakeLists.txt b/core/Properties/acsdkProperties/src/CMakeLists.txt new file mode 100644 index 0000000000..0892744db4 --- /dev/null +++ b/core/Properties/acsdkProperties/src/CMakeLists.txt @@ -0,0 +1,44 @@ + +set(acsdkProperties_SOURCES + Asn1Helper.cpp + Asn1Types.cpp + EncryptionKeyPropertyCodec.cpp + EncryptionKeyPropertyCodecState.cpp + DataPropertyCodec.cpp + DataPropertyCodecState.cpp + EncryptedProperties.cpp + EncryptedPropertiesFactories.cpp + EncryptedPropertiesFactory.cpp + ErrorCallbackSetter.cpp + Logging.cpp + MiscStorageAdapter.cpp + MiscStorageProperties.cpp + MiscStoragePropertiesFactory.cpp + RetryExecutor.cpp + SimpleMiscStorageUriMapper.cpp + ) +set(acsdkProperties_PUBLIC_LIBRARIES + acsdkCryptoInterfaces + acsdkPropertiesInterfaces + ) +set(acsdkProperties_PRIVATE_LIBRARIES + AVSCommon + acsdkCodecUtils + ${CRYPTO_LDFLAGS} + ) +set(acsdkProperties_PUBLIC_DEFINES) +set(acsdkProperties_PRIVATE_INCLUDES + "${acsdkProperties_SOURCE_DIR}/privateInclude" + ${CRYPTO_INCLUDE_DIRS} + ) + +add_library(acsdkProperties ${acsdkProperties_SOURCES}) +target_compile_definitions(acsdkProperties PRIVATE ACSDK_LOG_MODULE=acsdkProperties) +target_compile_definitions(acsdkProperties PUBLIC ${acsdkProperties_PUBLIC_DEFINES}) +target_include_directories(acsdkProperties PUBLIC "${acsdkProperties_SOURCE_DIR}/include") +target_include_directories(acsdkProperties PRIVATE "${acsdkProperties_PRIVATE_INCLUDES}") +target_link_libraries(acsdkProperties PUBLIC ${acsdkProperties_PUBLIC_LIBRARIES}) +target_link_libraries(acsdkProperties PRIVATE ${acsdkProperties_PRIVATE_LIBRARIES}) + +# install target +asdk_install() diff --git a/core/Properties/acsdkProperties/src/DataPropertyCodec.cpp b/core/Properties/acsdkProperties/src/DataPropertyCodec.cpp new file mode 100644 index 0000000000..27d69f4fcb --- /dev/null +++ b/core/Properties/acsdkProperties/src/DataPropertyCodec.cpp @@ -0,0 +1,199 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkProperties { + +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"DataPropertyCodec"}; + +/// @private +static constexpr DigestType DEFAULT_DIGEST_TYPE = DigestType::SHA_256; + +bool DataPropertyCodec::encode( + const std::shared_ptr& cryptoFactory, + const IV& dataIV, + const DataBlock& dataCiphertext, + const Tag& dataTag, + DataBlock& derEncoded) noexcept { + if (!cryptoFactory) { + ACSDK_ERROR(LX("encodeFailed").m("cryptoFactoryNull")); + return false; + } + + const DigestType digestType = DEFAULT_DIGEST_TYPE; + auto digest = cryptoFactory->createDigest(digestType); + DataBlock digestData; + DataBlock encodedInfo; + DataPropertyCodecState codecState; + + if (!digest) { + ACSDK_ERROR(LX("encodeFailed").m("digestCreateFailed")); + return false; + } + + if (!codecState.prepareForEncode()) { + ACSDK_ERROR(LX("encodeFailed").m("encodePrepareFailed")); + return false; + } + + if (!codecState.setVersion(ACSDK_DATA_KEY_VER_V1)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("propertyName", "version")); + return false; + } + + if (!codecState.setDataIV(dataIV)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("propertyName", "dataIV")); + return false; + } + + if (!codecState.setDataCiphertext(dataCiphertext)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("propertyName", "dataCiphertext")); + return false; + } + + if (!codecState.setDataTag(dataTag)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("propertyName", "dataTag")); + return false; + } + + if (!codecState.encodeEncInfo(encodedInfo)) { + ACSDK_ERROR(LX("encodeFailed").m("encodeInfoFailed")); + return false; + } + + if (!digest->process(encodedInfo)) { + ACSDK_ERROR(LX("encodeFailed").m("digestProcessFailed")); + return false; + } + + if (!digest->finalize(digestData)) { + ACSDK_ERROR(LX("encodeFailed").m("digestFinalizeFailed")); + return false; + } + + if (!codecState.setDigestType(digestType)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("propertyName", "digestType")); + return false; + } + + if (!codecState.setDigest(digestData)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("propertyName", "digest")); + return false; + } + + if (!codecState.encode(derEncoded)) { + ACSDK_ERROR(LX("encodeFailed").m("finalEncodeFailed")); + return false; + } + + return true; +} + +bool DataPropertyCodec::decode( + const std::shared_ptr& cryptoFactory, + const std::vector& derEncoded, + IV& dataIV, + DataBlock& dataCiphertext, + Tag& dataTag, + DataBlock& digestDecoded, + DataBlock& digestActual) noexcept { + if (!cryptoFactory) { + ACSDK_ERROR(LX("decodeFailed").m("cryptoFactoryNull")); + return false; + } + + DataPropertyCodecState codecState; + + int64_t version = 0; + DigestType digestType = DEFAULT_DIGEST_TYPE; + std::unique_ptr digest; + DataBlock encoded; + + if (!codecState.decode(derEncoded)) { + ACSDK_ERROR(LX("decodeFailed").m("initialDecodeFailed")); + return false; + } + + if (!codecState.getVersion(version)) { + ACSDK_ERROR(LX("encodeFailed").m("propertyGetFailed").d("propertyName", "version")); + return false; + } + + if (ACSDK_DATA_VER_V1 != version) { + ACSDK_ERROR(LX("decodeFailed").m("versionError")); + return false; + } + + if (!codecState.getDataIV(dataIV)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("propertyName", "dataIV")); + return false; + } + + if (!codecState.getDataCiphertext(dataCiphertext)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("propertyName", "dataCiphertext")); + return false; + } + + if (!codecState.getDataTag(dataTag)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("propertyName", "dataTag")); + return false; + } + + if (!codecState.getDigest(digestDecoded)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("propertyName", "digest")); + return false; + } + + if (!codecState.getDigestType(digestType)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("propertyName", "digestType")); + return false; + } + + digest = cryptoFactory->createDigest(digestType); + if (!digest) { + ACSDK_ERROR(LX("decodeFailed").m("createDigestFailed")); + return false; + } + + if (!codecState.encodeEncInfo(encoded)) { + ACSDK_ERROR(LX("decodeFailed").m("encodeInfoFailed")); + return false; + } + + if (!digest->process(encoded)) { + ACSDK_ERROR(LX("decodeFailed").m("digestProcessFailed")); + return false; + } + + if (!digest->finalize(digestActual)) { + ACSDK_ERROR(LX("decodeFailed").m("digestFinalizeFailed")); + return false; + } + + return true; +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/DataPropertyCodecState.cpp b/core/Properties/acsdkProperties/src/DataPropertyCodecState.cpp new file mode 100644 index 0000000000..8224def5c6 --- /dev/null +++ b/core/Properties/acsdkProperties/src/DataPropertyCodecState.cpp @@ -0,0 +1,161 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkProperties { + +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"DataPropertyCodecState"}; + +DataPropertyCodecState::DataPropertyCodecState() noexcept { + m_asn1Data = nullptr; +} + +DataPropertyCodecState::~DataPropertyCodecState() noexcept { + ACSDK_DATA_PROP_free(m_asn1Data); + m_asn1Data = nullptr; +} + +bool DataPropertyCodecState::prepareForEncode() noexcept { + if (!m_asn1Data && !(m_asn1Data = ACSDK_DATA_PROP_new())) { + ACSDK_ERROR(LX("prepareForEncodeFailed").m("newDataPropFailed")); + return false; + } + if (!m_asn1Data->dataInfo && !(m_asn1Data->dataInfo = ACSDK_DATA_INFO_new())) { + ACSDK_ERROR(LX("prepareForEncodeFailed").m("newDataInfoFailed")); + return false; + } + return true; +} + +bool DataPropertyCodecState::setVersion(int64_t value) noexcept { + return m_asn1Data->dataInfo && Asn1Helper::setOptInt(m_asn1Data->dataInfo->version, value, ACSDK_ENC_INFO::DEF_VER); +} + +bool DataPropertyCodecState::getVersion(int64_t& value) noexcept { + return m_asn1Data->dataInfo && Asn1Helper::getOptInt(m_asn1Data->dataInfo->version, value, ACSDK_ENC_INFO::DEF_VER); +} + +bool DataPropertyCodecState::setDataIV(const IV& value) noexcept { + return Asn1Helper::setData(m_asn1Data->dataInfo->dataIV, value); +} + +bool DataPropertyCodecState::getDataIV(IV& value) noexcept { + return Asn1Helper::getData(m_asn1Data->dataInfo->dataIV, value); +} + +bool DataPropertyCodecState::setDataCiphertext(const DataBlock& value) noexcept { + return Asn1Helper::setData(m_asn1Data->dataInfo->dataCiphertext, value); +} + +bool DataPropertyCodecState::getDataCiphertext(DataBlock& value) noexcept { + return Asn1Helper::getData(m_asn1Data->dataInfo->dataCiphertext, value); +} + +bool DataPropertyCodecState::setDataTag(const Tag& dataTag) noexcept { + return Asn1Helper::setData(m_asn1Data->dataInfo->dataTag, dataTag); +} + +bool DataPropertyCodecState::getDataTag(Tag& dataTag) noexcept { + return Asn1Helper::getData(m_asn1Data->dataInfo->dataTag, dataTag); +} + +bool DataPropertyCodecState::setDigestType(DigestType type) noexcept { + int64_t asn1Type; + return Asn1Helper::convertDigTypeToAsn1(type, asn1Type) && + Asn1Helper::setOptInt(m_asn1Data->digestAlgorithm, asn1Type, ACSDK_ENC_PROP::DEF_DIG_ALG); +} + +bool DataPropertyCodecState::getDigestType(DigestType& type) noexcept { + int64_t asn1Type; + return Asn1Helper::getOptInt(m_asn1Data->digestAlgorithm, asn1Type, ACSDK_ENC_PROP::DEF_DIG_ALG) && + Asn1Helper::convertDigTypeFromAsn1(asn1Type, type); +} + +bool DataPropertyCodecState::setDigest(const DataBlock& digest) noexcept { + return Asn1Helper::setData(m_asn1Data->digest, digest); +} + +bool DataPropertyCodecState::getDigest(DataBlock& digest) noexcept { + return Asn1Helper::getData(m_asn1Data->digest, digest); +} + +bool DataPropertyCodecState::encodeEncInfo(DataBlock& der) noexcept { + if (!m_asn1Data->dataInfo) { + ACSDK_ERROR(LX("encodeEncInfoFailed").m("nullDataInfo")); + return false; + } + + int res = i2d_ACSDK_DATA_INFO(m_asn1Data->dataInfo, nullptr); + if (res <= 0) { + ACSDK_ERROR(LX("encodeEncInfoFailed").m("derEncodingEstimateFailed")); + return false; + } + + der.resize(res); + unsigned char* data = der.data(); + res = i2d_ACSDK_DATA_INFO(m_asn1Data->dataInfo, &data); + if (res <= 0) { + ACSDK_ERROR(LX("encodeEncInfoFailed").m("derEncodingFailed")); + return false; + } + return true; +} + +bool DataPropertyCodecState::encode(DataBlock& der) noexcept { + int res = i2d_ACSDK_DATA_PROP(m_asn1Data, nullptr); + if (res <= 0) { + ACSDK_ERROR(LX("encodeEncInfoFailed").m("sizeEstimateFailed")); + return false; + } + + der.resize(res); + unsigned char* data = der.data(); + res = i2d_ACSDK_DATA_PROP(m_asn1Data, &data); + + if (res <= 0) { + ACSDK_ERROR(LX("encodeEncInfoFailed").m("derEncodingFailed")); + return false; + } + return true; +} + +bool DataPropertyCodecState::decode(const DataBlock& der) noexcept { + if (m_asn1Data) { + ACSDK_DEBUG9(LX("decodeReleasingData")); + ACSDK_DATA_PROP_free(m_asn1Data); + m_asn1Data = nullptr; + } + const unsigned char* data = der.data(); + m_asn1Data = d2i_ACSDK_DATA_PROP(nullptr, &data, der.size()); + if (!m_asn1Data || !m_asn1Data->dataInfo) { + ACSDK_ERROR(LX("decodeFailed").m("derDecodeFailed")); + return false; + } + + return true; +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/EncryptedProperties.cpp b/core/Properties/acsdkProperties/src/EncryptedProperties.cpp new file mode 100644 index 0000000000..f410d3a92e --- /dev/null +++ b/core/Properties/acsdkProperties/src/EncryptedProperties.cpp @@ -0,0 +1,821 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"EncryptedProperties"}; +/// @private +static const std::string KEY_PROPERTY_NAME = "$acsdkEncryption$"; +/// @private +static const AlgorithmType DEFAULT_ALGORITHM_FOR_PROPERTIES = AlgorithmType::AES_256_GCM; +/// @private +static const AlgorithmType DEFAULT_ALGORITHM_FOR_KEYS = AlgorithmType::AES_256_GCM; + +std::shared_ptr EncryptedProperties::create( + const std::string& configUri, + const std::shared_ptr& innerProperties, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept { + auto res = std::shared_ptr( + new EncryptedProperties(configUri, innerProperties, cryptoFactory, keyStore)); + + if (res->init()) { + ACSDK_DEBUG0(LX_CFG("createSuccess", configUri)); + } else { + ACSDK_ERROR(LX_CFG("createFailed", configUri)); + res.reset(); + } + return res; +} + +EncryptedProperties::EncryptedProperties( + const std::string& configUri, + const std::shared_ptr& innerProperties, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept : + m_configUri(configUri), + m_innerProperties(innerProperties), + m_cryptoFactory(cryptoFactory), + m_keyStore(keyStore) { +} + +bool EncryptedProperties::getString(const std::string& key, std::string& value) noexcept { + Bytes byteValue; + if (!getAndDecryptInternal(key, byteValue)) { + ACSDK_ERROR(LX_CFG_KEY("getStringFailed", m_configUri, key)); + return false; + } + + value.assign( + reinterpret_cast(byteValue.data()), + reinterpret_cast(byteValue.data()) + byteValue.size()); + + ACSDK_DEBUG9(LX_CFG_KEY("getStringSuccess", m_configUri, key)); + return true; +} + +bool EncryptedProperties::putString(const std::string& key, const std::string& value) noexcept { + Bytes byteValue{reinterpret_cast(value.c_str()), + reinterpret_cast(value.c_str()) + value.size()}; + + if (encryptAndPutInternal(key, byteValue)) { + ACSDK_DEBUG0(LX_CFG_KEY("putStringSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("petStringFailed", m_configUri, key)); + return false; + } +} + +bool EncryptedProperties::getBytes(const std::string& key, Bytes& value) noexcept { + if (KEY_PROPERTY_NAME == key) { + ACSDK_ERROR(LX_CFG("getBytesFailed", m_configUri).m("propertyKeyForbidden")); + return false; + } + + if (getAndDecryptInternal(key, value)) { + ACSDK_DEBUG0(LX_CFG_KEY("getBytesSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("getBytesFailed", m_configUri, key)); + return true; + } +} + +bool EncryptedProperties::getAndDecryptInternal(const std::string& key, Bytes& plaintext) noexcept { + // create executor to invoke error callback and limit number of retries + RetryExecutor executor{OperationType::Get, m_configUri}; + + auto result = executor.execute( + "decodeAndDecryptPropertyValue", + [this, &executor, &key, &plaintext]() -> StatusCodeWithRetry { + Bytes encodedCiphertext; + if (!loadValueWithRetries(executor, key, encodedCiphertext)) { + ACSDK_ERROR(LX_CFG_KEY("getAndDecryptInternalFailed", m_configUri, key)); + return RetryExecutor::NON_RETRYABLE_INNER_PROPERTIES_ERROR; + } + + ACSDK_DEBUG0(LX_CFG_KEY("getAndDecryptInternal", m_configUri, key).d("loaded", encodedCiphertext)); + + plaintext.clear(); + if (decodeAndDecryptPropertyValue(key, encodedCiphertext, plaintext)) { + return RetryExecutor::SUCCESS; + } else { + return RetryExecutor::RETRYABLE_CRYPTO_ERROR; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success != result) { + ACSDK_DEBUG0(LX_CFG_KEY("getAndDecryptInternalFailed", m_configUri, key)); + return false; + } else { + ACSDK_DEBUG0(LX_CFG_KEY("getAndDecryptInternalSuccess", m_configUri, key)); + return true; + } +} + +bool EncryptedProperties::putBytes(const std::string& key, const Bytes& value) noexcept { + if (key == KEY_PROPERTY_NAME) { + ACSDK_ERROR(LX_CFG("putBytesFailed", m_configUri).m("propertyKeyForbidden")); + return false; + } + if (encryptAndPutInternal(key, value)) { + ACSDK_DEBUG0(LX_CFG_KEY("putBytesSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("putBytesFailed", m_configUri, key)); + return true; + } +} + +bool EncryptedProperties::encryptAndPutInternal(const std::string& key, const Bytes& plaintext) noexcept { + // create executor to invoke error callback and limit number of retries + RetryExecutor executor{OperationType::Put, m_configUri}; + + Bytes encodedCiphertext; + auto res = executor.execute( + "encryptAndPutInternal", + [this, &key, &plaintext, &encodedCiphertext]() -> StatusCodeWithRetry { + if (!encryptAndEncodePropertyValue(key, plaintext, encodedCiphertext)) { + // If local cryptography API fails, it is a non-retryable error. + // We still notify error callback. + ACSDK_DEBUG0(LX_CFG_KEY("encryptPropertyFailed", m_configUri, key)); + return RetryExecutor::RETRYABLE_CRYPTO_ERROR; + } else { + ACSDK_DEBUG0(LX_CFG_KEY("encryptPropertySuccess", m_configUri, key)); + return RetryExecutor::SUCCESS; + } + }, + Action::FAIL); + switch (res) { + case RetryableOperationResult::Cleanup: + if (deleteValueWithRetries(executor, key)) { + ACSDK_DEBUG0(LX_CFG_KEY("encryptAndPutInternalCleanupSuccess", m_configUri, key)); + return true; + } else { + ACSDK_DEBUG0(LX_CFG_KEY("encryptAndPutInternalCleanupSuccessFailure", m_configUri, key)); + return false; + } + case RetryableOperationResult::Success: + break; + case RetryableOperationResult::Failure: // fall through + default: + return false; + } + + if (storeValueWithRetries(executor, key, encodedCiphertext, true)) { + ACSDK_DEBUG0(LX_CFG_KEY("encryptAndPutInternalSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("encryptAndPutInternalFailure", m_configUri, key)); + return false; + } +} + +bool EncryptedProperties::storeValueWithRetries( + RetryExecutor& executor, + const std::string& key, + const Bytes& value, + bool canDrop) noexcept { + auto result = executor.execute( + "storeKeyValue", + [this, &key, &value]() -> StatusCodeWithRetry { + // Compute property with encrypted key and algorithm types. + if (m_innerProperties->putBytes(key, value)) { + ACSDK_DEBUG9(LX_CFG_KEY("putBytesSuccess", m_configUri, key)); + return RetryExecutor::SUCCESS; + } else { + ACSDK_DEBUG9(LX_CFG_KEY("putBytesSRetryableFailure", m_configUri, key)); + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + } + }, + Action::RETRY); + + if (RetryableOperationResult::Success == result) { + ACSDK_DEBUG0(LX_CFG_KEY("storeKeyValueSuccess", m_configUri, key)); + return true; + } else if (canDrop && RetryableOperationResult::Cleanup == result) { + if (deleteValueWithRetries(executor, key)) { + ACSDK_DEBUG0(LX_CFG_KEY("storeKeyDropValueSuccess", m_configUri, key)); + return true; + } else { + ACSDK_DEBUG0(LX_CFG_KEY("storeKeyDropValueError", m_configUri, key)); + return false; + } + } else { + ACSDK_ERROR(LX_CFG_KEY("storeKeyValueError", m_configUri, key)); + return false; + } +} + +bool EncryptedProperties::loadValueWithRetries(RetryExecutor& executor, const std::string& key, Bytes& data) noexcept { + return executeKeyOperationWithRetries( + executor, "loadValue", key, [this, &key, &data]() -> bool { return m_innerProperties->getBytes(key, data); }); +} + +bool EncryptedProperties::deleteValueWithRetries(RetryExecutor& executor, const std::string& key) noexcept { + return executeKeyOperationWithRetries( + executor, "removeKey", key, [this, &key]() -> bool { return m_innerProperties->remove(key); }); +} + +bool EncryptedProperties::executeKeyOperationWithRetries( + RetryExecutor& executor, + const std::string& operationName, + const std::string& key, + const std::function& operation) noexcept { + auto res = executor.execute( + operationName, + [this, &operationName, &key, &operation]() -> StatusCodeWithRetry { + // Suppress unused variable errors when the code is built with logging disabled. + ACSDK_UNUSED_VARIABLE(this); + ACSDK_UNUSED_VARIABLE(operationName); + ACSDK_UNUSED_VARIABLE(key); + if (!operation()) { + ACSDK_DEBUG9( + LX_CFG_KEY("keyOperationRetryableFailure", m_configUri, key).d("operation", operationName)); + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + } else { + ACSDK_DEBUG9(LX_CFG_KEY("keyOperationSuccess", m_configUri, key).d("operation", operationName)); + return RetryExecutor::SUCCESS; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success == res) { + ACSDK_DEBUG0(LX_CFG_KEY("keyOperationSuccess", m_configUri, key).d("operation", operationName)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("keyOperationFailure", m_configUri, key).d("operation", operationName)); + return false; + } +} + +bool EncryptedProperties::clearAllValuesWithRetries(RetryExecutor& executor) noexcept { + return executeKeyOperationWithRetries( + executor, "clear", "", [this]() -> bool { return m_innerProperties->clear(); }); +} + +bool EncryptedProperties::clear() noexcept { + RetryExecutor executor{OperationType::Put, m_configUri}; + if (doClear(executor)) { + ACSDK_DEBUG0(LX_CFG("clearSuccess", m_configUri)); + return true; + } else { + ACSDK_ERROR(LX_CFG("clearFailed", m_configUri)); + return false; + } +} + +bool EncryptedProperties::doClear(RetryExecutor& executor) noexcept { + auto result = clearAllValuesWithRetries(executor); + if (!result) { + ACSDK_ERROR(LX_CFG("doClearFailed", m_configUri)); + return false; + } + + auto statusCode = generateAndStoreDataKeyWithRetries(executor); + + if (StatusCode::SUCCESS != statusCode) { + ACSDK_ERROR(LX_CFG("doClearStoreKeyFailed", m_configUri)); + return false; + } + + ACSDK_DEBUG0(LX("doClearSuccess")); + return true; +} + +bool EncryptedProperties::remove(const std::string& key) noexcept { + if (key == KEY_PROPERTY_NAME) { + ACSDK_ERROR(LX("removeFailed").m("propertyKeyForbidden")); + return false; + } + + // Remove calls are considered put. + RetryExecutor executor{OperationType::Put, m_configUri}; + if (deleteValueWithRetries(executor, key)) { + ACSDK_DEBUG0(LX_CFG_KEY("removeSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("removeFailed", m_configUri, key)); + return false; + } +} + +bool EncryptedProperties::getKeys(std::unordered_set& keys) noexcept { + RetryExecutor executor{OperationType::Get, m_configUri}; + if (loadKeysWithRetries(executor, keys)) { + ACSDK_DEBUG0(LX_CFG("getKeysSuccess", m_configUri)); + keys.erase(KEY_PROPERTY_NAME); + return true; + } else { + ACSDK_ERROR(LX_CFG("getKeysFailed", m_configUri)); + return false; + } +} + +bool EncryptedProperties::loadKeysWithRetries(RetryExecutor& executor, std::unordered_set& keys) noexcept { + auto result = executor.execute( + "getKeys", + [this, &keys]() -> StatusCodeWithRetry { + if (m_innerProperties->getKeys(keys)) { + ACSDK_DEBUG9(LX_CFG("lgetKeysSuccess", m_configUri)); + return RetryExecutor::SUCCESS; + } else { + ACSDK_DEBUG9(LX_CFG("getKeysRetryableFailure", m_configUri)); + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success == result) { + ACSDK_DEBUG0(LX_CFG("loadKeysSuccess", m_configUri)); + return true; + } else { + ACSDK_ERROR(LX_CFG("loadKeysFailure", m_configUri)); + return false; + } +} + +bool EncryptedProperties::init() noexcept { + if (!m_innerProperties) { + ACSDK_ERROR(LX_CFG("initFailed", m_configUri).m("innerPropertiesNull")); + return false; + } + if (!m_cryptoFactory) { + ACSDK_ERROR(LX_CFG("initFailed", m_configUri).m("cryptoFactoryNull")); + return false; + } + if (!m_keyStore) { + ACSDK_ERROR(LX_CFG("initFailed", m_configUri).m("keyStoreNull")); + return false; + } + // create executor to invoke error callback and limit number of retries + RetryExecutor executor{OperationType::Open, m_configUri}; + // Check if container is encrypted. + // This operation checks if the encryption property is present. The operation is retryable. + + // Collection of existing keys + std::unordered_set keys; + if (!loadKeysWithRetries(executor, keys)) { + ACSDK_ERROR(LX_CFG("initFailed", m_configUri).m("getKeysFailed")); + return false; + } + + const bool encryptedStorage = keys.find(KEY_PROPERTY_NAME) != keys.end(); + + StatusCode openStatus; + if (encryptedStorage) { + // Key is present, so the storage is encrypted. + openStatus = loadAndDecryptDataKey(executor); + } else { + // key is not present - try to upgrade the storage + openStatus = upgradeEncryption(executor, keys); + } + if (StatusCode::SUCCESS == openStatus) { + ACSDK_DEBUG0(LX_CFG("initSuccess", m_configUri)); + return true; + } else { + // If we manage to cleanup - it is successful. If we fail to cleanup, config is unusable. + if (doClear(executor)) { + ACSDK_DEBUG0(LX_CFG("initSuccessDataCleanup", m_configUri)); + return true; + } else { + ACSDK_ERROR(LX_CFG("initFailed", m_configUri)); + return false; + } + } +} + +StatusCode EncryptedProperties::loadAndDecryptDataKey(RetryExecutor& executor) noexcept { + Bytes encryptionProperty; + if (!loadValueWithRetries(executor, KEY_PROPERTY_NAME, encryptionProperty)) { + ACSDK_DEBUG9(LX_CFG("loadDataKeyFailed", m_configUri)); + return StatusCode::INNER_PROPERTIES_ERROR; + } + + // We need to decode and decrypt data key. + // If decoding fails, or there is a digest error we can try to reload property. + auto decryptStatus = decodeAndDecryptDataKey(encryptionProperty); + if (StatusCode::SUCCESS == decryptStatus) { + // Data key is available. + return StatusCode::SUCCESS; + } else { + ACSDK_DEBUG9(LX_CFG("decryptDataKeyFailed", m_configUri)); + return decryptStatus; + } +} + +bool EncryptedProperties::generateDataKeyWithRetries(RetryExecutor& executor) noexcept { + m_dataAlgorithmType = DEFAULT_ALGORITHM_FOR_PROPERTIES; + + auto result = executor.execute( + "generateDataKeyWithRetries", + [this]() -> StatusCodeWithRetry { + m_dataKey.clear(); + auto keyFactory = m_cryptoFactory->getKeyFactory(); + if (!keyFactory) { + ACSDK_WARN(LX("generateDataKeyWithRetriesFailed").m("keyFactoryNull")); + return RetryExecutor::NON_RETRYABLE_CRYPTO_ERROR; + } + if (!keyFactory->generateKey(m_dataAlgorithmType, m_dataKey)) { + ACSDK_WARN( + LX("generateDataKeyWithRetriesFailed").d("algorithmType", static_cast(m_dataAlgorithmType))); + return RetryExecutor::NON_RETRYABLE_CRYPTO_ERROR; + } + return {StatusCode::SUCCESS, false}; + }, + Action::RETRY); + + if (RetryableOperationResult::Success != result) { + ACSDK_ERROR(LX("generateDataKeyFailed").d("algorithmType", static_cast(m_dataAlgorithmType))); + return false; + } + + return true; +} + +bool EncryptedProperties::encryptAndEncodeDataKeyWithRetries(RetryExecutor& executor, Bytes& encoded) noexcept { + std::string mainKeyAlias; + KeyChecksum mainKeyChecksum; + AlgorithmType algorithmType; + IV iv; + DataBlock ciphertext; + Tag tag; + + // Attempt to encrypt data key. By default, we try once, but error callback may override this action. + auto result = executor.execute( + "encryptAndEncodeDataKeyWithRetries", + [this, &mainKeyAlias, &mainKeyChecksum, &algorithmType, &iv, &ciphertext, &tag]() -> StatusCodeWithRetry { + mainKeyChecksum.clear(); + iv.clear(); + ciphertext.clear(); + tag.clear(); + if (!encryptDataKey(mainKeyAlias, algorithmType, mainKeyChecksum, iv, ciphertext, tag)) { + ACSDK_ERROR(LX("encryptAndEncodeDataKeyFailed").m("failedToEncryptDataKey")); + return RetryExecutor::RETRYABLE_HSM_ERROR; + } else { + return RetryExecutor::SUCCESS; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success != result) { + return false; + } + + // Attempt to encode data key. By default, we try once, but error callback may override this action. + result = executor.execute( + "encryptAndEncodeDataKeyWithRetries", + [this, &mainKeyAlias, &mainKeyChecksum, &algorithmType, &iv, &ciphertext, &encoded, &tag]() + -> StatusCodeWithRetry { + encoded.clear(); + if (!EncryptionKeyPropertyCodec::encode( + m_cryptoFactory, + mainKeyAlias, + mainKeyChecksum, + algorithmType, + iv, + ciphertext, + tag, + m_dataAlgorithmType, + encoded)) { + ACSDK_ERROR(LX("encryptAndEncodeDataKeyFailed").m("failedToDerEncode")); + return RetryExecutor::RETRYABLE_HSM_ERROR; + } else { + return RetryExecutor::SUCCESS; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success != result) { + return false; + } + + return true; +} + +StatusCode EncryptedProperties::decodeAndDecryptDataKey(const Bytes& encoded) noexcept { + std::string mainKeyAlias; + KeyChecksum mainKeyChecksum; + AlgorithmType dataKeyAlg; + IV dataKeyIV; + DataBlock dataKeyCiphertext; + DataBlock digest; + DataBlock actualDigest; + Tag dataKeyTag; + + if (!EncryptionKeyPropertyCodec::decode( + m_cryptoFactory, + encoded, + mainKeyAlias, + mainKeyChecksum, + dataKeyAlg, + dataKeyIV, + dataKeyCiphertext, + dataKeyTag, + m_dataAlgorithmType, + digest, + actualDigest)) { + ACSDK_ERROR(LX("decodeAndDecryptDataKeyFailed").m("failedToDecodeDER")); + return StatusCode::UNKNOWN_ERROR; + } + + if (digest != actualDigest) { + ACSDK_ERROR(LX("decodeAndDecryptDataKey").m("failedToVerifyDataKeyDigest")); + return StatusCode::DIGEST_ERROR; + } + + if (decryptDataKey(mainKeyAlias, dataKeyAlg, mainKeyChecksum, dataKeyIV, dataKeyCiphertext, dataKeyTag)) { + return StatusCode::SUCCESS; + } else { + ACSDK_ERROR(LX("decodeAndDecryptDataKey").m("failedToDecryptDataKey")); + m_innerProperties->clear(); + return StatusCode::HSM_ERROR; + } +} + +bool EncryptedProperties::encryptDataKey( + std::string& mainKeyAlias, + AlgorithmType& algorithmType, + KeyChecksum& mainKeyChecksum, + IV& dataKeyIV, + Bytes& dataKeyCiphertext, + Tag& dataKeyTag) noexcept { + if (!m_keyStore->getDefaultKeyAlias(mainKeyAlias)) { + ACSDK_ERROR(LX("encryptDataKeyFailed").m("defaultKeyAliasError")); + return false; + } + + algorithmType = DEFAULT_ALGORITHM_FOR_KEYS; + + auto keyFactory = m_cryptoFactory->getKeyFactory(); + if (!keyFactory || !keyFactory->generateIV(algorithmType, dataKeyIV)) { + ACSDK_ERROR(LX("encryptDataKeyFailed").m("dataKeyIVGenerateFailed")); + return false; + } + + CryptoCodecInterface::DataBlock aadBinary{ + reinterpret_cast(m_configUri.data()), + reinterpret_cast(m_configUri.data()) + m_configUri.size()}; + + mainKeyChecksum.clear(); + dataKeyCiphertext.clear(); + dataKeyTag.clear(); + if (!m_keyStore->encryptAE( + mainKeyAlias, + algorithmType, + dataKeyIV, + aadBinary, + m_dataKey, + mainKeyChecksum, + dataKeyCiphertext, + dataKeyTag)) { + ACSDK_ERROR(LX("encryptDataKeyFailed").m("mainKeyEncryptionFailed")); + return false; + } + + return true; +} + +bool EncryptedProperties::decryptDataKey( + const std::string& mainKeyAlias, + AlgorithmType dataKeyAlgorithm, + const KeyChecksum& keyChecksum, + const std::vector& dataKeyIV, + const std::vector& keyCiphertext, + const Tag& dataKeyTag) noexcept { + CryptoCodecInterface::DataBlock aadBinary{ + reinterpret_cast(m_configUri.data()), + reinterpret_cast(m_configUri.data()) + m_configUri.size()}; + m_dataKey.clear(); + if (!m_keyStore->decryptAD( + mainKeyAlias, dataKeyAlgorithm, keyChecksum, dataKeyIV, aadBinary, keyCiphertext, dataKeyTag, m_dataKey)) { + ACSDK_ERROR(LX_CFG("decryptDataKeyFailed", m_configUri)); + return false; + } + + return true; +} + +StatusCode EncryptedProperties::generateAndStoreDataKeyWithRetries(RetryExecutor& executor) noexcept { + if (!generateDataKeyWithRetries(executor)) { + ACSDK_ERROR(LX_CFG("generateAndStoreDataKeyWithRetriesFailed", m_configUri)); + return StatusCode::CRYPTO_ERROR; + } + + Bytes dataKeyPropertyValue; + if (!encryptAndEncodeDataKeyWithRetries(executor, dataKeyPropertyValue)) { + ACSDK_ERROR(LX_CFG("generateAndStoreDataKeyWithRetriesFailed", m_configUri)); + return StatusCode::HSM_ERROR; + } + + if (!storeValueWithRetries(executor, KEY_PROPERTY_NAME, dataKeyPropertyValue, true)) { + ACSDK_ERROR(LX_CFG("generateAndStoreDataKeyWithRetriesFailed", m_configUri)); + return StatusCode::INNER_PROPERTIES_ERROR; + } + + ACSDK_DEBUG0(LX_CFG("generateAndStoreDataKeyWithRetriesSuccess", m_configUri)); + return StatusCode::SUCCESS; +} + +StatusCode EncryptedProperties::upgradeEncryption( + RetryExecutor& executor, + const std::unordered_set& keys) noexcept { + std::unordered_map unencryptedValues; + // Load unencrypted properties + for (auto it = keys.cbegin(); it != keys.cend(); ++it) { + Bytes binary; + + // When loading unencrypted properties the API tries to load value as a string, and then as a binary. + // This sequence is for a better compatibility when upgrading unencrypted data into encrypted form, + // as binary data can be mapped into Base64 textual form, and it is possible to get false positives + // when requesting data as binary. + + const auto& key = *it; + + auto result = executor.execute( + "upgradeEncryptionLoadKey", + [this, &binary, &key, &unencryptedValues]() -> StatusCodeWithRetry { + std::string string; + if (m_innerProperties->getString(key, string)) { + ACSDK_DEBUG9(LX("upgradeEncryptionString").d("key", key)); + binary.assign( + reinterpret_cast(string.c_str()), + reinterpret_cast(string.c_str()) + string.size()); + unencryptedValues.insert({key, binary}); + return RetryExecutor::SUCCESS; + } + binary.clear(); + if (m_innerProperties->getBytes(key, binary)) { + ACSDK_DEBUG9(LX("upgradeEncryptionBinary").d("key", key)); + unencryptedValues.insert({key, binary}); + return {StatusCode::SUCCESS, false}; + } + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + }, + Action::RETRY); + + if (RetryableOperationResult::Success != result) { + ACSDK_WARN(LX("upgradeEncryptionEntryLost").d("key", key)); + } + } + + if (!clearAllValuesWithRetries(executor)) { + return StatusCode::INNER_PROPERTIES_ERROR; + } + + auto statusCode = generateAndStoreDataKeyWithRetries(executor); + + if (StatusCode::SUCCESS != statusCode) { + return statusCode; + } + + // Store encrypted properties + for (auto it = unencryptedValues.cbegin(); it != unencryptedValues.cend(); ++it) { + ACSDK_DEBUG9(LX_CFG_KEY("upgradeEncryptionStoreCiphertext", m_configUri, it->first)); + Bytes encoded; + encryptAndEncodePropertyValue(it->first, it->second, encoded); + storeValueWithRetries(executor, it->first, encoded, false); + } + + return StatusCode::SUCCESS; +} + +bool EncryptedProperties::encryptAndEncodePropertyValue( + const std::string& key, + const Bytes& plaintext, + Bytes& encodedCiphertext) noexcept { + // Crypto Encoder + const auto cipher = m_cryptoFactory->createEncoder(m_dataAlgorithmType); + if (!cipher) { + ACSDK_ERROR(LX("encryptAndEncodePropertyValueFailed").m("createEncoderFailed")); + return false; + } + + // Initialization vector. + IV iv; + // Encrypted value. + DataBlock ciphertext; + // Tag for ADAD algorithms or authentication code for others. + Tag tag; + + auto keyFactory = m_cryptoFactory->getKeyFactory(); + + if (!keyFactory || !keyFactory->generateIV(m_dataAlgorithmType, iv)) { + ACSDK_ERROR(LX("encryptAndEncodePropertyValueFailed").m("ivGenerateFailed")); + return false; + } + if (!cipher->init(m_dataKey, iv)) { + ACSDK_ERROR(LX("encryptAndEncodePropertyValueFailed").m("cipherInitFailed")); + return false; + } + + if (!cipher->processAAD(DataBlock{key.data(), key.data() + key.size()})) { + ACSDK_ERROR(LX("encryptAndEncodePropertyValueFailed").m("processAADError")); + return false; + } + + CryptoCodecInterface::DataBlock binaryPlaintext; + binaryPlaintext.assign(plaintext.data(), plaintext.data() + plaintext.size()); + + if (!cipher->process(binaryPlaintext, ciphertext)) { + ACSDK_ERROR(LX("encryptAndEncodePropertyValueFailed").m("cipherUpdateFailed")); + return false; + } + + if (!cipher->finalize(ciphertext)) { + ACSDK_ERROR(LX("encryptAndEncodePropertyValueFailed").m("cipherFinalizeFailed")); + return false; + } + + if (!cipher->getTag(tag)) { + ACSDK_ERROR(LX("encryptAndEncodePropertyValueFailed").m("getTagFailed")); + return false; + } + + if (!DataPropertyCodec::encode(m_cryptoFactory, iv, ciphertext, tag, encodedCiphertext)) { + ACSDK_ERROR(LX("encryptAndEncodePropertyValueFailed").m("derEncodeFailed")); + return false; + } + + return true; +} + +bool EncryptedProperties::decodeAndDecryptPropertyValue( + const std::string& key, + const Bytes& encodedCiphertext, + Bytes& plaintext) noexcept { + CryptoCodecInterface::IV iv; + CryptoCodecInterface::DataBlock ciphertext; + CryptoCodecInterface::Tag tag; + DigestInterface::DataBlock digest; + DigestInterface::DataBlock actualDigest; + + if (!DataPropertyCodec::decode(m_cryptoFactory, encodedCiphertext, iv, ciphertext, tag, digest, actualDigest)) { + ACSDK_ERROR(LX("decodeAndDecryptPropertyValue").m("propertyValueDerDecodingFailed")); + return false; + } + if (digest != actualDigest) { + ACSDK_ERROR(LX("decodeAndDecryptPropertyValue").m("propertyValueDigestCheckFailed")); + return false; + } + auto codec = m_cryptoFactory->createDecoder(m_dataAlgorithmType); + if (!codec) { + ACSDK_ERROR(LX("decodeAndDecryptPropertyValue").m("decoderCreateFailed")); + return false; + } + if (!codec->init(m_dataKey, iv)) { + ACSDK_ERROR(LX("decodeAndDecryptPropertyValue").m("decoderInitFailed")); + return false; + } + DataBlock aad{key.data(), key.data() + key.size()}; + if (!codec->processAAD(aad)) { + ACSDK_ERROR(LX("decodeAndDecryptPropertyValue").m("decoderProcessFailed")); + return false; + } + if (!codec->process(ciphertext, plaintext)) { + ACSDK_ERROR(LX("decodeAndDecryptPropertyValue").m("decoderProcessFailed")); + return false; + } + if (!codec->setTag(tag)) { + ACSDK_ERROR(LX("decodeAndDecryptPropertyValue").m("decoderSetTagFailed")); + return false; + } + if (!codec->finalize(plaintext)) { + ACSDK_ERROR(LX("decodeAndDecryptPropertyValue").m("decoderFinalizeFailed")); + return false; + } + + return true; +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/EncryptedPropertiesFactories.cpp b/core/Properties/acsdkProperties/src/EncryptedPropertiesFactories.cpp new file mode 100644 index 0000000000..c38b76371d --- /dev/null +++ b/core/Properties/acsdkProperties/src/EncryptedPropertiesFactories.cpp @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"EncryptedPropertiesFactoryApi"}; + +std::shared_ptr createEncryptedPropertiesFactory( + const std::shared_ptr& innerPropertiesFactory, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept { + auto res = EncryptedPropertiesFactory::create(innerPropertiesFactory, cryptoFactory, keyStore); + if (!res) { + ACSDK_ERROR(LX("createEncryptedPropertiesFactoryFailed")); + } + return res; +} + +std::shared_ptr createEncryptedPropertiesFactory( + const std::shared_ptr& innerStorage, + const std::shared_ptr& uriMapper, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept { + auto adapter = createPropertiesFactory(innerStorage, uriMapper); + if (!adapter) { + ACSDK_ERROR(LX("createEncryptedPropertiesFactoryFailed").d("reason", "miscStorageAdapterCreateFailed")); + return nullptr; + } + return createEncryptedPropertiesFactory(adapter, cryptoFactory, keyStore); +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/EncryptedPropertiesFactory.cpp b/core/Properties/acsdkProperties/src/EncryptedPropertiesFactory.cpp new file mode 100644 index 0000000000..9889ad806a --- /dev/null +++ b/core/Properties/acsdkProperties/src/EncryptedPropertiesFactory.cpp @@ -0,0 +1,72 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage; +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"EncryptedPropertiesFactory"}; + +std::shared_ptr EncryptedPropertiesFactory::create( + const std::shared_ptr& innerFactory, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept { + auto res = std::shared_ptr( + new EncryptedPropertiesFactory(innerFactory, cryptoFactory, keyStore)); + if (!res->init()) { + res.reset(); + } + return res; +} + +EncryptedPropertiesFactory::EncryptedPropertiesFactory( + const std::shared_ptr& storage, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& keyStore) noexcept : + m_storage(storage), + m_cryptoFactory(cryptoFactory), + m_keyStore(keyStore) { +} + +bool EncryptedPropertiesFactory::init() noexcept { + if (!m_storage) { + ACSDK_ERROR(LX("innerStorageNull")); + return false; + } else if (!m_cryptoFactory) { + ACSDK_ERROR(LX("cryptoFactoryNull")); + return false; + } else if (!m_keyStore) { + ACSDK_ERROR(LX("keyStoreNull")); + return false; + } else { + return true; + } +} + +std::shared_ptr EncryptedPropertiesFactory::getProperties(const std::string& configUri) noexcept { + return EncryptedProperties::create(configUri, m_storage->getProperties(configUri), m_cryptoFactory, m_keyStore); +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/EncryptionKeyPropertyCodec.cpp b/core/Properties/acsdkProperties/src/EncryptionKeyPropertyCodec.cpp new file mode 100644 index 0000000000..dde01a4043 --- /dev/null +++ b/core/Properties/acsdkProperties/src/EncryptionKeyPropertyCodec.cpp @@ -0,0 +1,248 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"EncryptionKeyPropertyCodec"}; + +namespace alexaClientSDK { +namespace acsdkProperties { + +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +/// Default digest type used when creating new digests. +/// @private +static constexpr DigestType DEFAULT_DIGEST_TYPE = DigestType::SHA_256; + +bool EncryptionKeyPropertyCodec::encode( + const std::shared_ptr& cryptoFactory, + const std::string& mainKeyAlias, + const KeyChecksum& mainKeyChecksum, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType dataKeyAlgorithm, + const IV& dataKeyIV, + const DataBlock& dataKeyCiphertext, + const Tag& dataKeyTag, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType dataAlgorithm, + Bytes& derEncoded) noexcept { + DigestType digestType = DEFAULT_DIGEST_TYPE; + + if (!cryptoFactory) { + ACSDK_ERROR(LX("encodeFailed").m("cryptoFactoryNull")); + return false; + } + + auto digest = cryptoFactory->createDigest(digestType); + if (!digest) { + ACSDK_ERROR(LX("encodeFailed").m("digestCreateFailed")); + return false; + } + + EncryptionKeyPropertyCodecState helper; + DataBlock digestData; + DataBlock encodedInfo; + + if (!helper.prepareForEncoding()) { + ACSDK_ERROR(LX("encodeFailed").m("encodingPrepareFailed")); + return false; + } + + if (!helper.setVersion(ACSDK_DATA_KEY_VER_V1)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "version")); + return false; + } + + if (!helper.setMainKeyAlias(mainKeyAlias)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "mainKeyAlias")); + return false; + } + + if (!helper.setMainKeyChecksum(mainKeyChecksum)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "mainKeyChecksum")); + return false; + } + + if (!helper.setDataKeyAlgorithm(dataKeyAlgorithm)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "dataKeyAlgorithm")); + return false; + } + + if (!helper.setDataKeyIV(dataKeyIV)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "dataKeyIV")); + return false; + } + + if (!helper.setDataKeyCiphertext(dataKeyCiphertext)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "dataKeyCiphertext")); + return false; + } + + if (!helper.setDataKeyTag(dataKeyTag)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "dataKeyTag")); + return false; + } + + if (!helper.setDataAlgorithm(dataAlgorithm)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "dataAlgorithm")); + return false; + } + + if (!helper.encodeEncInfo(encodedInfo)) { + ACSDK_ERROR(LX("encodeFailed").m("infoEncodingFailed")); + return false; + } + + if (!digest->process(encodedInfo)) { + ACSDK_ERROR(LX("encodeFailed").m("digestProcessFailed")); + return false; + } + + if (!digest->finalize(digestData)) { + ACSDK_ERROR(LX("encodeFailed").m("digestFinalizeFailed")); + return false; + } + + if (!helper.setDigestType(digestType)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "digestType")); + return false; + } + + if (!helper.setDigest(digestData)) { + ACSDK_ERROR(LX("encodeFailed").m("propertySetFailed").d("property", "digestData")); + return false; + } + + if (!helper.encode(derEncoded)) { + ACSDK_ERROR(LX("encodeFailed").m("finalEncodeFailed")); + return false; + } + + return true; +} + +bool EncryptionKeyPropertyCodec::decode( + const std::shared_ptr& cryptoFactory, + const Bytes& derEncoded, + std::string& mainKeyAlias, + KeyChecksum& mainKeyChecksum, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType& dataKeyAlgorithm, + IV& dataKeyIV, + DataBlock& dataKeyCiphertext, + Tag& dataKeyTag, + alexaClientSDK::acsdkCryptoInterfaces::AlgorithmType& dataAlgorithm, + DataBlock& digestDecoded, + DataBlock& digestActual) noexcept { + if (!cryptoFactory) { + ACSDK_ERROR(LX("decodeFailed").m("cryptoFactoryNull")); + return false; + } + + EncryptionKeyPropertyCodecState codecState; + DigestType digestType = DigestType::SHA_256; + + if (!codecState.decode(derEncoded)) { + ACSDK_ERROR(LX("decodeFailed").m("initialDecodeFailed")); + return false; + } + Bytes encoded; + int64_t version = 0; + + if (!codecState.getVersion(version)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "version")); + return false; + } + + if (ACSDK_DATA_KEY_VER_V1 != version) { + ACSDK_ERROR(LX("decodeFailed").m("versionError")); + return false; + } + + if (!codecState.getMainKeyAlias(mainKeyAlias)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "mainKeyAlias")); + return false; + } + + if (!codecState.getMainKeyChecksum(mainKeyChecksum)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "mainKeyChecksum")); + return false; + } + + if (!codecState.getDataKeyAlgorithm(dataKeyAlgorithm)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "dataKeyAlgorithm")); + return false; + } + + if (!codecState.getDataKeyIV(dataKeyIV)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "dataKeyIV")); + return false; + } + + if (!codecState.getDataKeyCiphertext(dataKeyCiphertext)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "dataKeyCiphertext")); + return false; + } + + if (!codecState.getDataKeyTag(dataKeyTag)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "dataKeyTag")); + return false; + } + + if (!codecState.getDataAlgorithm(dataAlgorithm)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "dataAlgorithm")); + return false; + } + + if (!codecState.getDigest(digestDecoded)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "digest")); + return false; + } + + if (!codecState.getDigestType(digestType)) { + ACSDK_ERROR(LX("decodeFailed").m("propertyGetFailed").d("property", "digestType")); + return false; + } + + if (!codecState.encodeEncInfo(encoded)) { + ACSDK_ERROR(LX("decodeFailed").m("encodeInfoFailed")); + return false; + } + + auto digest = cryptoFactory->createDigest(digestType); + if (!digest) { + ACSDK_ERROR(LX("decodeFailed").m("digestInitFailed")); + return false; + } + + if (!digest->process(encoded)) { + ACSDK_ERROR(LX("decodeFailed").m("digestProcessFailed")); + return false; + } + + if (!digest->finalize(digestActual)) { + ACSDK_ERROR(LX("decodeFailed").m("digestFinalizeFailed")); + return false; + } + + return true; +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/EncryptionKeyPropertyCodecState.cpp b/core/Properties/acsdkProperties/src/EncryptionKeyPropertyCodecState.cpp new file mode 100644 index 0000000000..c2731cb928 --- /dev/null +++ b/core/Properties/acsdkProperties/src/EncryptionKeyPropertyCodecState.cpp @@ -0,0 +1,192 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkProperties { + +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; + +EncryptionKeyPropertyCodecState::EncryptionKeyPropertyCodecState() noexcept { + m_asn1Data = nullptr; +} + +EncryptionKeyPropertyCodecState::~EncryptionKeyPropertyCodecState() noexcept { + ACSDK_ENC_PROP_free(m_asn1Data); + m_asn1Data = nullptr; +} + +bool EncryptionKeyPropertyCodecState::prepareForEncoding() noexcept { + if (!m_asn1Data && !(m_asn1Data = ACSDK_ENC_PROP_new())) { + return false; + } + if (!m_asn1Data->encryptionInfo && !(m_asn1Data->encryptionInfo = ACSDK_ENC_INFO_new())) { + return false; + } + return true; +} + +bool EncryptionKeyPropertyCodecState::setVersion(int64_t value) noexcept { + return m_asn1Data->encryptionInfo && + Asn1Helper::setOptInt(m_asn1Data->encryptionInfo->version, value, ACSDK_ENC_INFO::DEF_VER); +} + +bool EncryptionKeyPropertyCodecState::getVersion(int64_t& value) noexcept { + return m_asn1Data->encryptionInfo && + Asn1Helper::getOptInt(m_asn1Data->encryptionInfo->version, value, ACSDK_ENC_INFO::DEF_VER); +} + +bool EncryptionKeyPropertyCodecState::setMainKeyAlias(const std::string& value) noexcept { + return Asn1Helper::setStr(m_asn1Data->encryptionInfo->mainKeyAlias, value); +} + +bool EncryptionKeyPropertyCodecState::getMainKeyAlias(std::string& value) noexcept { + return Asn1Helper::getStr(m_asn1Data->encryptionInfo->mainKeyAlias, value); +} + +bool EncryptionKeyPropertyCodecState::setMainKeyChecksum(const std::vector& value) noexcept { + return Asn1Helper::setData(m_asn1Data->encryptionInfo->mainKeyChecksum, value); +} + +bool EncryptionKeyPropertyCodecState::getMainKeyChecksum(std::vector& value) noexcept { + return Asn1Helper::getData(m_asn1Data->encryptionInfo->mainKeyChecksum, value); +} + +bool EncryptionKeyPropertyCodecState::setDataKeyAlgorithm(AlgorithmType value) noexcept { + int64_t asn1Value; + return Asn1Helper::convertAlgTypeToAsn1(value, asn1Value) && + Asn1Helper::setOptInt( + m_asn1Data->encryptionInfo->dataKeyAlgorithm, asn1Value, ACSDK_ENC_INFO::DEF_DATA_KEY_ALG); +} + +bool EncryptionKeyPropertyCodecState::getDataKeyAlgorithm(AlgorithmType& value) noexcept { + int64_t asn1Value; + return Asn1Helper::getOptInt( + m_asn1Data->encryptionInfo->dataKeyAlgorithm, asn1Value, ACSDK_ENC_INFO::DEF_DATA_KEY_ALG) && + Asn1Helper::convertAlgTypeFromAsn1(asn1Value, value); +} + +bool EncryptionKeyPropertyCodecState::setDataKeyIV(const std::vector& value) noexcept { + return Asn1Helper::setData(m_asn1Data->encryptionInfo->dataKeyIV, value); +} + +bool EncryptionKeyPropertyCodecState::getDataKeyIV(std::vector& value) noexcept { + return Asn1Helper::getData(m_asn1Data->encryptionInfo->dataKeyIV, value); +} + +bool EncryptionKeyPropertyCodecState::setDataKeyCiphertext(const std::vector& value) noexcept { + return Asn1Helper::setData(m_asn1Data->encryptionInfo->dataKeyCiphertext, value); +} + +bool EncryptionKeyPropertyCodecState::getDataKeyCiphertext(std::vector& value) noexcept { + return Asn1Helper::getData(m_asn1Data->encryptionInfo->dataKeyCiphertext, value); +} + +bool EncryptionKeyPropertyCodecState::setDataKeyTag(const Tag& dataKeyTag) noexcept { + return Asn1Helper::setData(m_asn1Data->encryptionInfo->dataKeyTag, dataKeyTag); +} + +bool EncryptionKeyPropertyCodecState::getDataKeyTag(std::vector& dataKeyTag) noexcept { + return Asn1Helper::getData(m_asn1Data->encryptionInfo->dataKeyTag, dataKeyTag); +} + +bool EncryptionKeyPropertyCodecState::setDataAlgorithm(AlgorithmType value) noexcept { + int64_t asn1Value; + return Asn1Helper::convertAlgTypeToAsn1(value, asn1Value) && + Asn1Helper::setOptInt(m_asn1Data->encryptionInfo->dataAlgorithm, asn1Value, ACSDK_ENC_INFO::DEF_DATA_ALG); +} + +bool EncryptionKeyPropertyCodecState::getDataAlgorithm(AlgorithmType& value) noexcept { + int64_t asn1Value; + return Asn1Helper::getOptInt(m_asn1Data->encryptionInfo->dataAlgorithm, asn1Value, ACSDK_ENC_INFO::DEF_DATA_ALG) && + Asn1Helper::convertAlgTypeFromAsn1(asn1Value, value); +} + +bool EncryptionKeyPropertyCodecState::setDigestType(DigestType type) noexcept { + int64_t asn1Type; + return Asn1Helper::convertDigTypeToAsn1(type, asn1Type) && + Asn1Helper::setOptInt(m_asn1Data->digestAlgorithm, asn1Type, ACSDK_ENC_PROP::DEF_DIG_ALG); +} + +bool EncryptionKeyPropertyCodecState::getDigestType(DigestType& type) noexcept { + int64_t asn1Type; + return Asn1Helper::getOptInt(m_asn1Data->digestAlgorithm, asn1Type, ACSDK_ENC_PROP::DEF_DIG_ALG) && + Asn1Helper::convertDigTypeFromAsn1(asn1Type, type); +} + +bool EncryptionKeyPropertyCodecState::setDigest(const std::vector& digest) noexcept { + return Asn1Helper::setData(m_asn1Data->digest, digest); +} + +bool EncryptionKeyPropertyCodecState::getDigest(std::vector& digest) noexcept { + return Asn1Helper::getData(m_asn1Data->digest, digest); +} + +bool EncryptionKeyPropertyCodecState::encodeEncInfo(std::vector& der) noexcept { + if (!m_asn1Data->encryptionInfo) { + return false; + } + + int res = i2d_ACSDK_ENC_INFO(m_asn1Data->encryptionInfo, nullptr); + if (res <= 0) { + return false; + } + + der.resize(res); + unsigned char* data = der.data(); + res = i2d_ACSDK_ENC_INFO(m_asn1Data->encryptionInfo, &data); + if (res <= 0) { + return false; + } + return true; +} + +bool EncryptionKeyPropertyCodecState::encode(std::vector& der) noexcept { + int res = i2d_ACSDK_ENC_PROP(m_asn1Data, nullptr); + if (res <= 0) { + return false; + } + + der.resize(res); + unsigned char* data = der.data(); + res = i2d_ACSDK_ENC_PROP(m_asn1Data, &data); + + if (res <= 0) { + return false; + } + return true; +} + +bool EncryptionKeyPropertyCodecState::decode(const std::vector& der) noexcept { + if (m_asn1Data) { + ACSDK_ENC_PROP_free(m_asn1Data); + m_asn1Data = nullptr; + } + const unsigned char* data = der.data(); + m_asn1Data = d2i_ACSDK_ENC_PROP(nullptr, &data, der.size()); + if (!m_asn1Data) { + return false; + } + + return true; +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/ErrorCallbackSetter.cpp b/core/Properties/acsdkProperties/src/ErrorCallbackSetter.cpp new file mode 100644 index 0000000000..ec716f7ec5 --- /dev/null +++ b/core/Properties/acsdkProperties/src/ErrorCallbackSetter.cpp @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +bool setErrorCallback( + const std::weak_ptr& callback, + uint32_t maxRetries, + std::weak_ptr* previous) noexcept { + return RetryExecutor::setErrorCallback(callback, maxRetries, previous); +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h b/core/Properties/acsdkProperties/src/Logging.cpp similarity index 50% rename from AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h rename to core/Properties/acsdkProperties/src/Logging.cpp index ee7bb3c269..5b5d1dfc89 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h +++ b/core/Properties/acsdkProperties/src/Logging.cpp @@ -1,6 +1,4 @@ /* - * SDKVersion.h - * * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). @@ -15,38 +13,35 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_SDKVERSION_H_ -#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_SDKVERSION_H_ +#include -#include +#include +#include namespace alexaClientSDK { namespace avsCommon { namespace utils { - -/// These functions are responsible for providing access to the current SDK version. -/// NOTE: To make changes to this file you *MUST* do so via SDKVersion.h.in. -namespace sdkVersion { - -inline static std::string getCurrentVersion() { - return "1.25.0"; -} - -inline static int getMajorVersion() { - return 1; -} - -inline static int getMinorVersion() { - return 25; +namespace logger { + +template <> +LogEntry& LogEntry::d(const char* key, const std::vector& value) { + prefixKeyValuePair(); + std::string hexValue; + alexaClientSDK::acsdkCodecUtils::encodeHex(value, hexValue); + m_stream << key << KEY_VALUE_SEPARATOR << hexValue; + return *this; } -inline static int getPatchVersion() { - return 0; -} - -} // namespace sdkVersion +} // namespace logger } // namespace utils } // namespace avsCommon } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_SDKVERSION_H_ +namespace alexaClientSDK { +namespace acsdkProperties { + +const std::string CONFIG_URI{"configUri"}; +const std::string KEY{"key"}; + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/MiscStorageAdapter.cpp b/core/Properties/acsdkProperties/src/MiscStorageAdapter.cpp new file mode 100644 index 0000000000..0fe8f5d783 --- /dev/null +++ b/core/Properties/acsdkProperties/src/MiscStorageAdapter.cpp @@ -0,0 +1,40 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +using namespace alexaClientSDK::avsCommon::sdkInterfaces::storage; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"MiscStorageAdapter"}; + +std::shared_ptr createPropertiesFactory( + const std::shared_ptr& innerStorage, + const std::shared_ptr& nameMapper) noexcept { + auto res = MiscStoragePropertiesFactory::create(innerStorage, nameMapper); + if (!res) { + ACSDK_ERROR(LX("createPropertiesFactory")); + } + return res; +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/MiscStorageProperties.cpp b/core/Properties/acsdkProperties/src/MiscStorageProperties.cpp new file mode 100644 index 0000000000..256aa33078 --- /dev/null +++ b/core/Properties/acsdkProperties/src/MiscStorageProperties.cpp @@ -0,0 +1,377 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage; +using namespace ::alexaClientSDK::acsdkCodecUtils; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"MiscStorageProperties"}; + +std::shared_ptr MiscStorageProperties::create( + const std::shared_ptr& storage, + const std::string& configUri, + const std::string& componentName, + const std::string& tableName) { + if (!storage) { + ACSDK_ERROR(LX_CFG("createFailed", configUri).m("nullStorage")); + return nullptr; + } + + auto res = + std::shared_ptr(new MiscStorageProperties(storage, configUri, componentName, tableName)); + if (res->init()) { + ACSDK_DEBUG0(LX_CFG("createSuccess", configUri)); + } else { + res.reset(); + ACSDK_ERROR(LX_CFG("createFailed", configUri)); + } + return res; +} + +MiscStorageProperties::MiscStorageProperties( + const std::shared_ptr& storage, + const std::string& configUri, + const std::string& componentName, + const std::string& tableName) : + m_storage(storage), + m_configUri(configUri), + m_componentName(componentName), + m_tableName(tableName) { +} + +bool MiscStorageProperties::init() { + bool tableExists = false; + + // create executor to invoke error callback and limit number of retries + RetryExecutor executor{OperationType::Open, m_configUri}; + + auto result = executor.execute( + "tableExists", + [this, &tableExists]() -> StatusCodeWithRetry { + if (m_storage->tableExists(m_componentName, m_tableName, &tableExists)) { + ACSDK_DEBUG9(LX_CFG("tableExistsSuccess", m_configUri)); + return RetryExecutor::SUCCESS; + } else { + ACSDK_DEBUG9(LX_CFG("tableExistsRetryableFailure", m_configUri)); + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success != result) { + ACSDK_DEBUG0(LX_CFG("initFailure", m_configUri)); + return false; + } + + if (!tableExists) { + result = executor.execute( + "tableExists", + [this]() -> StatusCodeWithRetry { + if (m_storage->createTable( + m_componentName, + m_tableName, + MiscStorageInterface::KeyType::STRING_KEY, + MiscStorageInterface::ValueType::STRING_VALUE)) { + ACSDK_DEBUG9(LX_CFG("createTableSuccess", m_configUri)); + return RetryExecutor::SUCCESS; + } else { + ACSDK_DEBUG9(LX_CFG("createTableRetryableFailure", m_configUri)); + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + } + }, + Action::FAIL); + if (RetryableOperationResult::Success != result) { + ACSDK_DEBUG0(LX_CFG("initFailure", m_configUri)); + return false; + } + } + + ACSDK_DEBUG0(LX_CFG("initSuccess", m_configUri)); + return true; +} + +bool MiscStorageProperties::getString(const std::string& key, std::string& value) noexcept { + // create executor to invoke error callback and limit number of retries + RetryExecutor executor{OperationType::Get, m_configUri}; + + auto success = executeRetryableKeyAction( + executor, + "getString", + key, + [this, &key, &value]() -> bool { return m_storage->get(m_componentName, m_tableName, key, &value); }, + true, + false); + + if (success) { + ACSDK_DEBUG0(LX_CFG_KEY("getStringSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("getStringFailed", m_configUri, key)); + return false; + } +} + +bool MiscStorageProperties::putString(const std::string& key, const std::string& value) noexcept { + // create executor to invoke error callback and limit number of retries + RetryExecutor executor{OperationType::Put, m_configUri}; + auto success = executeRetryableKeyAction( + executor, + "putString", + key, + [this, &key, &value]() -> bool { return m_storage->put(m_componentName, m_tableName, key, value); }, + true, + false); + + if (success) { + ACSDK_DEBUG0(LX_CFG_KEY("putStringSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("putStringFailed", m_configUri, key)); + return false; + } +} + +bool MiscStorageProperties::getBytes(const std::string& key, Bytes& value) noexcept { + // create executor to invoke error callback and limit number of retries + RetryExecutor executor{OperationType::Get, m_configUri}; + std::string base64Value; + auto success = executeRetryableKeyAction( + executor, + "getBytes", + key, + [this, &key, &base64Value]() -> bool { + return m_storage->get(m_componentName, m_tableName, key, &base64Value); + }, + false, + false); + + if (!success) { + ACSDK_ERROR(LX_CFG_KEY("getBytesFailed", m_configUri, key)); + return false; + } + + if (decodeBase64(base64Value, value)) { + ACSDK_DEBUG0(LX_CFG_KEY("getBytesSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("getBytesDecodeFailed", m_configUri, key)); + return false; + } +} + +bool MiscStorageProperties::putBytes(const std::string& key, const Bytes& value) noexcept { + // create executor to invoke error callback and limit number of retries + RetryExecutor executor{OperationType::Put, m_configUri}; + + std::string base64Value; + if (!encodeBase64(value, base64Value)) { + ACSDK_ERROR(LX_CFG_KEY("putBytesEncodingFailed", m_configUri, key)); + return false; + } + + auto success = executeRetryableKeyAction( + executor, + "putBytes", + key, + [this, &key, &base64Value]() -> bool { return m_storage->put(m_componentName, m_tableName, key, base64Value); }, + false, + false); + + if (success) { + ACSDK_DEBUG0(LX_CFG_KEY("putBytesSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("putBytesFailed", m_configUri, key)); + return false; + } +} + +bool MiscStorageProperties::remove(const std::string& key) noexcept { + RetryExecutor executor{OperationType::Put, m_configUri}; + auto success = executeRetryableKeyAction( + executor, + "remove", + key, + [this, &key]() -> bool { return m_storage->remove(m_componentName, m_tableName, key); }, + false, + false); + + if (success) { + ACSDK_DEBUG0(LX_CFG_KEY("removeSuccess", m_configUri, key)); + return true; + } else { + ACSDK_ERROR(LX_CFG_KEY("removeFailed", m_configUri, key)); + return false; + } +} + +bool MiscStorageProperties::getKeys(std::unordered_set& valueContainer) noexcept { + RetryExecutor executor{OperationType::Get, m_configUri}; + + if (loadKeysWithRetries(executor, valueContainer)) { + ACSDK_DEBUG0(LX_CFG("getKeysSuccess", m_configUri)); + return true; + } else { + ACSDK_ERROR(LX_CFG("getKeysFailed", m_configUri)); + return false; + } +} + +bool MiscStorageProperties::clear() noexcept { + RetryExecutor executor{OperationType::Put, m_configUri}; + if (clearAllValuesWithRetries(executor)) { + ACSDK_DEBUG0(LX_CFG("clearSuccess", m_configUri)); + return true; + } else { + ACSDK_ERROR(LX_CFG("clearFailed", m_configUri)); + return false; + } +} + +bool MiscStorageProperties::loadKeysWithRetries( + RetryExecutor& executor, + std::unordered_set& keys) noexcept { + std::unordered_map tmp; + auto result = executor.execute( + "loadKeysWithRetries", + [this, &tmp]() -> StatusCodeWithRetry { + if (m_storage->load(m_componentName, m_tableName, &tmp)) { + ACSDK_DEBUG9(LX_CFG("loadKeysSuccess", m_configUri)); + return RetryExecutor::SUCCESS; + } else { + ACSDK_DEBUG9(LX_CFG("loadKeysRetryableFailure", m_configUri)); + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success != result) { + ACSDK_DEBUG0(LX_CFG("loadKeysWithRetriesFailed", m_configUri)); + return false; + } + + keys.clear(); + std::transform( + tmp.cbegin(), + tmp.cend(), + std::inserter(keys, keys.end()), + [](const std::pair& x) -> const std::string& { + ACSDK_INFO(LX("getKey").d("name", x.first)); + return x.first; + }); + + ACSDK_DEBUG0(LX_CFG("loadKeysWithRetriesSuccess", m_configUri)); + return true; +} + +bool MiscStorageProperties::executeRetryableKeyAction( + RetryExecutor& executor, + const std::string& actionName, + const std::string& key, + const std::function& action, + bool canCleanup, + bool failOnCleanup) noexcept { + auto result = executor.execute( + actionName, + [this, &actionName, &key, &action]() -> StatusCodeWithRetry { + // suppress unused variable errors + ACSDK_UNUSED_VARIABLE(this); + ACSDK_UNUSED_VARIABLE(actionName); + ACSDK_UNUSED_VARIABLE(key); + if (action()) { + ACSDK_DEBUG9(LX_CFG_KEY(actionName + "Success", m_configUri, key)); + return RetryExecutor::SUCCESS; + } else { + ACSDK_DEBUG9(LX_CFG_KEY(actionName + "RetryableFailure", m_configUri, key)); + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success == result) { + ACSDK_DEBUG0(LX_CFG_KEY(actionName + "Success", m_configUri, key)); + return true; + } else if (canCleanup && RetryableOperationResult::Cleanup == result) { + if (deleteValueWithRetries(executor, key)) { + ACSDK_DEBUG0(LX_CFG_KEY(actionName + "CleanupSuccess", m_configUri, key)); + return !failOnCleanup; + } else { + ACSDK_DEBUG0(LX_CFG_KEY(actionName + "CleanupFailure", m_configUri, key)); + return false; + } + } else { + ACSDK_DEBUG0(LX_CFG_KEY(actionName + "Failure", m_configUri, key)); + return false; + } +} + +bool MiscStorageProperties::deleteValueWithRetries(RetryExecutor& executor, const std::string& key) noexcept { + auto result = executor.execute( + "deleteValue", + [this, &key]() -> StatusCodeWithRetry { + if (!m_storage->remove(m_componentName, m_tableName, key)) { + ACSDK_DEBUG9(LX_CFG_KEY("deleteValueSuccess", m_configUri, key)); + return RetryExecutor::SUCCESS; + } else { + ACSDK_DEBUG9(LX_CFG_KEY("deleteValueRetryableFailure", m_configUri, key)); + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success != result) { + ACSDK_DEBUG0(LX_CFG_KEY("deleteValueFailure", m_configUri, key)); + return false; + } + + ACSDK_DEBUG0(LX_CFG_KEY("deleteValueSuccess", m_configUri, key)); + return true; +} + +bool MiscStorageProperties::clearAllValuesWithRetries(RetryExecutor& executor) noexcept { + auto result = executor.execute( + "clear", + [this]() -> StatusCodeWithRetry { + if (m_storage->clearTable(m_componentName, m_tableName)) { + ACSDK_DEBUG9(LX_CFG("clearTableSuccess", m_configUri)); + return RetryExecutor::SUCCESS; + } else { + ACSDK_DEBUG9(LX_CFG("clearTableRetryableFailure", m_configUri)); + return RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR; + } + }, + Action::FAIL); + + if (RetryableOperationResult::Success != result) { + ACSDK_DEBUG0(LX_CFG("clearTableFailure", m_configUri)); + return false; + } + + ACSDK_DEBUG0(LX_CFG("clearValuesSuccess", m_configUri)); + return true; +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/MiscStoragePropertiesFactory.cpp b/core/Properties/acsdkProperties/src/MiscStoragePropertiesFactory.cpp new file mode 100644 index 0000000000..fabdffcb29 --- /dev/null +++ b/core/Properties/acsdkProperties/src/MiscStoragePropertiesFactory.cpp @@ -0,0 +1,108 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +using namespace alexaClientSDK::acsdkPropertiesInterfaces; +using namespace alexaClientSDK::avsCommon::sdkInterfaces::storage; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"MiscStoragePropertiesFactory"}; + +std::shared_ptr MiscStoragePropertiesFactory::create( + const std::shared_ptr& storage, + const std::shared_ptr& uriMapper) noexcept { + auto res = std::shared_ptr(new MiscStoragePropertiesFactory(storage, uriMapper)); + if (!res->init()) { + res.reset(); + } + return res; +} + +MiscStoragePropertiesFactory::MiscStoragePropertiesFactory( + const std::shared_ptr& storage, + const std::shared_ptr& uriMapper) noexcept : + m_storage{storage}, + m_uriMapper{uriMapper} { +} + +bool MiscStoragePropertiesFactory::init() noexcept { + if (!m_storage || !m_uriMapper) { + ACSDK_ERROR(LX("initFailed").d("reason", "storageOrMapperNull")); + return false; + } + + if (!m_storage->isOpened()) { + if (!m_storage->open()) { + ACSDK_DEBUG9(LX("init").m("openFailed")); + if (!m_storage->createDatabase()) { + ACSDK_ERROR(LX("initFailed").d("reason", "storageOpenAndCreateFailed")); + return false; + } + } + } + + ACSDK_DEBUG5(LX("initSuccess")); + + return true; +} + +void MiscStoragePropertiesFactory::dropNullReferences() noexcept { + for (auto it = m_openProperties.begin(); it != m_openProperties.end();) { + if (it->second.expired()) { + it = m_openProperties.erase(it); + } else { + it++; + } + } +} + +std::shared_ptr MiscStoragePropertiesFactory::getProperties( + const std::string& configUri) noexcept { + std::lock_guard stateLock(m_stateMutex); + dropNullReferences(); + + std::shared_ptr result; + auto it = m_openProperties.find(configUri); + if (it != m_openProperties.end()) { + result = it->second.lock(); + } + + if (!result) { + std::string componentName; + std::string tableName; + if (m_uriMapper->extractComponentAndTableName(configUri, componentName, tableName)) { + result = MiscStorageProperties::create(m_storage, configUri, componentName, tableName); + + if (result) { + m_openProperties.emplace(configUri, result); + } + } else { + ACSDK_ERROR(LX("failedToExtractComponentAndTableName").d("configURI", configUri)); + return nullptr; + } + } + + return result; +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/RetryExecutor.cpp b/core/Properties/acsdkProperties/src/RetryExecutor.cpp new file mode 100644 index 0000000000..248fe500f1 --- /dev/null +++ b/core/Properties/acsdkProperties/src/RetryExecutor.cpp @@ -0,0 +1,160 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"RetryExecutor"}; + +const StatusCodeWithRetry RetryExecutor::SUCCESS{StatusCode::SUCCESS, false}; +const StatusCodeWithRetry RetryExecutor::RETRYABLE_CRYPTO_ERROR{StatusCode::CRYPTO_ERROR, true}; +const StatusCodeWithRetry RetryExecutor::NON_RETRYABLE_CRYPTO_ERROR{StatusCode::CRYPTO_ERROR, false}; +const StatusCodeWithRetry RetryExecutor::RETRYABLE_HSM_ERROR{StatusCode::HSM_ERROR, true}; +const StatusCodeWithRetry RetryExecutor::RETRYABLE_INNER_PROPERTIES_ERROR{StatusCode::INNER_PROPERTIES_ERROR, true}; +const StatusCodeWithRetry RetryExecutor::NON_RETRYABLE_INNER_PROPERTIES_ERROR{StatusCode::INNER_PROPERTIES_ERROR, + false}; + +std::mutex RetryExecutor::c_stateMutex; +volatile uint32_t RetryExecutor::c_maxRetries{DEFAULT_MAX_RETRIES}; +std::weak_ptr RetryExecutor::c_callback; + +bool RetryExecutor::setErrorCallback( + const std::weak_ptr& callback, + uint32_t maxRetries, + std::weak_ptr* previous) noexcept { + auto tmp = callback; + + { + std::unique_lock lock(c_stateMutex); + c_maxRetries = maxRetries; + c_callback.swap(tmp); + } + if (previous) { + previous->swap(tmp); + } + + ACSDK_DEBUG0(LX("setErrorCallbackSuccess")); + return true; +} + +RetryExecutor::RetryExecutor(OperationType operationType, const std::string& configUri) : + m_operationType{operationType}, + m_configUri{configUri}, + m_retryCounter{0} { + std::lock_guard lock(c_stateMutex); + m_callback = c_callback.lock(); + m_retryCounter = c_maxRetries; +} + +RetryableOperationResult RetryExecutor::execute( + const std::string& actionName, + const RetryableOperation& operation, + Action continueAction) noexcept { + if (!isValidContinueAction(continueAction)) { + ACSDK_ERROR(LX("executeFailed").d("action", actionName).m("continueActionInvalid")); + return RetryableOperationResult::Failure; + } + + for (;;) { + const auto result = operation(); + if (StatusCode::SUCCESS == result.first) { + // Upon successful result, forward the result to caller. + ACSDK_DEBUG9(LX("executeSuccess").d("action", actionName).d("retriesLeft", m_retryCounter)); + return RetryableOperationResult::Success; + } else if (!result.second) { + // Upon unsuccessful non-retryable result, invoke the callback, and forward the result to caller. + invokeErrorCallback(result.first); + ACSDK_DEBUG9(LX("executeFailed").d("action", actionName).d("retriesLeft", m_retryCounter)); + return RetryableOperationResult::Failure; + } else { + // Check if we can retry + auto action = invokeErrorCallback(result.first); + if (Action::CONTINUE == action) { + action = continueAction; + } + switch (action) { + case Action::CLEAR_DATA: + ACSDK_DEBUG9(LX("executeCleanup").d("action", actionName).d("retriesLeft", m_retryCounter)); + return RetryableOperationResult::Cleanup; + case Action::RETRY: + ACSDK_DEBUG9(LX("executeRetry").d("action", actionName).d("retriesLeft", m_retryCounter)); + continue; + case Action::FAIL: // fall through + default: + ACSDK_DEBUG9(LX("executeFailed").d("action", actionName).d("retriesLeft", m_retryCounter)); + return RetryableOperationResult::Failure; + } + } + } +} + +Action RetryExecutor::invokeErrorCallback(StatusCode status) noexcept { + Action result = Action::CONTINUE; + + if (m_callback) { + switch (m_operationType) { + case OperationType::Open: + ACSDK_DEBUG0(LX("invokeErrorCallbackOnOpen").d("configUri", m_configUri)); + result = m_callback->onOpenPropertiesError(status, m_configUri); + break; + case OperationType::Get: + ACSDK_DEBUG0(LX("invokeErrorCallbackOnGet").d("configUri", m_configUri)); + result = m_callback->onGetPropertyError(status, m_configUri); + break; + case OperationType::Put: + ACSDK_DEBUG0(LX("invokeErrorCallbackOnPut").d("configUri", m_configUri)); + result = m_callback->onPutPropertyError(status, m_configUri); + break; + case OperationType::Other: // fall through + default: + ACSDK_WARN(LX("invokeErrorCallbackFailed").m("otherOperation")); + result = Action::FAIL; + break; + } + } + ACSDK_DEBUG9(LX("invokeErrorCallback").d("result", static_cast(result))); + + if (Action::RETRY == result) { + if (!m_retryCounter) { + ACSDK_ERROR(LX("invokeErrorCallbackFailed").d("configUri", m_configUri).m("tooManyRetries")); + return Action::FAIL; + } + if (m_retryCounter != UNLIMITED_RETRIES) { + m_retryCounter--; + } + } + + return result; +} + +bool RetryExecutor::isValidContinueAction(Action continueAction) noexcept { + switch (continueAction) { + case Action::FAIL: + case Action::RETRY: + case Action::CLEAR_DATA: + return true; + default: + return false; + } +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/src/SimpleMiscStorageUriMapper.cpp b/core/Properties/acsdkProperties/src/SimpleMiscStorageUriMapper.cpp new file mode 100644 index 0000000000..3dd8373869 --- /dev/null +++ b/core/Properties/acsdkProperties/src/SimpleMiscStorageUriMapper.cpp @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { + +static const std::string TAG{"SimpleMiscStorageUriMapper"}; + +std::shared_ptr SimpleMiscStorageUriMapper::create(char sep) noexcept { + return std::shared_ptr(new SimpleMiscStorageUriMapper(sep)); +} + +SimpleMiscStorageUriMapper::SimpleMiscStorageUriMapper(char separator) noexcept : m_separator(separator) { +} + +bool SimpleMiscStorageUriMapper::extractComponentAndTableName( + const std::string& configUri, + std::string& componentName, + std::string& tableName) noexcept { + size_t index = configUri.find(m_separator); + + if (std::string::npos != index) { + componentName = configUri.substr(0, index); + tableName = configUri.substr(index + 1); + ACSDK_DEBUG0(LX_CFG("extractComponentAndTableNameSuccess", configUri) + .d("componentName", componentName) + .d("tableName", tableName)); + return true; + } else { + ACSDK_ERROR(LX_CFG("extractComponentAndTableNameError", configUri)); + return false; + } +} + +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/test/CMakeLists.txt b/core/Properties/acsdkProperties/test/CMakeLists.txt new file mode 100644 index 0000000000..d893a81d10 --- /dev/null +++ b/core/Properties/acsdkProperties/test/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +set(UNITTEST_INCLUDES + "${acsdkProperties_SOURCE_DIR}/include" + "${acsdkProperties_SOURCE_DIR}/privateInclude" + "${AVSCommon_SOURCE_DIR}/Utils/test" + ) +set(UNITTEST_LIBRARIES + SDKInterfacesTests + acsdkProperties + acsdkPropertiesInterfaces + SDKInterfacesTests + acsdkCodecUtils + ) + +add_definitions( + -DACSDK_LOG_MODULE=acsdkPropertiesTest +) + +discover_unit_tests("${UNITTEST_INCLUDES}" "${UNITTEST_LIBRARIES}") diff --git a/core/Properties/acsdkProperties/test/MiscStoragePropertiesFactoryTest.cpp b/core/Properties/acsdkProperties/test/MiscStoragePropertiesFactoryTest.cpp new file mode 100644 index 0000000000..04d76e6f56 --- /dev/null +++ b/core/Properties/acsdkProperties/test/MiscStoragePropertiesFactoryTest.cpp @@ -0,0 +1,142 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkProperties { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage; +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage::test; + +/// Test constructor. +TEST(MiscStoragePropertiesFactoryTest, test_createOpened) { + auto mockMiscStorage = std::make_shared(); + EXPECT_CALL(*mockMiscStorage, isOpened()).WillOnce(Return(true)); + EXPECT_CALL(*mockMiscStorage, createDatabase()).Times(0); + EXPECT_CALL(*mockMiscStorage, open()).Times(0); + + auto factory = MiscStoragePropertiesFactory::create(mockMiscStorage, SimpleMiscStorageUriMapper::create()); + + ASSERT_NE(nullptr, factory); +} + +TEST(MiscStoragePropertiesFactoryTest, test_createClosed) { + auto mockMiscStorage = std::make_shared(); + EXPECT_CALL(*mockMiscStorage, isOpened()).WillOnce(Return(false)); + EXPECT_CALL(*mockMiscStorage, open()).WillOnce(Return(true)); + EXPECT_CALL(*mockMiscStorage, createDatabase()).Times(0); + + auto factory = MiscStoragePropertiesFactory::create(mockMiscStorage, SimpleMiscStorageUriMapper::create()); + + ASSERT_NE(nullptr, factory); +} + +TEST(MiscStoragePropertiesFactoryTest, test_createDatabase) { + auto mockMiscStorage = std::make_shared(); + EXPECT_CALL(*mockMiscStorage, isOpened()).WillOnce(Return(false)); + EXPECT_CALL(*mockMiscStorage, open()).WillOnce(Return(false)); + EXPECT_CALL(*mockMiscStorage, createDatabase()).WillOnce(Return(true)); + + auto factory = MiscStoragePropertiesFactory::create(mockMiscStorage, SimpleMiscStorageUriMapper::create()); + + ASSERT_NE(nullptr, factory); +} + +TEST(MiscStoragePropertiesFactoryTest, test_createWhenStorageClosedAndCreateFails) { + auto mockMiscStorage = std::make_shared(); + EXPECT_CALL(*mockMiscStorage, isOpened()).WillOnce(Return(false)); + EXPECT_CALL(*mockMiscStorage, open()).WillOnce(Return(false)); + EXPECT_CALL(*mockMiscStorage, createDatabase()).WillOnce(Return(false)); + + auto factory = MiscStoragePropertiesFactory::create(mockMiscStorage, SimpleMiscStorageUriMapper::create()); + + ASSERT_EQ(nullptr, factory); +} + +TEST(MiscStoragePropertiesFactoryTest, test_createNullArgs) { + auto factory = MiscStoragePropertiesFactory::create(nullptr, SimpleMiscStorageUriMapper::create()); + + EXPECT_EQ(nullptr, factory); + + auto mockMiscStorage = std::make_shared(); + factory = MiscStoragePropertiesFactory::create(mockMiscStorage, nullptr); + ASSERT_EQ(nullptr, factory); +} + +TEST(MiscStoragePropertiesFactoryTest, test_createProperties) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, isOpened()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockMiscStorage, tableExists(Eq("component"), Eq("namespace"), _)) + .WillOnce(Invoke([](const std::string& comp, const std::string& name, bool* res) -> bool { + *res = true; + return true; + })); + + auto factory = MiscStoragePropertiesFactory::create(mockMiscStorage, SimpleMiscStorageUriMapper::create()); + ASSERT_NE(nullptr, factory); + + auto properties = factory->getProperties("component/namespace"); + ASSERT_NE(nullptr, properties); +} + +TEST(MiscStoragePropertiesFactoryTest, test_createPropertiesCreatesTable) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, isOpened()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockMiscStorage, tableExists(Eq("component"), Eq("namespace"), _)) + .WillOnce(Invoke([](const std::string& comp, const std::string& name, bool* res) -> bool { + *res = false; + return true; + })); + EXPECT_CALL(*mockMiscStorage, createTable(Eq("component"), Eq("namespace"), _, _)).WillOnce(Return(true)); + + auto factory = MiscStoragePropertiesFactory::create(mockMiscStorage, SimpleMiscStorageUriMapper::create()); + ASSERT_NE(nullptr, factory); + + auto properties = factory->getProperties("component/namespace"); + ASSERT_NE(nullptr, properties); +} + +TEST(MiscStoragePropertiesFactoryTest, test_createPropertiesFailsToCreateTable) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, isOpened()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockMiscStorage, tableExists(Eq("component"), Eq("namespace"), _)) + .WillOnce(Invoke([](const std::string& comp, const std::string& name, bool* res) -> bool { + *res = false; + return true; + })); + EXPECT_CALL(*mockMiscStorage, createTable(Eq("component"), Eq("namespace"), _, _)).WillOnce(Return(false)); + + auto factory = MiscStoragePropertiesFactory::create(mockMiscStorage, SimpleMiscStorageUriMapper::create()); + ASSERT_NE(nullptr, factory); + + auto properties = factory->getProperties("component/namespace"); + ASSERT_EQ(nullptr, properties); +} + +} // namespace test +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/test/MiscStoragePropertiesTest.cpp b/core/Properties/acsdkProperties/test/MiscStoragePropertiesTest.cpp new file mode 100644 index 0000000000..f540c3a20a --- /dev/null +++ b/core/Properties/acsdkProperties/test/MiscStoragePropertiesTest.cpp @@ -0,0 +1,281 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + * @private + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +namespace alexaClientSDK { +namespace acsdkProperties { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage; +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage::test; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"MiscStoragePropertiesTest"}; + +/// Test that the constructor. +TEST(MiscStoragePropertiesTest, testCreatePropertiesTableExists) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(Eq("component"), Eq("namespace"), _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + + ASSERT_NE(nullptr, props); +} + +TEST(MiscStoragePropertiesTest, testCreatePropertiesCreateTable) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(Eq("component"), Eq("namespace"), _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = false; + return true; + })); + EXPECT_CALL( + *mockMiscStorage, + createTable( + Eq("component"), + Eq("namespace"), + Eq(MiscStorageInterface::KeyType::STRING_KEY), + Eq(MiscStorageInterface::ValueType::STRING_VALUE))) + .WillOnce(Invoke( + [](const std::string&, const std::string&, MiscStorageInterface::KeyType, MiscStorageInterface::ValueType) { + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + + ASSERT_NE(nullptr, props); +} + +TEST(MiscStoragePropertiesTest, testCreatePropertiesCreateTableFailed) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(Eq("component"), Eq("namespace"), _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = false; + return true; + })); + EXPECT_CALL( + *mockMiscStorage, + createTable( + Eq("component"), + Eq("namespace"), + Eq(MiscStorageInterface::KeyType::STRING_KEY), + Eq(MiscStorageInterface::ValueType::STRING_VALUE))) + .WillOnce(Invoke( + [](const std::string&, const std::string&, MiscStorageInterface::KeyType, MiscStorageInterface::ValueType) { + return false; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + + ASSERT_EQ(nullptr, props); +} + +TEST(MiscStoragePropertiesTest, testCreatePropertiesTableExistsFailed) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(Eq("component"), Eq("namespace"), _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { return false; })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + + ASSERT_EQ(nullptr, props); +} + +TEST(MiscStoragePropertiesTest, testPutString) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(_, _, _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + ASSERT_NE(nullptr, props); + + EXPECT_CALL(*mockMiscStorage, put(Eq("component"), Eq("namespace"), Eq("key"), Eq("value"))).WillOnce(Return(true)); + ASSERT_EQ(true, props->putString("key", "value")); +} + +TEST(MiscStoragePropertiesTest, testPutBytes) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(_, _, _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + ASSERT_NE(nullptr, props); + + EXPECT_CALL(*mockMiscStorage, put(Eq("component"), Eq("namespace"), Eq("key"), Eq("AAEC"))).WillOnce(Return(true)); + ASSERT_TRUE(props->putBytes("key", MiscStorageProperties::Bytes{0, 1, 2})); +} + +TEST(MiscStoragePropertiesTest, testPutFailed) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(_, _, _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + ASSERT_NE(nullptr, props); + + EXPECT_CALL(*mockMiscStorage, put(Eq("component"), Eq("namespace"), Eq("key"), Eq("value"))) + .WillOnce(Return(false)); + ASSERT_FALSE(props->putString("key", "value")); +} + +TEST(MiscStoragePropertiesTest, testGetString) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(_, _, _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + ASSERT_NE(nullptr, props); + + EXPECT_CALL(*mockMiscStorage, get(Eq("component"), Eq("namespace"), Eq("key"), _)) + .WillOnce(Invoke([](const std::string&, const std::string&, const std::string&, std::string* res) -> bool { + *res = "value"; + return true; + })); + std::string value; + ASSERT_TRUE(props->getString("key", value)); + ASSERT_EQ("value", value); +} + +TEST(MiscStoragePropertiesTest, testGetBinary) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(_, _, _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + ASSERT_NE(nullptr, props); + + EXPECT_CALL(*mockMiscStorage, get(Eq("component"), Eq("namespace"), Eq("key"), _)) + .WillOnce(Invoke([](const std::string&, const std::string&, const std::string&, std::string* res) -> bool { + *res = "AAEC"; + return true; + })); + MiscStorageProperties::Bytes value; + ASSERT_TRUE(props->getBytes("key", value)); + ASSERT_EQ((MiscStorageProperties::Bytes{0, 1, 2}), value); +} + +TEST(MiscStoragePropertiesTest, testGetFailed) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(_, _, _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + ASSERT_NE(nullptr, props); + + EXPECT_CALL(*mockMiscStorage, get(Eq("component"), Eq("namespace"), Eq("key"), _)).WillOnce(Return(false)); + std::string tmp; + ASSERT_FALSE(props->getString("key", tmp)); +} + +TEST(MiscStoragePropertiesTest, testRemove) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(_, _, _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + ASSERT_NE(nullptr, props); + + EXPECT_CALL(*mockMiscStorage, remove(Eq("component"), Eq("namespace"), Eq("key"))).WillOnce(Return(true)); + ASSERT_TRUE(props->remove("key")); +} + +TEST(MiscStoragePropertiesTest, testClear) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(_, _, _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + ASSERT_NE(nullptr, props); + + EXPECT_CALL(*mockMiscStorage, clearTable(Eq("component"), Eq("namespace"))).WillOnce(Return(true)); + ASSERT_TRUE(props->clear()); +} + +TEST(MiscStoragePropertiesTest, testClearFailedClearTable) { + auto mockMiscStorage = std::make_shared(); + + EXPECT_CALL(*mockMiscStorage, tableExists(_, _, _)) + .WillOnce(Invoke([](const std::string&, const std::string&, bool* res) { + *res = true; + return true; + })); + + auto props = MiscStorageProperties::create(mockMiscStorage, "component/namespace", "component", "namespace"); + ASSERT_NE(nullptr, props); + + EXPECT_CALL(*mockMiscStorage, clearTable(Eq("component"), Eq("namespace"))).WillOnce(Return(false)); + ASSERT_FALSE(props->clear()); +} + +} // namespace test +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/testCrypto/CMakeLists.txt b/core/Properties/acsdkProperties/testCrypto/CMakeLists.txt new file mode 100644 index 0000000000..bc85cb177f --- /dev/null +++ b/core/Properties/acsdkProperties/testCrypto/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +set(UNITTEST_INCLUDES + "${acsdkProperties_SOURCE_DIR}/include" + "${acsdkProperties_SOURCE_DIR}/privateInclude" + "${AVSCommon_SOURCE_DIR}/Utils/test" + "${CRYPTO_INCLUDE_DIRS}" + ) + +set(UNITTEST_LIBRARIES + acsdkProperties + acsdkCrypto + acsdkPkcs11 + acsdkCryptoInterfaces + acsdkPropertiesInterfaces + SDKInterfacesTests + acsdkCryptoInterfacesTestLib + acsdkPropertiesInterfacesTestLib + acsdkCodecUtils + acsdkPkcs11Stubs + ) + +add_definitions( + -DACSDK_LOG_MODULE=acsdkEncryptedPropertiesTest + -DPKCS11_LIBRARY="${PKCS11_TEST_LIBRARY}" + -DPKCS11_PIN="${PKCS11_TEST_USER_PIN}" + -DPKCS11_KEY_NAME="${PKCS11_TEST_MAIN_KEY_ALIAS}" + -DPKCS11_TOKEN_NAME="${PKCS11_TEST_TOKEN_NAME}" +) + +discover_unit_tests("${UNITTEST_INCLUDES}" "${UNITTEST_LIBRARIES}") diff --git a/core/Properties/acsdkProperties/testCrypto/DataPropertyCodecTest.cpp b/core/Properties/acsdkProperties/testCrypto/DataPropertyCodecTest.cpp new file mode 100644 index 0000000000..aac6bcd451 --- /dev/null +++ b/core/Properties/acsdkProperties/testCrypto/DataPropertyCodecTest.cpp @@ -0,0 +1,116 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +// Workaround for GCC < 5.x +#if (defined(__GNUC__) && (__GNUC___ < 5)) +#define RETURN_UNIQUE_PTR(x) std::move(x) +#else +#define RETURN_UNIQUE_PTR(x) (x) +#endif + +namespace alexaClientSDK { +namespace acsdkProperties { +namespace test { + +using namespace ::testing; + +using namespace ::alexaClientSDK::acsdkCodecUtils; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces::test; + +/// @private +static const DataPropertyCodec::IV TEST_IV{0x10, 0x10, 0x10, 0x10}; +/// @private +static const DataPropertyCodec::DataBlock TEST_DATA_CIPHERTEXT{0xAA, 0xAA, 0xAA, 0xAA}; +/// @private +static const DataPropertyCodec::DataBlock TEST_DATA_TAG{0x05, 0x05}; +/// @private +static const DigestInterface::DataBlock TEST_DIGEST{0xDD, 0xDD}; +/// @private +static const DigestInterface::DataBlock TEST_DIGEST2{0xEE, 0xEE}; +/// @private +static const std::string TEST_DER_DIGEST_HEX{"301630100404101010100404aaaaaaaa040205050402dddd"}; +/// @private +static const std::string TEST_DER_DIGEST2_HEX{"301630100404101010100404aaaaaaaa040205050402eeee"}; + +TEST(DataPropertyCodecTest, test_encodeDer) { + auto mockCryptoFactory = std::make_shared(); + + EXPECT_CALL(*mockCryptoFactory, _createDigest(DigestType::SHA_256)) + .WillOnce(Invoke([](DigestType type) -> std::unique_ptr { + auto mockDigest = std::unique_ptr(new MockDigest); + EXPECT_CALL(*mockDigest, _process(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockDigest, _finalize(_)).WillOnce(Invoke([](DigestInterface::DataBlock& res) -> bool { + res.insert(res.end(), TEST_DIGEST.begin(), TEST_DIGEST.end()); + return true; + })); + return RETURN_UNIQUE_PTR(mockDigest); + })); + + std::vector derEncoded; + ASSERT_TRUE(DataPropertyCodec::encode(mockCryptoFactory, TEST_IV, TEST_DATA_CIPHERTEXT, TEST_DATA_TAG, derEncoded)); + + std::string hexDer; + EXPECT_TRUE(encodeHex(derEncoded, hexDer)); + ASSERT_EQ(TEST_DER_DIGEST_HEX, hexDer); +} + +TEST(DataPropertyCodecTest, test_decodeDer) { + auto mockCryptoFactory = std::make_shared(); + + EXPECT_CALL(*mockCryptoFactory, _createDigest(DigestType::SHA_256)) + .WillRepeatedly(Invoke([](DigestType type) -> std::unique_ptr { + auto mockDigest = std::unique_ptr(new MockDigest); + EXPECT_CALL(*mockDigest, _process(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockDigest, _finalize(_)).WillOnce(Invoke([](DigestInterface::DataBlock& res) -> bool { + res.insert(res.end(), TEST_DIGEST2.begin(), TEST_DIGEST2.end()); + return true; + })); + return RETURN_UNIQUE_PTR(mockDigest); + })); + + DataPropertyCodec::IV dataKeyIV; + DataPropertyCodec::DataBlock dataKeyCiphertext; + DataPropertyCodec::Tag dataKeyTag; + DataPropertyCodec::DataBlock digestDecoded, digestActual; + + DataPropertyCodec::DataBlock derEncoded; + decodeHex(TEST_DER_DIGEST_HEX, derEncoded); + ASSERT_TRUE(DataPropertyCodec::decode( + mockCryptoFactory, derEncoded, dataKeyIV, dataKeyCiphertext, dataKeyTag, digestDecoded, digestActual)); + + ASSERT_EQ(TEST_IV, dataKeyIV); + ASSERT_EQ(TEST_DATA_CIPHERTEXT, dataKeyCiphertext); + ASSERT_EQ(TEST_DATA_TAG, dataKeyTag); + + ASSERT_TRUE(DataPropertyCodec::encode(mockCryptoFactory, dataKeyIV, dataKeyCiphertext, dataKeyTag, derEncoded)); + + std::string hexDer; + EXPECT_TRUE(encodeHex(derEncoded, hexDer)); + EXPECT_EQ(TEST_DER_DIGEST2_HEX, hexDer); +} + +} // namespace test +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/testCrypto/EncryptedPropertiesFactoryTest.cpp b/core/Properties/acsdkProperties/testCrypto/EncryptedPropertiesFactoryTest.cpp new file mode 100644 index 0000000000..bac6c6420b --- /dev/null +++ b/core/Properties/acsdkProperties/testCrypto/EncryptedPropertiesFactoryTest.cpp @@ -0,0 +1,122 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkProperties { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces::test; + +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; +using namespace ::alexaClientSDK::acsdkCrypto; +using namespace ::alexaClientSDK::acsdkPkcs11; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces::test; +using namespace ::alexaClientSDK::avsCommon::utils::configuration; + +/// @private +static const std::string JSON_TEST_CONFIG = R"( +{ + "pkcs11Module": { + "libraryPath":")" PKCS11_LIBRARY R"(", + "tokenName": ")" PKCS11_TOKEN_NAME R"(", + "userPin": ")" PKCS11_PIN R"(", + "defaultKeyName": ")" PKCS11_KEY_NAME R"(" + } +} +)"; + +/// @private +static const std::string CONFIG_URI{"component/config"}; + +/// @private +static void initConfig() { + ConfigurationNode::uninitialize(); + std::shared_ptr ss = std::make_shared(JSON_TEST_CONFIG); + EXPECT_TRUE(ConfigurationNode::initialize({ss})); +} + +/// Test that the constructor with a nullptr doesn't segfault. +TEST(EncryptedPropertiesFactoryTest, test_createNonNull) { + auto mockCryptoFactory = std::make_shared(); + auto mockKeyStore = std::make_shared(); + auto mockPropertiesFactory = std::make_shared(); + + auto factory = EncryptedPropertiesFactory::create(mockPropertiesFactory, mockCryptoFactory, mockKeyStore); + + ASSERT_NE(nullptr, factory); +} + +TEST(EncryptedPropertiesFactoryTest, test_getPropertiesEncrypted) { + initConfig(); + + auto cryptoFactory = createCryptoFactory(); + auto keyStore = createKeyStore(); + auto innerPropertiesFactory = StubPropertiesFactory::create(); + + auto factory = EncryptedPropertiesFactory::create(innerPropertiesFactory, cryptoFactory, keyStore); + ASSERT_NE(nullptr, factory); + + auto props = factory->getProperties(CONFIG_URI); + ASSERT_NE(nullptr, props); + + auto innerProperties = innerPropertiesFactory->getProperties(CONFIG_URI); + PropertiesInterface::Bytes value; + ASSERT_TRUE(innerProperties->getBytes("$acsdkEncryption$", value)); +} + +TEST(EncryptedPropertiesFactoryTest, test_createNullInnerFactory) { + auto mockCryptoFactory = std::make_shared(); + auto mockKeyStore = std::make_shared(); + + auto factory = EncryptedPropertiesFactory::create(nullptr, mockCryptoFactory, mockKeyStore); + + ASSERT_EQ(nullptr, factory); +} + +TEST(EncryptedPropertiesFactoryTest, test_createNullCryptoFactory) { + auto mockKeyStore = std::make_shared(); + auto mockPropertiesFactory = std::make_shared(); + + auto factory = EncryptedPropertiesFactory::create(mockPropertiesFactory, nullptr, mockKeyStore); + + ASSERT_EQ(nullptr, factory); +} + +TEST(EncryptedPropertiesFactoryTest, test_createNullKeyStore) { + auto mockCryptoFactory = std::make_shared(); + auto mockPropertiesFactory = std::make_shared(); + + auto factory = EncryptedPropertiesFactory::create(mockPropertiesFactory, mockCryptoFactory, nullptr); + + ASSERT_EQ(nullptr, factory); +} + +} // namespace test +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/testCrypto/EncryptedPropertiesTest.cpp b/core/Properties/acsdkProperties/testCrypto/EncryptedPropertiesTest.cpp new file mode 100644 index 0000000000..b1741167f7 --- /dev/null +++ b/core/Properties/acsdkProperties/testCrypto/EncryptedPropertiesTest.cpp @@ -0,0 +1,238 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +namespace alexaClientSDK { +namespace acsdkProperties { +namespace test { + +using namespace ::testing; +using namespace ::alexaClientSDK::acsdkCodecUtils; +using namespace ::alexaClientSDK::acsdkCrypto; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces::test; +using namespace ::alexaClientSDK::acsdkPkcs11; +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces; +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces::test; +using namespace ::alexaClientSDK::avsCommon::utils::configuration; +using namespace ::alexaClientSDK::avsCommon::sdkInterfaces::storage::test; + +/// String to identify log entries originating from this file. +/// @private +static const std::string TAG{"EncryptedPropertiesTest"}; + +/// @private +static const std::string JSON_TEST_CONFIG = R"( +{ + "pkcs11Module": { + "libraryPath":")" PKCS11_LIBRARY R"(", + "tokenName": ")" PKCS11_TOKEN_NAME R"(", + "userPin": ")" PKCS11_PIN R"(", + "defaultKeyName": ")" PKCS11_KEY_NAME R"(" + } +} +)"; + +/// @private +static const std::string COMPONENT_NAME{"component"}; +/// @private +static const std::string CONFIG_NAMESPACE{"config"}; +/// @private +static const std::string CONFIG_URI{"component/config"}; +/// @private +static const std::string KEY_PROPERTY_NAME = "$acsdkEncryption$"; + +/// @private +static void initConfig() { + ConfigurationNode::uninitialize(); + std::shared_ptr ss = std::make_shared(JSON_TEST_CONFIG); + EXPECT_TRUE(ConfigurationNode::initialize({ss})); +} + +TEST(EncryptedPropertiesTest, test_create) { + initConfig(); + + auto cryptoFactory = createCryptoFactory(); + auto keyStore = createKeyStore(); + auto innerStorage = StubMiscStorage::create(); + auto innerProperties = MiscStorageProperties::create(innerStorage, CONFIG_URI, COMPONENT_NAME, CONFIG_NAMESPACE); + ASSERT_NE(nullptr, innerProperties); + auto properties = EncryptedProperties::create(CONFIG_URI, innerProperties, cryptoFactory, keyStore); + ASSERT_NE(nullptr, properties); + + PropertiesInterface::Bytes value; + ASSERT_TRUE(innerProperties->getBytes(KEY_PROPERTY_NAME, value)); + ASSERT_FALSE(value.empty()); +} + +TEST(EncryptedPropertiesTest, test_createUpgradeEncryptionString) { + initConfig(); + + auto cryptoFactory = createCryptoFactory(); + auto keyStore = createKeyStore(); + auto innerStorage = StubMiscStorage::create(); + auto innerProperties = MiscStorageProperties::create(innerStorage, CONFIG_URI, COMPONENT_NAME, CONFIG_NAMESPACE); + ASSERT_NE(nullptr, innerProperties); + + std::string plaintextString = R"({"json":"text"})"; + EncryptedProperties::Bytes ciphertext; + + ASSERT_TRUE(innerProperties->putString("StringKey", plaintextString)); + + std::string decryptedString; + ASSERT_TRUE(innerProperties->getString("StringKey", decryptedString)); + + ACSDK_DEBUG0(LX("UpgradingEncryption")); + + auto properties = EncryptedProperties::create(CONFIG_URI, innerProperties, cryptoFactory, keyStore); + ASSERT_NE(nullptr, properties); + + ACSDK_DEBUG0(LX("UpgradedEncryption")); + ASSERT_TRUE(innerProperties->getBytes(KEY_PROPERTY_NAME, ciphertext)); + ACSDK_DEBUG0(LX("keyProperty").d("data", ciphertext)); + + ciphertext.clear(); + ACSDK_DEBUG0(LX("loading encrypted key value")); + + ASSERT_TRUE(innerProperties->getBytes("StringKey", ciphertext)); + ACSDK_DEBUG0(LX("stringKeyEncrypted").d("data", ciphertext)); + + ACSDK_DEBUG0(LX("loading decrypted key value")); + + ASSERT_TRUE(properties->getString("StringKey", decryptedString)); + ACSDK_DEBUG0(LX("stringKeyPlaintext").d("data", decryptedString)); + EXPECT_EQ(plaintextString, decryptedString); + + PropertiesInterface::Bytes encryptedString; + + ASSERT_TRUE(innerProperties->getBytes("StringKey", encryptedString)); + + EXPECT_NE(plaintextString, (std::string{encryptedString.data(), encryptedString.data() + encryptedString.size()})); +} + +TEST(EncryptedPropertiesTest, test_createUpgradeEncryptionBytes) { + initConfig(); + + auto cryptoFactory = createCryptoFactory(); + auto keyStore = createKeyStore(); + auto innerPropertiesFactory = StubPropertiesFactory::create(); + auto innerProperties = innerPropertiesFactory->getProperties(CONFIG_URI); + ASSERT_NE(nullptr, innerProperties); + + PropertiesInterface::Bytes plaintextBytes{0, 1, 2}; + + ASSERT_TRUE(innerProperties->putBytes("BytesKey", plaintextBytes)); + + auto properties = EncryptedProperties::create(CONFIG_URI, innerProperties, cryptoFactory, keyStore); + ASSERT_NE(nullptr, properties); + + PropertiesInterface::Bytes decryptedBytes; + ASSERT_TRUE(properties->getBytes("BytesKey", decryptedBytes)); + EXPECT_EQ(plaintextBytes, decryptedBytes); + + PropertiesInterface::Bytes encryptedBytes; + + ASSERT_TRUE(innerProperties->getBytes("BytesKey", encryptedBytes)); + + EXPECT_NE(plaintextBytes, encryptedBytes); +} + +TEST(EncryptedPropertiesTest, test_createNullInnerProperties) { + auto mockCryptoFacotry = std::make_shared(); + auto mockKeyStore = std::make_shared(); + + auto properties = EncryptedProperties::create(CONFIG_URI, nullptr, mockCryptoFacotry, mockKeyStore); + ASSERT_EQ(nullptr, properties); +} + +TEST(EncryptedPropertiesTest, test_createNullCryptoFactory) { + auto mockKeyStore = std::make_shared(); + auto mockProperties = std::make_shared(); + + auto properties = EncryptedProperties::create(CONFIG_URI, mockProperties, nullptr, mockKeyStore); + ASSERT_EQ(nullptr, properties); +} + +TEST(EncryptedPropertiesTest, test_createNullKeyStore) { + auto mockCryptoFacotry = std::make_shared(); + auto mockProperties = std::make_shared(); + + auto properties = EncryptedProperties::create(CONFIG_URI, mockProperties, mockCryptoFacotry, nullptr); + ASSERT_EQ(nullptr, properties); +} + +TEST(EncryptedPropertiesTest, test_encryptPut) { + initConfig(); + + auto cryptoFactory = createCryptoFactory(); + ASSERT_NE(nullptr, cryptoFactory); + auto keyStore = createKeyStore(); + ASSERT_NE(nullptr, keyStore); + + auto stubPropsFactory = StubPropertiesFactory::create(); + auto innerProps = stubPropsFactory->getProperties("test/test"); + + auto properties = EncryptedProperties::create(CONFIG_URI, innerProps, cryptoFactory, keyStore); + ASSERT_NE(nullptr, properties); + + PropertiesInterface::Bytes tmp; + ASSERT_TRUE(innerProps->getBytes("$acsdkEncryption$", tmp)); + + ASSERT_FALSE(innerProps->getBytes("property1", tmp)); + ASSERT_TRUE(properties->putString("property1", "some plaintext value")); + ASSERT_TRUE(innerProps->getBytes("property1", tmp)); +} + +TEST(EncryptedPropertiesTest, test_reopenEncryptedProperties) { + initConfig(); + + auto cryptoFactory = createCryptoFactory(); + ASSERT_NE(nullptr, cryptoFactory); + auto keyStore = createKeyStore(); + ASSERT_NE(nullptr, keyStore); + + auto stubPropsFactory = StubPropertiesFactory::create(); + auto innerProps = stubPropsFactory->getProperties("test/test"); + + auto properties = EncryptedProperties::create(CONFIG_URI, innerProps, cryptoFactory, keyStore); + ASSERT_NE(nullptr, properties); + ASSERT_TRUE(properties->putString("property1", "some plaintext value")); + properties.reset(); + + properties = EncryptedProperties::create(CONFIG_URI, innerProps, cryptoFactory, keyStore); + ASSERT_NE(nullptr, properties); + std::string value; + ASSERT_TRUE(properties->getString("property1", value)); + ASSERT_EQ("some plaintext value", value); +} + +} // namespace test +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkProperties/testCrypto/EncryptionKeyPropertyCodecTest.cpp b/core/Properties/acsdkProperties/testCrypto/EncryptionKeyPropertyCodecTest.cpp new file mode 100644 index 0000000000..a890e4cb95 --- /dev/null +++ b/core/Properties/acsdkProperties/testCrypto/EncryptionKeyPropertyCodecTest.cpp @@ -0,0 +1,160 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +// Workaround for GCC < 5.x +#if (defined(__GNUC__) && (__GNUC___ < 5)) +#define RETURN_UNIQUE_PTR(x) std::move(x) +#else +#define RETURN_UNIQUE_PTR(x) (x) +#endif + +namespace alexaClientSDK { +namespace acsdkProperties { +namespace test { + +using namespace ::testing; + +using namespace ::alexaClientSDK::acsdkPropertiesInterfaces; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces; +using namespace ::alexaClientSDK::acsdkCryptoInterfaces::test; +using namespace ::alexaClientSDK::acsdkCodecUtils; + +// Test content for decoding. +static const std::string DER_ENCODED_HEX = + "3024301e0c076d61696e4b657904030303030404101010100404aaaaaaaa040205050402dddd"; + +TEST(EncryptionKeyPropertyTest, test_encodeDer) { + auto mockCryptoFactory = std::make_shared(); + + EXPECT_CALL(*mockCryptoFactory, _createDigest(DigestType::SHA_256)) + .WillOnce(Invoke([](DigestType type) -> std::unique_ptr { + auto mockDigest = std::unique_ptr(new MockDigest); + EXPECT_CALL(*mockDigest, _process(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockDigest, _finalize(_)).WillOnce(Invoke([](DigestInterface::DataBlock& res) -> bool { + DigestInterface::DataBlock digest{0xDD, 0xDD}; + res.insert(res.end(), digest.begin(), digest.end()); + return true; + })); + return RETURN_UNIQUE_PTR(mockDigest); + })); + + std::string mainKeyAlias = "mainKey"; + KeyStoreInterface::KeyChecksum mainKeyChecksum{0x03, 0x03, 0x03}; + AlgorithmType dataKeyAlgorithm = AlgorithmType::AES_256_GCM; + KeyStoreInterface::IV dataKeyIV{ + 0x10, + 0x10, + 0x10, + 0x10, + }; + KeyStoreInterface::DataBlock dataKeyCiphertext{0xAA, 0xAA, 0xAA, 0xAA}; + KeyStoreInterface::Tag dataKeyTag{0x05, 0x05}; + AlgorithmType dataAlgorithm = AlgorithmType::AES_256_GCM; + PropertiesInterface::Bytes derEncoded; + ASSERT_TRUE(EncryptionKeyPropertyCodec::encode( + mockCryptoFactory, + mainKeyAlias, + mainKeyChecksum, + dataKeyAlgorithm, + dataKeyIV, + dataKeyCiphertext, + dataKeyTag, + dataAlgorithm, + derEncoded)); + + std::string hexString; + ASSERT_TRUE(encodeHex(derEncoded, hexString)); + ASSERT_EQ(DER_ENCODED_HEX, hexString); +} + +TEST(EncryptionKeyPropertyTest, test_decodeDer) { + auto mockCryptoFactory = std::make_shared(); + + EXPECT_CALL(*mockCryptoFactory, _createDigest(DigestType::SHA_256)) + .WillRepeatedly(Invoke([](DigestType type) -> std::unique_ptr { + auto mockDigest = std::unique_ptr(new MockDigest); + EXPECT_CALL(*mockDigest, _process(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mockDigest, _finalize(_)).WillOnce(Invoke([](std::vector& res) -> bool { + DigestInterface::DataBlock digest{0xEE, 0xEE}; + res.insert(res.end(), digest.begin(), digest.end()); + return true; + })); + return RETURN_UNIQUE_PTR(mockDigest); + })); + + std::string mainKeyAlias; + KeyStoreInterface::KeyChecksum mainKeyChecksum; + AlgorithmType dataKeyAlgorithm; + KeyStoreInterface::IV dataKeyIV; + KeyStoreInterface::DataBlock dataKeyCiphertext; + KeyStoreInterface::Tag dataKeyTag; + AlgorithmType dataAlgorithm; + DigestInterface::DataBlock digestDecoded, digestActual; + + PropertiesInterface::Bytes derEncoded; + + ASSERT_TRUE(decodeHex(DER_ENCODED_HEX, derEncoded)); + ASSERT_TRUE(EncryptionKeyPropertyCodec::decode( + mockCryptoFactory, + derEncoded, + mainKeyAlias, + mainKeyChecksum, + dataKeyAlgorithm, + dataKeyIV, + dataKeyCiphertext, + dataKeyTag, + dataAlgorithm, + digestDecoded, + digestActual)); + + ASSERT_EQ("mainKey", mainKeyAlias); + ASSERT_EQ((KeyStoreInterface::KeyChecksum{0x03, 0x03, 0x03}), mainKeyChecksum); + ASSERT_EQ(AlgorithmType::AES_256_GCM, dataKeyAlgorithm); + ASSERT_EQ( + (KeyStoreInterface::IV{ + 0x10, + 0x10, + 0x10, + 0x10, + }), + dataKeyIV); + ASSERT_EQ((KeyStoreInterface::DataBlock{0xAA, 0xAA, 0xAA, 0xAA}), dataKeyCiphertext); + ASSERT_EQ((KeyStoreInterface::Tag{0x05, 0x05}), dataKeyTag); + ASSERT_EQ(AlgorithmType::AES_256_GCM, dataAlgorithm); + + ASSERT_TRUE(EncryptionKeyPropertyCodec::encode( + mockCryptoFactory, + mainKeyAlias, + mainKeyChecksum, + dataKeyAlgorithm, + dataKeyIV, + dataKeyCiphertext, + dataKeyTag, + dataAlgorithm, + derEncoded)); +} + +} // namespace test +} // namespace acsdkProperties +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkPropertiesInterfaces/CMakeLists.txt b/core/Properties/acsdkPropertiesInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..09f3844763 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(acsdkPropertiesInterfaces LANGUAGES CXX) + +add_library(acsdkPropertiesInterfaces INTERFACE) +target_include_directories(acsdkPropertiesInterfaces INTERFACE include) + +# install target +asdk_install_interface() + +add_subdirectory("test") diff --git a/core/Properties/acsdkPropertiesInterfaces/doc/Namespaces.dox b/core/Properties/acsdkPropertiesInterfaces/doc/Namespaces.dox new file mode 100644 index 0000000000..60a7d09a24 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/doc/Namespaces.dox @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \namespace ::alexaClientSDK::acsdkPropertiesInterfaces + * \brief Properties API Interfaces and Data Types. + * \ingroup PropertiesAPI + */ diff --git a/core/Properties/acsdkPropertiesInterfaces/doc/PropertiesAPI.dox b/core/Properties/acsdkPropertiesInterfaces/doc/PropertiesAPI.dox new file mode 100644 index 0000000000..47098f32aa --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/doc/PropertiesAPI.dox @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \defgroup PropertiesAPI + * \defgroup PropertiesAPI Properties API + * @brief Interfaces for accessing persistent configuration. + * + * Properties API provides interfaces for accessing persistent configuration. The API doesn't mandate how the + * configuration is stored, but assumes that it can be accessed as a group of containers. A single container can be + * identified by configuration URI (Uniform ResourceLocator). + * + * Usage Example: + * \code{.cpp} + * auto properties = propertiesFactory->getProperties("componentName/tableName"); + * properties->putString("propertyName", "stringValue"); + * \endcode + * + * ACSDK provides reference implementation of \ref PropertiesAPI to use with \ref MiscStorageInterface and to store + * properties values in encrypted form. See \ref PropertiesIMPL for documentation on reference implementation. + * + * \sa PropertiesIMPL + * \sa \ref acsdkPropertiesInterfaces Namespace + * \sa \ref acsdkPropertiesInterfaces::test Namespace + */ diff --git a/core/Properties/acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces/PropertiesFactoryInterface.h b/core/Properties/acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces/PropertiesFactoryInterface.h new file mode 100644 index 0000000000..531e03fc3e --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces/PropertiesFactoryInterface.h @@ -0,0 +1,68 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIESINTERFACES_PROPERTIESFACTORYINTERFACE_H_ +#define ACSDKPROPERTIESINTERFACES_PROPERTIESFACTORYINTERFACE_H_ + +#include +#include + +#include + +namespace alexaClientSDK { +//! \addtogroup PropertiesAPI +//! @{ +namespace acsdkPropertiesInterfaces { + +//! Factory interface to component properties. +/** + * This interface provide a way to construct component-specific key-value storage. The storage is identified by + * configuration URI (Uniform Resource Identifier) to disambiguate properties between different components. The format + * of configuration URI is implementation specific, but must conform to RFC3986. + * + * Application may operate with a single or multiple instances of this interface, that use different implementation, or + * provide access to different physical resources for access control. + * + * \ingroup PropertiesAPI + * \sa PropertiesIMPL + */ +class PropertiesFactoryInterface { +public: + /** + * Destructor. + */ + virtual ~PropertiesFactoryInterface() = default; + + //! Create properties interface for a given component and namespace. + /** + * This method creates interface to access component specific configuration. The implementation maps URI into + * implementation-specific configuration container, and URI can be treated as a file location, database or table + * name, or region in non-volatile memory. + * + * The method may return the same or different instances for the same configuration URI, but if different instances + * are returned, all of them must provide access to the same configuration container. + * + * @param uri Resource URI. The format must conform to RFC3986, but handling is implementation-specific. + * + * @return Reference to properties adapter or nullptr on error. + */ + virtual std::shared_ptr getProperties(const std::string& uri) noexcept = 0; +}; + +} // namespace acsdkPropertiesInterfaces +//! @} +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIESINTERFACES_PROPERTIESFACTORYINTERFACE_H_ diff --git a/core/Properties/acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces/PropertiesInterface.h b/core/Properties/acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces/PropertiesInterface.h new file mode 100644 index 0000000000..75028aa670 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces/PropertiesInterface.h @@ -0,0 +1,142 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIESINTERFACES_PROPERTIESINTERFACE_H_ +#define ACSDKPROPERTIESINTERFACES_PROPERTIESINTERFACE_H_ + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkPropertiesInterfaces { + +//! This class provides an interface to a simple key/value container. +/** + * This interface is obtained through @c PropertiesFactoryInterface factory, which handles disambiguation of properties + * namespace. + * + * The implementation must do best effort for data consistency when handling data update errors for each property. If + * it is possible, the value of property shall be either left intact, or deleted if the data corruption is unavoidable. + * + * \ingroup PropertiesAPI + * @sa PropertiesFactoryInterface + */ +class PropertiesInterface { +public: + /// \brief Bytes data type. + /// This data type represent a continuous byte array. + typedef std::vector Bytes; + + //! Destructor. + virtual ~PropertiesInterface() noexcept = default; + + //! Method to load string value from configuration. + /** + * This method loads string value from configuration. If the value in the storage is not a string, the method + * behaviour is undefined. + * + * @param[in] key Configuration key. + * @param[out] value Result container. If the method completes successfully, @a value will contain loaded value. + * Otherwise contents of @a value is unmodified. + * @return True if value has been loaded, false otherwise. + * + * @sa #putString + */ + virtual bool getString(const std::string& key, std::string& value) noexcept = 0; + + //! Method to store string value into configuration. + /** + * This method stores string value into configuration. If there is an existing value for the the same key, the value + * is overwritten. + * + * If operation fails, the implementation shall make a best effort for either keeping value unmodified, or clear it + * to prevent data corruption. Other properties shall not be impacted in case of an error. + * + * @param[in] key Configuration key. + * @param[in] value Value to store. + * @return True if value has been stored, false otherwise. If this method returns false, the value may stay + * unchanged, or lost. + * + * @sa #getString + */ + virtual bool putString(const std::string& key, const std::string& value) noexcept = 0; + + //! Method to load binary value from configuration. + /** + * This method loads binary value from configuration. If the value in the storage is not binary data, the method + * behaviour is undefined. + * + * @param[in] key Configuration key. + * @param[out] value If the method completes successfully, @a value will contain loaded value. + * Otherwise contents of @a value is unmodified. + * @return True if value has been loaded, false otherwise. + * @sa #putBytes + */ + virtual bool getBytes(const std::string& key, Bytes& value) noexcept = 0; + + //! Method to store binary value into configuration. + /** + * This method stores binary value into configuration. If there is an existing value for the + * the same key, the value is overwritten. + * + * If operation fails, the implementation shall make a best effort for either keeping value unmodified, or clear it + * to prevent data corruption. Other properties shall not be impacted in case of an error. + * + * @param[in] key Configuration key. + * @param[in] value Value to store. + * @return True if value has been stored, false otherwise. If this method returns false, the value may stay + * unchanged, or lost. + * + * @sa #getBytes + */ + virtual bool putBytes(const std::string& key, const Bytes& value) noexcept = 0; + + //! Method to inspect existing properties. + /** + * This method provides a set of known property keys from a configuration container. + * + * @param[out] keys Container for property keys. If method completes successfully, \a keys will contain all property + * names. On error, the contents of \a keys is undefined. + * + * @return True if operation succeeds, false otherwise. + */ + virtual bool getKeys(std::unordered_set& keys) noexcept = 0; + + //! Removes a property with a given name. + /** + * This method removes a property with a given name from a configuration container. If the property doesn't exist, + * the method succeeds. + * + * @param[in] key Configuration key to remove. + * @return True if the key has been removed or didn't exist. In case of error, false is + * returned and the state of configuration container is undefined. + */ + virtual bool remove(const std::string& key) noexcept = 0; + + //! Removes all properties from a configuration container. + /** + * This method removes all properties from a configuration container. + * + * @return True if the container has been cleared. In case of error, false is returned, + * and the contents of container is undefined. + */ + virtual bool clear() noexcept = 0; +}; + +} // namespace acsdkPropertiesInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIESINTERFACES_PROPERTIESINTERFACE_H_ diff --git a/core/Properties/acsdkPropertiesInterfaces/test/CMakeLists.txt b/core/Properties/acsdkPropertiesInterfaces/test/CMakeLists.txt new file mode 100644 index 0000000000..8195470327 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/test/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(acsdkPropertiesInterfacesTestLib LANGUAGES CXX) + +add_subdirectory("src") diff --git a/core/Properties/acsdkPropertiesInterfaces/test/doc/Namespaces.dox b/core/Properties/acsdkPropertiesInterfaces/test/doc/Namespaces.dox new file mode 100644 index 0000000000..c5e35754b6 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/test/doc/Namespaces.dox @@ -0,0 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \namespace ::alexaClientSDK::acsdkPropertiesInterfaces::test + * \brief Test utilities for \ref PropertiesAPI + * \ingroup PropertiesAPI + * \sa PropertiesAPI. + */ diff --git a/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockProperties.h b/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockProperties.h new file mode 100644 index 0000000000..71b8d8cecd --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockProperties.h @@ -0,0 +1,88 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIESINTERFACES_TEST_MOCKPROPERTIES_H_ +#define ACSDKPROPERTIESINTERFACES_TEST_MOCKPROPERTIES_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkPropertiesInterfaces { +namespace test { + +//! Mock class for @c PropertiesInterface. +/** + * \ingroup PropertiesAPI + */ +class MockProperties : public PropertiesInterface { +public: + /// @name PropertiesInterface methods + ///@{ + bool getString(const std::string& key, std::string& value) noexcept override; + bool putString(const std::string& key, const std::string& value) noexcept override; + bool getBytes(const std::string& key, Bytes& value) noexcept override; + bool putBytes(const std::string& key, const Bytes& value) noexcept override; + bool getKeys(std::unordered_set& keys) noexcept override; + bool remove(const std::string& key) noexcept override; + bool clear() noexcept override; + ///@} + /// @name PropertiesInterface stub methods for 1.8.x gmock + ///@{ + MOCK_METHOD2(_getString, bool(const std::string&, std::string&)); + MOCK_METHOD2(_putString, bool(const std::string&, const std::string&)); + MOCK_METHOD2(_getBytes, bool(const std::string&, Bytes&)); + MOCK_METHOD2(_putBytes, bool(const std::string&, const Bytes&)); + MOCK_METHOD1(_getKeys, bool(std::unordered_set&)); + MOCK_METHOD1(_remove, bool(const std::string&)); + MOCK_METHOD0(_clear, bool()); + ///@} +}; + +inline bool MockProperties::getString(const std::string& key, std::string& value) noexcept { + return _getString(key, value); +} + +inline bool MockProperties::putString(const std::string& key, const std::string& value) noexcept { + return _putString(key, value); +} + +inline bool MockProperties::getBytes(const std::string& key, Bytes& value) noexcept { + return _getBytes(key, value); +} + +inline bool MockProperties::putBytes(const std::string& key, const Bytes& value) noexcept { + return _putBytes(key, value); +} + +inline bool MockProperties::getKeys(std::unordered_set& keys) noexcept { + return _getKeys(keys); +} + +inline bool MockProperties::remove(const std::string& key) noexcept { + return _remove(key); +} + +inline bool MockProperties::clear() noexcept { + return _clear(); +} + +} // namespace test +} // namespace acsdkPropertiesInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIESINTERFACES_TEST_MOCKPROPERTIES_H_ diff --git a/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockPropertiesFactory.h b/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockPropertiesFactory.h new file mode 100644 index 0000000000..a9fe371096 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockPropertiesFactory.h @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIESINTERFACES_TEST_MOCKPROPERTIESFACTORY_H_ +#define ACSDKPROPERTIESINTERFACES_TEST_MOCKPROPERTIESFACTORY_H_ + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkPropertiesInterfaces { +namespace test { + +//! Mock class for @c PropertiesFactoryInterface. +/** + * \ingroup PropertiesAPI + */ +class MockPropertiesFactory : public PropertiesFactoryInterface { +public: + /// @name PropertiesFactoryInterface methods + ///@{ + std::shared_ptr getProperties(const std::string& configUri) noexcept override; + ///@} + /// @name PropertiesFactoryInterface stub methods for 1.8.x gmock + ///@{ + MOCK_METHOD1(_getProperties, std::shared_ptr(const std::string&)); + ///@} +}; + +inline std::shared_ptr MockPropertiesFactory::getProperties( + const std::string& configUri) noexcept { + return _getProperties(configUri); +} + +} // namespace test +} // namespace acsdkPropertiesInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIESINTERFACES_TEST_MOCKPROPERTIESFACTORY_H_ diff --git a/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/StubProperties.h b/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/StubProperties.h new file mode 100644 index 0000000000..3a99f3e487 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/StubProperties.h @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIESINTERFACES_TEST_STUBPROPERTIES_H_ +#define ACSDKPROPERTIESINTERFACES_TEST_STUBPROPERTIES_H_ + +#include +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkPropertiesInterfaces { +namespace test { + +//! In-memory stub implementation of @c PropertiesInterface. +/** + * This class provides in-memory implementation of \c PropertiesInterface. Users can create instances of this class + * by using \ref StubPropertiesFactory. + * + * \sa StubPropertiesFactory. + * \ingroup PropertiesAPI + */ +class StubProperties : public PropertiesInterface { +public: + /// @name PropertiesFactoryInterface functions. + /// @{ + bool getString(const std::string& key, std::string& value) noexcept override; + bool putString(const std::string& key, const std::string& value) noexcept override; + bool getBytes(const std::string& key, Bytes& value) noexcept override; + bool putBytes(const std::string& key, const Bytes& value) noexcept override; + bool getKeys(std::unordered_set& keys) noexcept override; + bool remove(const std::string& key) noexcept override; + bool clear() noexcept override; + ///@} +private: + friend class StubPropertiesFactory; + + StubProperties(const std::shared_ptr& owner, const std::string& configUri) noexcept; + + /// Provides fully qualified name in the parent's container + std::string createFullyQualifiedName(const std::string& keyName) noexcept; + + /// Provides prefix for container-owned keys. + std::string createKeyPrefix() noexcept; + + /// Reference of owning class that contains all the data. + const std::shared_ptr m_owner; + + /// Configuration URI. + const std::string m_configUri; +}; + +} // namespace test +} // namespace acsdkPropertiesInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIESINTERFACES_TEST_STUBPROPERTIES_H_ diff --git a/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/StubPropertiesFactory.h b/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/StubPropertiesFactory.h new file mode 100644 index 0000000000..e3edbf4d64 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/StubPropertiesFactory.h @@ -0,0 +1,68 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKPROPERTIESINTERFACES_TEST_STUBPROPERTIESFACTORY_H_ +#define ACSDKPROPERTIESINTERFACES_TEST_STUBPROPERTIESFACTORY_H_ + +#include +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkPropertiesInterfaces { +namespace test { + +//! In-memory stub implementation of @c PropertiesFactoryInterface. +/** + * In memory implementation for @c PropertiesFactoryInterface. This class is not thread safe. + * + * \ingroup PropertiesAPI + */ +class StubPropertiesFactory + : public PropertiesFactoryInterface + , public std::enable_shared_from_this { +public: + ///! Creates new factory instance. + /** + * This method provides a new property factory instance. This instance has own in-memory storage for all + * properties. + * + * @return New object instance. + */ + static std::shared_ptr create() noexcept; + + /// @name PropertiesFactoryInterface functions. + /// @{ + std::shared_ptr getProperties(const std::string& configUri) noexcept override; + ///@} + +private: + friend class StubProperties; + + /// Private constructor + StubPropertiesFactory() noexcept; + + /// Container to keep stored values. The format of the key is "configUri/key". + std::unordered_map> m_storage; +}; + +} // namespace test +} // namespace acsdkPropertiesInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKPROPERTIESINTERFACES_TEST_STUBPROPERTIESFACTORY_H_ diff --git a/core/Properties/acsdkPropertiesInterfaces/test/src/CMakeLists.txt b/core/Properties/acsdkPropertiesInterfaces/test/src/CMakeLists.txt new file mode 100644 index 0000000000..2865f89306 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/test/src/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +if (BUILD_TESTING) + add_library(acsdkPropertiesInterfacesTestLib STATIC + StubProperties.cpp + StubPropertiesFactory.cpp) + target_compile_definitions(acsdkPropertiesInterfacesTestLib PRIVATE ACSDK_LOG_MODULE=acsdkPropertiesInterfacesTest) + target_include_directories(acsdkPropertiesInterfacesTestLib PUBLIC "../include") + target_link_libraries(acsdkPropertiesInterfacesTestLib PRIVATE acsdkPropertiesInterfaces acsdkCodecUtils) +endif() diff --git a/core/Properties/acsdkPropertiesInterfaces/test/src/StubProperties.cpp b/core/Properties/acsdkPropertiesInterfaces/test/src/StubProperties.cpp new file mode 100644 index 0000000000..e9910ea573 --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/test/src/StubProperties.cpp @@ -0,0 +1,118 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkPropertiesInterfaces { +namespace test { + +/// @private +static constexpr char TYPE_BIN = 'b'; +/// @private +static constexpr char TYPE_STR = 's'; + +std::string StubProperties::createFullyQualifiedName(const std::string& keyName) noexcept { + return createKeyPrefix() + keyName; +} + +std::string StubProperties::createKeyPrefix() noexcept { + return m_configUri + "/"; +} + +StubProperties::StubProperties( + const std::shared_ptr& owner, + const std::string& configUri) noexcept : + m_owner{owner}, + m_configUri{configUri} { +} + +bool StubProperties::getString(const std::string& key, std::string& value) noexcept { + std::string keyStr = createFullyQualifiedName(key); + + auto it = m_owner->m_storage.find(keyStr); + if (m_owner->m_storage.end() == it) { + return false; + } + if (TYPE_STR != it->second.first) { + return false; + } + value.assign(it->second.second.data(), it->second.second.data() + it->second.second.size()); + return true; +} + +bool StubProperties::putString(const std::string& key, const std::string& value) noexcept { + std::string keyStr = createFullyQualifiedName(key); + Bytes bin(value.c_str(), value.c_str() + value.size()); + m_owner->m_storage[keyStr] = std::pair(TYPE_STR, bin); + return true; +} + +bool StubProperties::getBytes(const std::string& key, Bytes& value) noexcept { + std::string keyStr = createFullyQualifiedName(key); + + auto it = m_owner->m_storage.find(keyStr); + if (m_owner->m_storage.end() == it) { + return false; + } + if (TYPE_BIN != it->second.first) { + return false; + } + value = it->second.second; + return true; +} + +bool StubProperties::putBytes(const std::string& key, const Bytes& value) noexcept { + std::string keyStr = createFullyQualifiedName(key); + m_owner->m_storage[keyStr] = std::pair(TYPE_BIN, value); + return true; +} + +bool StubProperties::remove(const std::string& key) noexcept { + std::string keyStr = createFullyQualifiedName(key); + m_owner->m_storage.erase(keyStr); + return true; +} + +bool StubProperties::getKeys(std::unordered_set& keys) noexcept { + std::string keyPrefix = createKeyPrefix(); + size_t keyLen = keyPrefix.length(); + for (const auto& it : m_owner->m_storage) { + const std::string& key = it.first; + if (key.compare(0, keyLen, keyPrefix) == 0) { + std::string targetKey = key.substr(keyLen); + keys.insert(targetKey); + } + } + return true; +} + +bool StubProperties::clear() noexcept { + std::string keyPrefix = createKeyPrefix(); + size_t keyLen = keyPrefix.length(); + for (auto it = m_owner->m_storage.begin(); it != m_owner->m_storage.end();) { + const std::string& key = it->first; + if (key.compare(0, keyLen, keyPrefix) == 0) { + it = m_owner->m_storage.erase(it); + } else { + it++; + } + } + return true; +} + +} // namespace test +} // namespace acsdkPropertiesInterfaces +} // namespace alexaClientSDK diff --git a/core/Properties/acsdkPropertiesInterfaces/test/src/StubPropertiesFactory.cpp b/core/Properties/acsdkPropertiesInterfaces/test/src/StubPropertiesFactory.cpp new file mode 100644 index 0000000000..2ff8146aba --- /dev/null +++ b/core/Properties/acsdkPropertiesInterfaces/test/src/StubPropertiesFactory.cpp @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkPropertiesInterfaces { +namespace test { + +std::shared_ptr StubPropertiesFactory::create() noexcept { + return std::shared_ptr(new StubPropertiesFactory); +} + +StubPropertiesFactory::StubPropertiesFactory() noexcept { +} + +std::shared_ptr StubPropertiesFactory::getProperties(const std::string& configUri) noexcept { + return std::shared_ptr(new StubProperties(shared_from_this(), configUri)); +} + +} // namespace test +} // namespace acsdkPropertiesInterfaces +} // namespace alexaClientSDK diff --git a/core/acsdkAlexaEventProcessedNotifierInterfaces/include/acsdkAlexaEventProcessedNotifierInterfaces/AlexaEventProcessedNotifierInterface.h b/core/acsdkAlexaEventProcessedNotifierInterfaces/include/acsdkAlexaEventProcessedNotifierInterfaces/AlexaEventProcessedNotifierInterface.h index 09962ecf51..939c116ad8 100644 --- a/core/acsdkAlexaEventProcessedNotifierInterfaces/include/acsdkAlexaEventProcessedNotifierInterfaces/AlexaEventProcessedNotifierInterface.h +++ b/core/acsdkAlexaEventProcessedNotifierInterfaces/include/acsdkAlexaEventProcessedNotifierInterfaces/AlexaEventProcessedNotifierInterface.h @@ -16,7 +16,7 @@ #ifndef ACSDKALEXAEVENTPROCESSEDNOTIFIERINTERFACES_ALEXAEVENTPROCESSEDNOTIFIERINTERFACE_H_ #define ACSDKALEXAEVENTPROCESSEDNOTIFIERINTERFACES_ALEXAEVENTPROCESSEDNOTIFIERINTERFACE_H_ -#include +#include #include namespace alexaClientSDK { diff --git a/KWD/Sensory/CMakeLists.txt b/core/acsdkCodecUtils/CMakeLists.txt similarity index 55% rename from KWD/Sensory/CMakeLists.txt rename to core/acsdkCodecUtils/CMakeLists.txt index c24403ad7e..9457dd4297 100644 --- a/KWD/Sensory/CMakeLists.txt +++ b/core/acsdkCodecUtils/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -project(SENSORY LANGUAGES CXX) - -include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +project(acsdkCodecUtils LANGUAGES CXX) add_subdirectory("src") add_subdirectory("test") diff --git a/core/acsdkCodecUtils/doc/CodecUtils.dox b/core/acsdkCodecUtils/doc/CodecUtils.dox new file mode 100644 index 0000000000..d10fc6781c --- /dev/null +++ b/core/acsdkCodecUtils/doc/CodecUtils.dox @@ -0,0 +1,34 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * \defgroup CodecUtils Binary Codec Utilities + * @brief Non-cryptographic binary encoders and decoders + * + * This module includes functions for non-cryptographic binary encoders and decoders. + * + * \sa \ref acsdkCodecUtils Namespace + * \sa \ref acsdkCodecUtils::test Namespace + * + * \namespace ::alexaClientSDK::acsdkCodecUtils + * \brief Binary codec utilities. + * \ingroup CodecUtils + * \sa CodecUtils + * + * \namespace ::alexaClientSDK::acsdkCodecUtils::test + * \brief Test cases for \ref CodecUtils + * \ingroup CodecUtils + * \sa CodecUtils + */ diff --git a/core/acsdkCodecUtils/include/acsdkCodecUtils/Base64.h b/core/acsdkCodecUtils/include/acsdkCodecUtils/Base64.h new file mode 100644 index 0000000000..15ce2ee347 --- /dev/null +++ b/core/acsdkCodecUtils/include/acsdkCodecUtils/Base64.h @@ -0,0 +1,64 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCODECUTILS_BASE64_H_ +#define ACSDKCODECUTILS_BASE64_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCodecUtils { + +/** + * @brief Encodes binary data into string using Base64. + * + * This method encodes binary data into printable form using Base64 encoding. The output uses characters A-Z,a-z,0-9, + * "+", "/". Every three bytes of data are converted into four bytes of output. If the input is not a multiple of 3 + * bytes, the output will be padded with "=" characters (one or two). + * + * @param[in] binary Binary data to encode. + * @param[in,out] base64String Destination container. The method appends data to the container. + * + * @return True, if operation succeeds. If operation fails, the contents of \a base64String is unmodified. + * + * @sa decodeBase64() + * @ingroup CodecUtils + */ +bool encodeBase64(const Bytes& binary, std::string& base64String) noexcept; + +/** + * @brief Decodes binary data from string using Base64. + * + * This method decodes binary data from string using Base64. Whitespace, newline, and carriage return characters are + * ignored. + * + * The method converts 4 input characters (excluding ignorable whitespace) into 3 output bytes. If the method encounters + * unsupported character (other than A-Z,a-z,0-9,"+", "/", "=" at the end, or ignorable whitespace), the operation + * fails. + * + * @param[in] base64String Data to decode in base64 form. + * @param[in,out] binary Decoded data. The method appends data to the container. + * @return True, if operation succeeds. If operation fails, the contents of \a binary is unmodified. + * + * @sa encodeBase64() + * @ingroup CodecUtils + */ +bool decodeBase64(const std::string& base64String, Bytes& binary) noexcept; + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK + +#endif // ACSDKCODECUTILS_BASE64_H_ diff --git a/core/acsdkCodecUtils/include/acsdkCodecUtils/Hex.h b/core/acsdkCodecUtils/include/acsdkCodecUtils/Hex.h new file mode 100644 index 0000000000..ba6d8943e8 --- /dev/null +++ b/core/acsdkCodecUtils/include/acsdkCodecUtils/Hex.h @@ -0,0 +1,64 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCODECUTILS_HEX_H_ +#define ACSDKCODECUTILS_HEX_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCodecUtils { + +/** + * Encode binary data into string using hex encoding. + * + * Method encodes binary data into hexadecimal printable form. Every input byte is represented by two output bytes. + * The method uses number characters 0-9 and lowercase letters a-f to represent hexadecimal values. + * + * Method appends data to destination container. + * + * @param[in] binary Binary data to encode. + * @param[in,out] hexString Container to store encoded data. The data is appended to container. + * + * @return True, if operation succeeds. If operation fails, the contents of \a hexString is unmodified. + * + * @sa decodeHex() + * @ingroup CodecUtils + */ +bool encodeHex(const Bytes& binary, std::string& hexString) noexcept; + +/** + * @brief Decodes binary data from string using hex. + * + * Method decodes input from hexadecimal string. Whitespace, newline, and carriage return characters are ignored. + * + * The method converts every 2 input characters (excluding ignorable whitespace) into single output byte. If the method + * encounters unsupported character (other than A-F,a-f,0-9, or ignorable whitespace), the operation fails. + * + * @param[in] hexString Data to decode in hex form. + * @param[in,out] binary Container to store decoded data. The decoded contents is appended to container. + * + * @return True, if operation succeeds. If operation fails, the contents of \a binary is unmodified. + * + * @sa encodeHex() + * @ingroup CodecUtils + */ +bool decodeHex(const std::string& hexString, Bytes& binary) noexcept; + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK + +#endif // ACSDKCODECUTILS_HEX_H_ diff --git a/core/acsdkCodecUtils/include/acsdkCodecUtils/Types.h b/core/acsdkCodecUtils/include/acsdkCodecUtils/Types.h new file mode 100644 index 0000000000..585975ea4c --- /dev/null +++ b/core/acsdkCodecUtils/include/acsdkCodecUtils/Types.h @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCODECUTILS_TYPES_H_ +#define ACSDKCODECUTILS_TYPES_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkCodecUtils { + +/// @brief Byte data type. +/// @ingroup CodecUtils +typedef unsigned char Byte; + +/// @brief Byte data block. +/// @ingroup CodecUtils +typedef std::vector Bytes; + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK + +#endif // ACSDKCODECUTILS_TYPES_H_ diff --git a/core/acsdkCodecUtils/privateInclude/acsdkCodecUtils/private/Base64Common.h b/core/acsdkCodecUtils/privateInclude/acsdkCodecUtils/private/Base64Common.h new file mode 100644 index 0000000000..d9a52c54fb --- /dev/null +++ b/core/acsdkCodecUtils/privateInclude/acsdkCodecUtils/private/Base64Common.h @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCODECUTILS_PRIVATE_BASE64COMMON_H_ +#define ACSDKCODECUTILS_PRIVATE_BASE64COMMON_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace acsdkCodecUtils { + +/// @brief Number of bytes in Base64 encoded block. +/// @private +static constexpr unsigned B64CHAR_BLOCK = 4; + +/// @brief Number of bytes in Base64 binary block. +/// @private +static constexpr unsigned B64BIN_BLOCK = 3; + +/** + * @brief Preprocesses Base64 input for decoding. + * + * Method validates input string and strips all whitespace characters. + * + * @param[in] base64String Base64 string. + * @param[out] output Binary form of base64 string without whitespaces. + * + * @return true on success, false on error. + * @private + */ +bool preprocessBase64(const std::string& base64String, Bytes& output) noexcept; + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK + +#endif // ACSDKCODECUTILS_PRIVATE_BASE64COMMON_H_ diff --git a/core/acsdkCodecUtils/privateInclude/acsdkCodecUtils/private/CodecsCommon.h b/core/acsdkCodecUtils/privateInclude/acsdkCodecUtils/private/CodecsCommon.h new file mode 100644 index 0000000000..ffc153d2b3 --- /dev/null +++ b/core/acsdkCodecUtils/privateInclude/acsdkCodecUtils/private/CodecsCommon.h @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCODECUTILS_PRIVATE_CODECSCOMMON_H_ +#define ACSDKCODECUTILS_PRIVATE_CODECSCOMMON_H_ + +namespace alexaClientSDK { +namespace acsdkCodecUtils { + +/** + * @brief Method to determine is character is ignorable when decoding data. + * + * This method tells if the character shall be ignored when converting strings into binary. + * + * @param [in] ch Character position to test. + * + * @retval true If \a ch character is ignored when decoding string. + * @retval false If \a ch character is not ignorable whitespace. + * @private + */ +bool isIgnorableWhitespace(char ch); + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK + +#endif // ACSDKCODECUTILS_PRIVATE_CODECSCOMMON_H_ diff --git a/core/acsdkCodecUtils/src/Base64Common.cpp b/core/acsdkCodecUtils/src/Base64Common.cpp new file mode 100644 index 0000000000..dc6d8531ed --- /dev/null +++ b/core/acsdkCodecUtils/src/Base64Common.cpp @@ -0,0 +1,75 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCodecUtils { + +/** + * @brief Checks if the character is valid. + * + * This method checks if the character is a valid for base64 decoding. The valid character must be one of A-Z,a-z,0-9, + * '/','+'. + * + * @param[in] ch Character to test. + * @return true if character is valid, false otherwise. + * @private + */ +static bool isValidChar(char ch) { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || '/' == ch || '+' == ch; +} + +bool preprocessBase64(const std::string& base64String, Bytes& output) noexcept { + // Strip all ignorable characters from input before processing and validate the input. + // The input must match the expression: + // ^([a-zA-Z0-9+/]{4})*([a-zA-Z0-9+/]{3}=|[a-zA-Z0-9+/]{2}==)?$ + output.reserve(base64String.size()); + unsigned int cnt = 0; + bool seenTail = false; + for (char ch : base64String) { + if (isIgnorableWhitespace(ch)) { + continue; + } + if (seenTail) { + if (cnt < B64BIN_BLOCK || '=' != ch) { + // Bad input: data after '=' character. + return false; + } + output.push_back(ch); + } else { + if (isValidChar(ch)) { + output.push_back(ch); + } else if (cnt > 1 && ch == '=') { + seenTail = true; + output.push_back(ch); + } else { + return false; + } + } + cnt = (cnt + 1) % B64CHAR_BLOCK; + } + if (output.empty()) { + return true; + } + if (output.size() % B64CHAR_BLOCK) { + return false; + } + return true; +} + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK diff --git a/core/acsdkCodecUtils/src/Base64Internal.cpp b/core/acsdkCodecUtils/src/Base64Internal.cpp new file mode 100644 index 0000000000..5254800e7f --- /dev/null +++ b/core/acsdkCodecUtils/src/Base64Internal.cpp @@ -0,0 +1,158 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCodecUtils { + +/// @brief Number of bits in Base64 byte. +/// @private +static constexpr unsigned B64CHAR_BIT = 6; + +/// @brief Bit mask (maximum value) of Base64 byte. +/// @private +static constexpr unsigned B64CHAR_MAX = 0x3F; + +/// @brief Letter count for A-Z or a-z range. +/// @private +static constexpr unsigned AZ_LETTER_COUNT = 26; + +/// @brief Letter count for 0-9 range. +/// @private +static constexpr unsigned DIGITS_COUNT = 10; + +/// @brief Binary value for plus character. +/// @private +static constexpr unsigned SYM_PLUS_VALUE = 62; + +/// @brief Binary value for divide character. +/// @private +static constexpr unsigned SYM_DIV_VALUE = 63; + +/** + * @brief Maps binary value into character. + * + * Maps 6-bit binary value into ASCII character for Base64 encoding. + * + * @param[in] value Binary value to map into character. The value must be in range of 0..63 inclusive. + * @return ASCII character. + */ +static char mapValueToChar(uint32_t value) noexcept { + if (value < AZ_LETTER_COUNT) { + return static_cast(value + 'A'); + } else if (value < AZ_LETTER_COUNT * 2) { + return static_cast(value - AZ_LETTER_COUNT + 'a'); + } else if (value < AZ_LETTER_COUNT * 2 + DIGITS_COUNT) { + return static_cast(value - (AZ_LETTER_COUNT * 2) + '0'); + } else if (SYM_PLUS_VALUE == value) { + return '+'; + } else { + // value must be equal to SYM_DIV_VALUE + return '/'; + } +} + +bool encodeBase64(const Bytes& binary, std::string& base64String) noexcept { + if (binary.empty()) { + return true; + } + + size_t nBlocks = binary.size() / B64BIN_BLOCK; + size_t nTail = binary.size() % B64BIN_BLOCK; + + size_t outputSize = nBlocks * B64CHAR_BLOCK; + if (nTail) { + outputSize += B64CHAR_BLOCK; + } + base64String.reserve(base64String.size() + outputSize); + + unsigned int accumulator = 0; + unsigned int nBits = 0; + + for (Byte b : binary) { + accumulator = (accumulator << CHAR_BIT) | static_cast(b); + nBits += CHAR_BIT; + if (nBits == B64CHAR_BIT * 2) { + nBits = B64CHAR_BIT; + base64String.push_back(mapValueToChar((accumulator >> B64CHAR_BIT) & B64CHAR_MAX)); + } + if (nBits >= B64CHAR_BIT) { + nBits -= B64CHAR_BIT; + base64String.push_back(mapValueToChar((accumulator >> nBits) & B64CHAR_MAX)); + } + } + if (nBits > 0) { + base64String.push_back(mapValueToChar((accumulator << (B64CHAR_BIT - nBits)) & B64CHAR_MAX)); + } + + if (nTail) { + base64String.push_back('='); + if (1 == nTail) { + base64String.push_back('='); + } + } + + return true; +} + +bool decodeBase64(const std::string& base64String, Bytes& binary) noexcept { + Bytes tmp; + if (!preprocessBase64(base64String, tmp)) { + return false; + } + if (tmp.empty()) { + return true; + } + if (tmp.size() % B64CHAR_BLOCK) { + return false; + } + size_t expectedLen = tmp.size() / B64CHAR_BLOCK * B64BIN_BLOCK; + + unsigned int accumulator = 0; + unsigned int nBits = 0; + size_t len = 0; + for (unsigned char ch : tmp) { + unsigned value; + if (ch == '=') { + break; + } else if (ch >= 'A' && ch <= 'Z') { + value = ch - 'A'; + } else if (ch >= 'a' && ch <= 'z') { + value = ch - 'a' + AZ_LETTER_COUNT; + } else if (ch >= '0' && ch <= '9') { + value = ch - '0' + AZ_LETTER_COUNT * 2; + } else if ('+' == ch) { + value = SYM_PLUS_VALUE; + } else { + // ch must be equal to '/' + value = SYM_DIV_VALUE; + } + accumulator = (accumulator << B64CHAR_BIT) | value; + nBits += B64CHAR_BIT; + if (nBits >= CHAR_BIT) { + nBits -= CHAR_BIT; + binary.push_back(static_cast((accumulator >> nBits) & UCHAR_MAX)); + len++; + } + } + + return len <= expectedLen; +} + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK diff --git a/core/acsdkCodecUtils/src/Base64OpenSsl.cpp b/core/acsdkCodecUtils/src/Base64OpenSsl.cpp new file mode 100644 index 0000000000..93c8a1996b --- /dev/null +++ b/core/acsdkCodecUtils/src/Base64OpenSsl.cpp @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCodecUtils { + +bool encodeBase64(const Bytes& binary, std::string& base64String) noexcept { + if (binary.empty()) { + return true; + } + + // Base64 creates 4 output bytes for each 3 bytes of input + size_t expectedLen = binary.size() / B64BIN_BLOCK * B64CHAR_BLOCK; + // If input size is not dividable by 3, Base64 creates an additional 4 byte block. + if (binary.size() % B64BIN_BLOCK) { + expectedLen += B64CHAR_BLOCK; + } + std::vector tmp; + // OpenSSL encoder adds a null character when encoding. + tmp.resize(expectedLen + 1); + int len = EVP_EncodeBlock( + reinterpret_cast(&tmp[0]), + reinterpret_cast(binary.data()), + binary.size()); + if (len >= 0 && static_cast(len) == expectedLen) { + base64String.append(tmp.data()); + return true; + } else { + return false; + } +} + +bool decodeBase64(const std::string& base64String, Bytes& binary) noexcept { + Bytes tmp; + if (!preprocessBase64(base64String, tmp)) { + return false; + } + if (tmp.empty()) { + return true; + } + + size_t expectedLen = tmp.size() / B64CHAR_BLOCK * B64BIN_BLOCK; + size_t index = binary.size(); + binary.resize(index + expectedLen); + int len = EVP_DecodeBlock(&binary[index], &tmp[0], tmp.size()); + + return len >= 0 && static_cast(len) <= expectedLen; +} + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK diff --git a/core/acsdkCodecUtils/src/CMakeLists.txt b/core/acsdkCodecUtils/src/CMakeLists.txt new file mode 100644 index 0000000000..f52155d43d --- /dev/null +++ b/core/acsdkCodecUtils/src/CMakeLists.txt @@ -0,0 +1,36 @@ + +set(acsdkCodecUtils_SOURCES + Base64Common.cpp + CodecsCommon.cpp + Hex.cpp + ) +set(acsdkCodecUtils_COMPILE_DEFS + ACSDK_LOG_MODULE=acsdkCodecUtils + ) +set(acsdkCodecUtils_INCLUDES + "${acsdkCodecUtils_SOURCE_DIR}/include" + ) +set(acsdkCodecUtils_PRIVATE_INCLUDES + "${acsdkCodecUtils_SOURCE_DIR}/privateInclude" + ) +set(acsdkCodecUtils_LIBRARIES) + +if(CRYPTO_FOUND) + # Use OpenSSL Crypto API for Base64 if present. + message(STATUS "Using OpenSSL for Base64 in acsdkCodecUtils") + list(APPEND acsdkCodecUtils_SOURCES Base64OpenSsl.cpp) + list(APPEND acsdkCodecUtils_PRIVATE_INCLUDES ${CRYPTO_INCLUDE_DIRS}) + list(APPEND acsdkCodecUtils_LIBRARIES ${CRYPTO_LDFLAGS}) +else() + message(STATUS "Using custom base64 implementation in acsdkCodecUtils.") + list(APPEND acsdkCodecUtils_SOURCES Base64Internal.cpp) +endif() + +add_library(acsdkCodecUtils ${acsdkCodecUtils_SOURCES}) +target_compile_definitions(acsdkCodecUtils PRIVATE ${acsdkCodecUtils_COMPILE_DEFS}) +target_include_directories(acsdkCodecUtils PUBLIC ${acsdkCodecUtils_INCLUDES}) +target_include_directories(acsdkCodecUtils PRIVATE ${acsdkCodecUtils_PRIVATE_INCLUDES}) +target_link_libraries(acsdkCodecUtils ${acsdkCodecUtils_LIBRARIES}) + +# install target +asdk_install() diff --git a/core/acsdkCodecUtils/src/CodecsCommon.cpp b/core/acsdkCodecUtils/src/CodecsCommon.cpp new file mode 100644 index 0000000000..62adc4d505 --- /dev/null +++ b/core/acsdkCodecUtils/src/CodecsCommon.cpp @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCodecUtils { + +bool isIgnorableWhitespace(char ch) { + switch (ch) { + case ' ': + // fallthrough + case '\n': + // fallthrough + case '\r': + // fallthrough + case '\t': + return true; + default: + return false; + } +} + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK diff --git a/core/acsdkCodecUtils/src/Hex.cpp b/core/acsdkCodecUtils/src/Hex.cpp new file mode 100644 index 0000000000..7a60aac410 --- /dev/null +++ b/core/acsdkCodecUtils/src/Hex.cpp @@ -0,0 +1,114 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCodecUtils { + +/** + * @brief Static table to convert binary code into ASCII character. + * + * This array maps values 0-15 into one of the 0-9,a-f characters. + * @private + */ +static const char BINARY_TO_HEX[] = "0123456789abcdef"; + +bool encodeHex(const Bytes& binary, std::string& hexString) noexcept { + hexString.reserve(hexString.size() + binary.size() * 2); + + for (Byte b : binary) { + hexString.push_back(BINARY_TO_HEX[b >> 4]); + hexString.push_back(BINARY_TO_HEX[b & 15u]); + } + + return true; +} + +/** + * @brief Converts character code into binary. + * This method converts hex-encoded code into binary form. The input must be one of 0-9,a-f,A-F characters. + * + * @param[in] ch Character to decode. + * @return Binary code. + * @private + */ +/// +static int charToInt(char ch) { + if (ch >= '0' && ch <= '9') { + return ch - '0'; + } else if (ch >= 'a' && ch <= 'f') { + return ch - 'a' + 10; + } else /* if (ch >= 'A' && ch <= 'F') */ { + return ch - 'A' + 10; + } +} + +/** + * @brief Helper to verify if the character is valid for hex decoding. + * + * Method verifies if the input character is a valid hexadecimal symbol. + * + * @param[in] ch Character code to verify. + * @retval true If \a ch is one of 0-9,a-f,A-F characters. + * @private + */ +static bool isValidHexChar(char ch) { + return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); +} + +bool decodeHex(const std::string& hexString, Bytes& binary) noexcept { + binary.reserve(hexString.size() / 2 + binary.size()); + + int b0 = 0; + bool accumulator = false; + + // validate the input. + for (char ch : hexString) { + if (isIgnorableWhitespace(ch)) { + continue; + } + if (!isValidHexChar(ch)) { + return false; + } + accumulator = !accumulator; + } + if (accumulator) { + // We have an odd number of input characters, which is an error. + return false; + } + + for (char ch : hexString) { + if (isIgnorableWhitespace(ch)) { + continue; + } + + int b1 = charToInt(ch); + + if (accumulator) { + accumulator = false; + binary.push_back((b0 << 4) | b1); + } else { + accumulator = true; + b0 = b1; + } + } + + return true; +} + +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK diff --git a/core/acsdkCodecUtils/test/Base64CodecTest.cpp b/core/acsdkCodecUtils/test/Base64CodecTest.cpp new file mode 100644 index 0000000000..02d36841e6 --- /dev/null +++ b/core/acsdkCodecUtils/test/Base64CodecTest.cpp @@ -0,0 +1,113 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +#ifdef CRYPTO_FOUND + +#include + +#include + +namespace alexaClientSDK { +namespace acsdkCodecUtils { +namespace test { + +using namespace ::testing; + +// Test string. +static const std::string TEST_STR{"A quick brown fox jumps over the lazy dog."}; + +// Test string encoded in Base64. +static const std::string TEST_STR_B64{"QSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cu"}; + +// Test empty encoding works and resets buffer +TEST(Base64CodecTest, test_base64EncodeEmpty) { + std::string encoded; + ASSERT_TRUE(encodeBase64(Bytes{}, encoded)); + ASSERT_TRUE(encoded.empty()); +} + +// Test encoding works and appends data +TEST(Base64CodecTest, test_base64EncodeAppend) { + std::string encoded{"prefix:"}; + ASSERT_TRUE(encodeBase64(Bytes{0, 1, 2}, encoded)); + ASSERT_EQ("prefix:AAEC", encoded); +} + +// Test encoding works +TEST(Base64CodecTest, test_base64EncodeTestStr) { + std::string encoded; + Bytes source{TEST_STR.data(), TEST_STR.data() + TEST_STR.size()}; + ASSERT_TRUE(encodeBase64(source, encoded)); + ASSERT_EQ(TEST_STR_B64, encoded); +} + +// Test empty decoding works +TEST(Base64CodecTest, test_base64DecodeEmpty) { + Bytes decoded; + ASSERT_TRUE(decodeBase64("", decoded)); + ASSERT_TRUE(decoded.empty()); +} + +// Test decoding works and appends buffer +TEST(Base64CodecTest, test_base64DecodeAppend) { + Bytes decoded{1}; + ASSERT_TRUE(decodeBase64("AAEC", decoded)); + ASSERT_EQ((Bytes{1, 0, 1, 2}), decoded); +} + +// Test decoding works +TEST(Base64CodecTest, test_base64DecodeTestStr) { + Bytes decoded; + ASSERT_TRUE(decodeBase64(TEST_STR_B64, decoded)); + std::string decodedStr{decoded.data(), decoded.data() + decoded.size()}; + ASSERT_EQ(TEST_STR, decodedStr); +} + +// Test decoding works and appends buffer +TEST(Base64CodecTest, test_base64DecodeAppendWhitespace) { + Bytes decoded{1}; + ASSERT_TRUE(decodeBase64(" \t\n\rA A\t\n\r E C\r\n\t ", decoded)); + ASSERT_EQ((Bytes{1, 0, 1, 2}), decoded); +} + +// Test decoding fails on error +TEST(Base64CodecTest, test_base64DecodeError) { + Bytes decoded; + ASSERT_FALSE(decodeBase64("....", decoded)); +} + +// Test decoding fails on error +TEST(Base64CodecTest, test_base64DecodeErrorBadTail) { + Bytes decoded; + ASSERT_FALSE(decodeBase64("AA=C", decoded)); +} + +// Test decoding fails on error +TEST(Base64CodecTest, test_base64DecodeErrorDataAfterEnd) { + Bytes decoded; + ASSERT_FALSE(decodeBase64("AA==AAEC", decoded)); +} + +// Test decoding fails on error +TEST(Base64CodecTest, test_base64DecodeErrorEarlyEnd) { + Bytes decoded; + ASSERT_FALSE(decodeBase64("A===", decoded)); +} + +} // namespace test +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK + +#endif // ifdef CRYPTO_FOUND diff --git a/core/acsdkCodecUtils/test/Base64InternalCodecTest.cpp b/core/acsdkCodecUtils/test/Base64InternalCodecTest.cpp new file mode 100644 index 0000000000..a494bca1ed --- /dev/null +++ b/core/acsdkCodecUtils/test/Base64InternalCodecTest.cpp @@ -0,0 +1,109 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCodecUtils { +namespace test { + +using namespace ::testing; + +// Test string. +static const std::string TEST_STR{"A quick brown fox jumps over the lazy dog."}; + +// Test string encoded in Base64. +static const std::string TEST_STR_B64{"QSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cu"}; + +// Test empty encoding works and resets buffer +TEST(Base64InternalCodecTest, test_base64EncodeEmpty) { + std::string encoded; + ASSERT_TRUE(encodeBase64(Bytes{}, encoded)); + ASSERT_TRUE(encoded.empty()); +} + +// Test encoding works and appends data +TEST(Base64InternalCodecTest, test_base64EncodeAppend) { + std::string encoded{"prefix:"}; + ASSERT_TRUE(encodeBase64(Bytes{0, 1, 2}, encoded)); + ASSERT_EQ("prefix:AAEC", encoded); +} + +// Test encoding works +TEST(Base64InternalCodecTest, test_base64EncodeTestStr) { + std::string encoded; + Bytes source{TEST_STR.data(), TEST_STR.data() + TEST_STR.size()}; + ASSERT_TRUE(encodeBase64(source, encoded)); + ASSERT_EQ(TEST_STR_B64, encoded); +} + +// Test empty decoding works +TEST(Base64InternalCodecTest, test_base64DecodeEmpty) { + Bytes decoded; + ASSERT_TRUE(decodeBase64("", decoded)); + ASSERT_TRUE(decoded.empty()); +} + +// Test decoding works and appends buffer +TEST(Base64InternalCodecTest, test_base64DecodeAppend) { + Bytes decoded{1}; + ASSERT_TRUE(decodeBase64("AAEC", decoded)); + ASSERT_EQ((Bytes{1, 0, 1, 2}), decoded); +} + +// Test decoding works +TEST(Base64InternalCodecTest, test_base64DecodeTestStr) { + Bytes decoded; + ASSERT_TRUE(decodeBase64(TEST_STR_B64, decoded)); + std::string decodedStr{decoded.data(), decoded.data() + decoded.size()}; + ASSERT_EQ(TEST_STR, decodedStr); +} + +// Test decoding works and appends buffer +TEST(Base64InternalCodecTest, test_base64DecodeAppendWhitespace) { + Bytes decoded{1}; + ASSERT_TRUE(decodeBase64(" \t\n\rA A\t\n\r E C\r\n\t ", decoded)); + ASSERT_EQ((Bytes{1, 0, 1, 2}), decoded); +} + +// Test decoding fails on error +TEST(Base64InternalCodecTest, test_base64DecodeError) { + Bytes decoded; + ASSERT_FALSE(decodeBase64("....", decoded)); +} + +// Test decoding fails on error +TEST(Base64InternalCodecTest, test_base64DecodeErrorBadTail) { + Bytes decoded; + ASSERT_FALSE(decodeBase64("AA=C", decoded)); +} + +// Test decoding fails on error +TEST(Base64InternalCodecTest, test_base64DecodeErrorDataAfterEnd) { + Bytes decoded; + ASSERT_FALSE(decodeBase64("AA==AAEC", decoded)); +} + +// Test decoding fails on error +TEST(Base64InternalCodecTest, test_base64DecodeErrorEarlyEnd) { + Bytes decoded; + ASSERT_FALSE(decodeBase64("A===", decoded)); +} + +} // namespace test +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK diff --git a/core/acsdkCodecUtils/test/CMakeLists.txt b/core/acsdkCodecUtils/test/CMakeLists.txt new file mode 100644 index 0000000000..5ccae3c463 --- /dev/null +++ b/core/acsdkCodecUtils/test/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +set(TEST_INCLUDES + "${acsdkCodecUtils_SOURCE_DIR}/privateInclude" + "${acsdkCodecUtils_SOURCE_DIR}/src" + ) +set(TEST_LIBRIRIES + acsdkCodecUtils + ) + +add_definitions("-DACSDK_LOG_MODULE=acsdkCodecUtilsTest") +if(CRYPTO_FOUND) + add_definitions("-DCRYPTO_FOUND") +endif() +discover_unit_tests("${TEST_INCLUDES}" "${TEST_LIBRIRIES}") diff --git a/core/acsdkCodecUtils/test/HexCodecTest.cpp b/core/acsdkCodecUtils/test/HexCodecTest.cpp new file mode 100644 index 0000000000..f70d3ac217 --- /dev/null +++ b/core/acsdkCodecUtils/test/HexCodecTest.cpp @@ -0,0 +1,151 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 acsdkCodecUtils { +namespace test { + +using namespace ::testing; + +const std::string HEX_STR = "0123456789"; +const Bytes HEX_STR_BINARY{0x01, 0x23, 0x45, 0x67, 0x89}; +const Bytes HEX_STR_BINARY2{0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89}; + +// Test string. +static const std::string TEST_STR{"A quick brown fox jumps over the lazy dog."}; + +// Test string encoded in hex (uppercase). +static const std::string TEST_STR_HEX_U{ + "4120717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F672E"}; + +// Test string encoded in hex (lowercase). +static const std::string TEST_STR_HEX_L{ + "4120717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e"}; + +// Verify hex decoding works and resets the output buffer. +TEST(HexCodecTest, test_hexDecode) { + Bytes decoded; + ASSERT_TRUE(decodeHex(HEX_STR, decoded)); + ASSERT_EQ(HEX_STR_BINARY, decoded); +} + +// Verify hex decoding works up for lowercase letter values +TEST(HexCodecTest, test_hexDecodeAFLowerCase) { + Bytes decoded; + ASSERT_TRUE(decodeHex("ab", decoded)); + ASSERT_TRUE(decodeHex("cd", decoded)); + ASSERT_TRUE(decodeHex("ef", decoded)); + ASSERT_EQ((Bytes{0xAB, 0xCD, 0xEF}), decoded); +} + +// Verify hex decoding works up for uppercase letter values +TEST(HexCodecTest, test_hexDecodeAFUpperCase) { + Bytes decoded; + ASSERT_TRUE(decodeHex("AB", decoded)); + ASSERT_TRUE(decodeHex("CD", decoded)); + ASSERT_TRUE(decodeHex("EF", decoded)); + ASSERT_EQ((Bytes{0xAB, 0xCD, 0xEF}), decoded); +} + +// Verify hex decoding works up for mixed case letter values +TEST(HexCodecTest, test_hexDecodeAFMixedCase) { + Bytes decoded; + ASSERT_TRUE(decodeHex("Ab", decoded)); + ASSERT_TRUE(decodeHex("cD", decoded)); + ASSERT_TRUE(decodeHex("eF", decoded)); + ASSERT_EQ((Bytes{0xAB, 0xCD, 0xEF}), decoded); +} + +// Verify hex decoding works with larger input +TEST(HexCodecTest, test_hexDecodeTestStringUpperCase) { + Bytes decoded; + ASSERT_TRUE(decodeHex(TEST_STR_HEX_U, decoded)); + std::string decodedStr{decoded.data(), decoded.data() + decoded.size()}; + ASSERT_EQ(TEST_STR, decodedStr); +} + +// Verify hex decoding works with larger input +TEST(HexCodecTest, test_hexDecodeTestStringLowerCase) { + Bytes decoded; + ASSERT_TRUE(decodeHex(TEST_STR_HEX_L, decoded)); + std::string decodedStr{decoded.data(), decoded.data() + decoded.size()}; + ASSERT_EQ(TEST_STR, decodedStr); +} + +// Verify hex decoding can append data to buffer +TEST(HexCodecTest, test_hexDecodeAppend) { + Bytes decoded; + ASSERT_TRUE(decodeHex(HEX_STR, decoded)); + ASSERT_TRUE(decodeHex(HEX_STR, decoded)); + ASSERT_EQ(HEX_STR_BINARY2, decoded); +} + +// Verify hex decoding fails on bad size +TEST(HexCodecTest, test_hexDecodeBadSize) { + Bytes decoded; + ASSERT_FALSE(decodeHex("012", decoded)); +} + +// Verify hex decoding fails on bad size +TEST(HexCodecTest, test_hexDecodeBadChar) { + Bytes decoded; + ASSERT_FALSE(decodeHex("AZ", decoded)); +} + +// Verify hex encoding works and resets the buffer +TEST(HexCodecTest, test_hexEncode) { + std::string encoded{}; + ASSERT_TRUE(encodeHex(HEX_STR_BINARY, encoded)); + ASSERT_EQ(HEX_STR, encoded); +} + +// Verify hex encoding works and can append to buffer +TEST(HexCodecTest, test_hexEncodeAppend) { + std::string encoded; + ASSERT_TRUE(encodeHex(HEX_STR_BINARY, encoded)); + ASSERT_TRUE(encodeHex(HEX_STR_BINARY, encoded)); + ASSERT_EQ(HEX_STR + HEX_STR, encoded); +} + +// Verify hex encode works for A-F +TEST(HexCodecTest, test_hexEncodeAF) { + std::string encoded; + ASSERT_TRUE(encodeHex(Bytes{0xab}, encoded)); + ASSERT_TRUE(encodeHex(Bytes{0xcd}, encoded)); + ASSERT_TRUE(encodeHex(Bytes{0xef}, encoded)); + ASSERT_EQ("abcdef", encoded); +} + +// Verify hex encode works with test string +TEST(HexCodecTest, test_hexEncodeTestString) { + std::string encoded; + ASSERT_TRUE(encodeHex(Bytes{TEST_STR.data(), TEST_STR.data() + TEST_STR.size()}, encoded)); + ASSERT_EQ(TEST_STR_HEX_L, encoded); +} + +// Verify hex decoding works up for whitespace values +TEST(HexCodecTest, test_hexDecodeWithWhitespace) { + Bytes decoded; + ASSERT_TRUE(decodeHex("\rA B\tC\nD\n", decoded)); + ASSERT_EQ((Bytes{0xAB, 0xCD}), decoded); +} + +} // namespace test +} // namespace acsdkCodecUtils +} // namespace alexaClientSDK diff --git a/core/acsdkCore/src/CMakeLists.txt b/core/acsdkCore/src/CMakeLists.txt index a30e80fb6f..bff36789c2 100644 --- a/core/acsdkCore/src/CMakeLists.txt +++ b/core/acsdkCore/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkCore") -add_library(acsdkCore SHARED +add_library(acsdkCore CoreComponent.cpp) target_include_directories(acsdkCore PUBLIC diff --git a/core/acsdkPostConnectOperationProviderRegistrar/src/CMakeLists.txt b/core/acsdkPostConnectOperationProviderRegistrar/src/CMakeLists.txt index 2c2ea01fa6..8a9bae188d 100644 --- a/core/acsdkPostConnectOperationProviderRegistrar/src/CMakeLists.txt +++ b/core/acsdkPostConnectOperationProviderRegistrar/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkPostConnectOperationProviderRegistrar") -add_library(acsdkPostConnectOperationProviderRegistrar SHARED +add_library(acsdkPostConnectOperationProviderRegistrar PostConnectOperationProviderRegistrar.cpp) target_include_directories(acsdkPostConnectOperationProviderRegistrar PUBLIC diff --git a/core/acsdkPostConnectOperationProviderRegistrar/test/PostConnectOperationProviderRegistrarTest.cpp b/core/acsdkPostConnectOperationProviderRegistrar/test/PostConnectOperationProviderRegistrarTest.cpp index 8ac240c906..d3d5c44f33 100644 --- a/core/acsdkPostConnectOperationProviderRegistrar/test/PostConnectOperationProviderRegistrarTest.cpp +++ b/core/acsdkPostConnectOperationProviderRegistrar/test/PostConnectOperationProviderRegistrarTest.cpp @@ -34,6 +34,8 @@ class MockStartupNotifier : public StartupNotifierInterface { public: MOCK_METHOD1(addObserver, void(const std::shared_ptr& observer)); MOCK_METHOD1(removeObserver, void(const std::shared_ptr& observer)); + MOCK_METHOD1(addWeakPtrObserver, void(const std::weak_ptr& observer)); + MOCK_METHOD1(removeWeakPtrObserver, void(const std::weak_ptr& observer)); MOCK_METHOD1(notifyObservers, void(std::function&)>)); MOCK_METHOD1(notifyObserversInReverse, bool(std::function&)>)); MOCK_METHOD1(setAddObserverFunction, void(std::function&)>)); diff --git a/core/acsdkRegistrationManager/privateInclude/RegistrationManager/RegistrationNotifier.h b/core/acsdkRegistrationManager/privateInclude/RegistrationManager/RegistrationNotifier.h index 0851b39f5b..19c1640920 100644 --- a/core/acsdkRegistrationManager/privateInclude/RegistrationManager/RegistrationNotifier.h +++ b/core/acsdkRegistrationManager/privateInclude/RegistrationManager/RegistrationNotifier.h @@ -18,7 +18,7 @@ #include -#include +#include #include #include diff --git a/core/acsdkRegistrationManager/src/CMakeLists.txt b/core/acsdkRegistrationManager/src/CMakeLists.txt index b9903afe41..2c6ecc3c87 100644 --- a/core/acsdkRegistrationManager/src/CMakeLists.txt +++ b/core/acsdkRegistrationManager/src/CMakeLists.txt @@ -1,6 +1,6 @@ add_definitions("-DACSDK_LOG_MODULE=RegistrationManager") -add_library(RegistrationManager SHARED +add_library(RegistrationManager CustomerDataHandler.cpp CustomerDataManager.cpp CustomerDataManagerFactory.cpp diff --git a/core/acsdkRegistrationManagerInterfaces/include/RegistrationManager/RegistrationNotifierInterface.h b/core/acsdkRegistrationManagerInterfaces/include/RegistrationManager/RegistrationNotifierInterface.h index b806b7e38e..c5a33530a4 100644 --- a/core/acsdkRegistrationManagerInterfaces/include/RegistrationManager/RegistrationNotifierInterface.h +++ b/core/acsdkRegistrationManagerInterfaces/include/RegistrationManager/RegistrationNotifierInterface.h @@ -15,7 +15,7 @@ #ifndef REGISTRATIONMANAGER_REGISTRATIONNOTIFIERINTERFACE_H_ #define REGISTRATIONMANAGER_REGISTRATIONNOTIFIERINTERFACE_H_ -#include +#include #include "RegistrationManager/RegistrationObserverInterface.h" diff --git a/core/acsdkSystemClockMonitor/include/acsdkSystemClockMonitor/SystemClockNotifier.h b/core/acsdkSystemClockMonitor/include/acsdkSystemClockMonitor/SystemClockNotifier.h index 5a043bc731..890dd11cf4 100644 --- a/core/acsdkSystemClockMonitor/include/acsdkSystemClockMonitor/SystemClockNotifier.h +++ b/core/acsdkSystemClockMonitor/include/acsdkSystemClockMonitor/SystemClockNotifier.h @@ -18,7 +18,7 @@ #include -#include +#include #include #include diff --git a/core/acsdkSystemClockMonitor/src/CMakeLists.txt b/core/acsdkSystemClockMonitor/src/CMakeLists.txt index 905d3d6f53..e7052a8850 100644 --- a/core/acsdkSystemClockMonitor/src/CMakeLists.txt +++ b/core/acsdkSystemClockMonitor/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkStartupManager") -add_library(acsdkSystemClockMonitor SHARED +add_library(acsdkSystemClockMonitor SystemClockMonitor.cpp SystemClockNotifier.cpp) diff --git a/core/acsdkSystemClockMonitorInterfaces/include/acsdkSystemClockMonitorInterfaces/SystemClockNotifierInterface.h b/core/acsdkSystemClockMonitorInterfaces/include/acsdkSystemClockMonitorInterfaces/SystemClockNotifierInterface.h index c10ad5a375..71be9ba3f8 100644 --- a/core/acsdkSystemClockMonitorInterfaces/include/acsdkSystemClockMonitorInterfaces/SystemClockNotifierInterface.h +++ b/core/acsdkSystemClockMonitorInterfaces/include/acsdkSystemClockMonitorInterfaces/SystemClockNotifierInterface.h @@ -16,7 +16,7 @@ #ifndef ACSDKSYSTEMCLOCKMONITORINTERFACES_SYSTEMCLOCKNOTIFIERINTERFACE_H_ #define ACSDKSYSTEMCLOCKMONITORINTERFACES_SYSTEMCLOCKNOTIFIERINTERFACE_H_ -#include +#include #include "acsdkSystemClockMonitorInterfaces/SystemClockMonitorObserverInterface.h" diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in index 627caad6a8..848cd8237f 100644 --- a/doc/doxygen.cfg.in +++ b/doc/doxygen.cfg.in @@ -291,7 +291,7 @@ OPTIMIZE_OUTPUT_VHDL = NO # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. -EXTENSION_MAPPING = +EXTENSION_MAPPING = in=C++ # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable @@ -858,7 +858,8 @@ FILE_PATTERNS = *.c \ *.vhd \ *.vhdl \ *.ucf \ - *.qsf + *.qsf \ + *.h.in # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt index 14e54bae99..64ba8a82da 100644 --- a/shared/CMakeLists.txt +++ b/shared/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +add_subdirectory("acsdkCommunication") +add_subdirectory("acsdkCommunicationInterfaces") add_subdirectory("acsdkManufactory") add_subdirectory("acsdkNotifier") add_subdirectory("acsdkNotifierInterfaces") @@ -8,4 +10,5 @@ add_subdirectory("acsdkShutdownManager") add_subdirectory("acsdkShutdownManagerInterfaces") add_subdirectory("acsdkStartupManager") add_subdirectory("acsdkStartupManagerInterfaces") +add_subdirectory("KWD") diff --git a/shared/KWD/CMakeLists.txt b/shared/KWD/CMakeLists.txt new file mode 100644 index 0000000000..3d6f1d45e0 --- /dev/null +++ b/shared/KWD/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +add_subdirectory("acsdkKWD") +add_subdirectory("acsdkKWDImplementations") +add_subdirectory("acsdkKWDInterfaces") + +if(KWD) + add_subdirectory("acsdkKWDProvider") +endif() diff --git a/shared/KWD/acsdkKWD/CMakeLists.txt b/shared/KWD/acsdkKWD/CMakeLists.txt new file mode 100644 index 0000000000..e7b9f62568 --- /dev/null +++ b/shared/KWD/acsdkKWD/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkKWD LANGUAGES CXX) + +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +add_subdirectory("src") diff --git a/shared/KWD/acsdkKWD/include/acsdkKWD/KWDComponent.h b/shared/KWD/acsdkKWD/include/acsdkKWD/KWDComponent.h new file mode 100644 index 0000000000..2c08f33ab6 --- /dev/null +++ b/shared/KWD/acsdkKWD/include/acsdkKWD/KWDComponent.h @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKKWD_KWDCOMPONENT_H_ +#define ACSDKKWD_KWDCOMPONENT_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkKWD { + +/** + * Manufactory Component definition for the @c AbstractKeywordDetector. + */ +using KWDComponent = acsdkManufactory::Component< + std::shared_ptr, + std::shared_ptr, + std::shared_ptr, + acsdkManufactory::Import>, + acsdkManufactory::Import>>; + +/** + * Get the @c Manufactory component for creating an instance of @c AbstractKeywordDetector. + * + * @return The @c Manufactory component for creating an instance of @c AbstractKeywordDetector. + */ +KWDComponent getComponent(); + +} // namespace acsdkKWD +} // namespace alexaClientSDK + +#endif // ACSDKKWD_KWDCOMPONENT_H_ diff --git a/shared/KWD/acsdkKWD/src/CMakeLists.txt b/shared/KWD/acsdkKWD/src/CMakeLists.txt new file mode 100644 index 0000000000..776619d46c --- /dev/null +++ b/shared/KWD/acsdkKWD/src/CMakeLists.txt @@ -0,0 +1,25 @@ +add_definitions("-DACSDK_LOG_MODULE=acsdkKWD") + +# If KWD Component file is not defined by an adapter, set to null kwd. +if (NOT DEFINED KWD_COMPONENT_FILE) + message("Creating NullKWDComponent") + set(KWD_COMPONENT_FILE KWDComponent.cpp) +endif() + +# Add KWDComponent file from Adapter Repository +add_library(acsdkKWD ${KWD_COMPONENT_FILE}) + +target_include_directories(acsdkKWD PUBLIC + "${acsdkKWD_SOURCE_DIR}/include" +) + +target_link_libraries(acsdkKWD + acsdkKWDInterfaces + acsdkKWDImplementations + acsdkManufactory + AVSCommon + ${TARGET_KWD_LIB} +) + +# install target +asdk_install() diff --git a/shared/KWD/acsdkKWD/src/KWDComponent.cpp b/shared/KWD/acsdkKWD/src/KWDComponent.cpp new file mode 100644 index 0000000000..01654c6a7d --- /dev/null +++ b/shared/KWD/acsdkKWD/src/KWDComponent.cpp @@ -0,0 +1,31 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkKWD/KWDComponent.h" + +namespace alexaClientSDK { +namespace acsdkKWD { + +KWDComponent getComponent() { + return acsdkManufactory::ComponentAccumulator<>() + .addInstance>(nullptr) + .addInstance>(nullptr) + .addInstance>(nullptr); +} + +} // namespace acsdkKWD +} // namespace alexaClientSDK diff --git a/shared/KWD/acsdkKWDImplementations/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/CMakeLists.txt new file mode 100644 index 0000000000..cfde167965 --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkKWDImplementations LANGUAGES CXX) + +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +add_subdirectory("src") +add_subdirectory("test") diff --git a/KWD/include/KWD/AbstractKeywordDetector.h b/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/AbstractKeywordDetector.h similarity index 79% rename from KWD/include/KWD/AbstractKeywordDetector.h rename to shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/AbstractKeywordDetector.h index 27f21d9dcf..7e8231a3d8 100644 --- a/KWD/include/KWD/AbstractKeywordDetector.h +++ b/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/AbstractKeywordDetector.h @@ -13,19 +13,21 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_KWD_INCLUDE_KWD_ABSTRACTKEYWORDDETECTOR_H_ -#define ALEXA_CLIENT_SDK_KWD_INCLUDE_KWD_ABSTRACTKEYWORDDETECTOR_H_ +#ifndef ACSDKKWDIMPLEMENTATIONS_ABSTRACTKEYWORDDETECTOR_H_ +#define ACSDKKWDIMPLEMENTATIONS_ABSTRACTKEYWORDDETECTOR_H_ #include #include -#include +#include +#include #include -#include #include +#include +#include namespace alexaClientSDK { -namespace kwd { +namespace acsdkKWDImplementations { class AbstractKeywordDetector { public: @@ -44,7 +46,8 @@ class AbstractKeywordDetector { void removeKeyWordObserver(std::shared_ptr keyWordObserver); /** - * Adds the specified observer to the list of observers to notify of key word detector state changes. + * Adds the specified observer to the list of observers to notify of key word detector state changes. Observer will + * have onStateChanged called upon being added to notify of current detector state. * * @param keyWordDetectorStateObserver The observer to add. */ @@ -66,7 +69,7 @@ class AbstractKeywordDetector { protected: /** - * Constructor. + * @deprecated Constructor. * * @param keyWordObservers The observers to notify of keyword detections. * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. @@ -78,6 +81,16 @@ class AbstractKeywordDetector { keyWordDetectorStateObservers = std::unordered_set>()); + /** + * Constructor. + * + * @param keyWordNotifier The object with which to notifiy observers of keyword detections. + * @param KeyWordDetectorStateNotifier The object with which to notify observers of state changes in the engine. + */ + AbstractKeywordDetector( + std::shared_ptr keywordNotifier, + std::shared_ptr keyWordDetectorStateNotifier); + /** * Notifies all keyword observers of the keyword detection. * @@ -131,32 +144,27 @@ class AbstractKeywordDetector { private: /** - * The observers to notify on key word detections. This should be locked with m_keyWordObserversMutex prior to - * usage. + * The notifier to notify observers of key word detections. */ - std::unordered_set> m_keyWordObservers; + std::shared_ptr m_keywordNotifier; /** - * The observers to notify of state changes in the engine. This should be locked with - * m_keyWordDetectorStateObserversMutex prior to usage. + * The notifier to notify observers of state changes in the engine. */ - std::unordered_set> - m_keyWordDetectorStateObservers; - - /// Lock to protect m_keyWordObservers when users wish to add or remove observers - mutable std::mutex m_keyWordObserversMutex; - /// Lock to protect m_keyWordDetectorStateObservers when users wish to add or remove observers - mutable std::mutex m_keyWordDetectorStateObserversMutex; + std::shared_ptr m_keywordDetectorStateNotifier; /** * The current state of the detector. This is stored so that we don't notify observers of the same change in state * multiple times. */ avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState m_detectorState; + + /// Lock to protect m_detectorState. + mutable std::mutex m_detectorStateMutex; }; -} // namespace kwd +} // namespace acsdkKWDImplementations } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_KWD_INCLUDE_KWD_ABSTRACTKEYWORDDETECTOR_H_ +#endif // ACSDKKWDIMPLEMENTATIONS_ABSTRACTKEYWORDDETECTOR_H_ diff --git a/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/KWDNotifierFactories.h b/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/KWDNotifierFactories.h new file mode 100644 index 0000000000..10e68ed665 --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/KWDNotifierFactories.h @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKKWDIMPLEMENTATIONS_KWDNOTIFIERFACTORIES_H_ +#define ACSDKKWDIMPLEMENTATIONS_KWDNOTIFIERFACTORIES_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkKWDImplementations { + +/** + * This class produces a @c KeywordNotifierInterface and @c KeywordDetectorStateNotifierInterface. + */ +class KWDNotifierFactories { +public: + static std::shared_ptr + createKeywordDetectorStateNotifier(); + + static std::shared_ptr createKeywordNotifier(); +}; + +} // namespace acsdkKWDImplementations +} // namespace alexaClientSDK + +#endif // ACSDKKWDIMPLEMENTATIONS_KWDNOTIFIERFACTORIES_H_ diff --git a/KWD/inputs/alexa_joke.wav b/shared/KWD/acsdkKWDImplementations/inputs/alexa_joke.wav similarity index 100% rename from KWD/inputs/alexa_joke.wav rename to shared/KWD/acsdkKWDImplementations/inputs/alexa_joke.wav diff --git a/KWD/inputs/alexa_stop_alexa_joke.wav b/shared/KWD/acsdkKWDImplementations/inputs/alexa_stop_alexa_joke.wav similarity index 100% rename from KWD/inputs/alexa_stop_alexa_joke.wav rename to shared/KWD/acsdkKWDImplementations/inputs/alexa_stop_alexa_joke.wav diff --git a/KWD/inputs/four_alexa.wav b/shared/KWD/acsdkKWDImplementations/inputs/four_alexa.wav similarity index 100% rename from KWD/inputs/four_alexa.wav rename to shared/KWD/acsdkKWDImplementations/inputs/four_alexa.wav diff --git a/KWD/inputs/stop_stop.wav b/shared/KWD/acsdkKWDImplementations/inputs/stop_stop.wav similarity index 100% rename from KWD/inputs/stop_stop.wav rename to shared/KWD/acsdkKWDImplementations/inputs/stop_stop.wav diff --git a/shared/KWD/acsdkKWDImplementations/privateInclude/acsdkKWDImplementations/KeywordDetectorStateNotifier.h b/shared/KWD/acsdkKWDImplementations/privateInclude/acsdkKWDImplementations/KeywordDetectorStateNotifier.h new file mode 100644 index 0000000000..ef5bd2ee81 --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/privateInclude/acsdkKWDImplementations/KeywordDetectorStateNotifier.h @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKKWDIMPLEMENTATIONS_KEYWORDDETECTORSTATENOTIFIER_H_ +#define ACSDKKWDIMPLEMENTATIONS_KEYWORDDETECTORSTATENOTIFIER_H_ + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkKWDImplementations { + +/** + * Relays notifications related to KeywordDetectorState. + */ +class KeywordDetectorStateNotifier + : public acsdkNotifier::Notifier { +public: + /** + * Factory method. + * @return A new instance of @c KeywordDetectorStateNotifierInterface. + */ + static std::shared_ptr + createKeywordDetectorStateNotifierInterface(); +}; + +} // namespace acsdkKWDImplementations +} // namespace alexaClientSDK + +#endif // ACSDKKWDIMPLEMENTATIONS_KEYWORDDETECTORSTATENOTIFIER_H_ diff --git a/shared/KWD/acsdkKWDImplementations/privateInclude/acsdkKWDImplementations/KeywordNotifier.h b/shared/KWD/acsdkKWDImplementations/privateInclude/acsdkKWDImplementations/KeywordNotifier.h new file mode 100644 index 0000000000..ff3788485b --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/privateInclude/acsdkKWDImplementations/KeywordNotifier.h @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKKWDIMPLEMENTATIONS_KEYWORDNOTIFIER_H_ +#define ACSDKKWDIMPLEMENTATIONS_KEYWORDNOTIFIER_H_ + +#include + +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkKWDImplementations { + +/** + * Relays notifications related to keyword. + */ +class KeywordNotifier : public acsdkNotifier::Notifier { +public: + /** + * Factory method. + * @return A new instance of @c KeywordNotifierInterface. + */ + static std::shared_ptr createKeywordNotifierInterface(); +}; + +} // namespace acsdkKWDImplementations +} // namespace alexaClientSDK + +#endif // ACSDKKWDIMPLEMENTATIONS_KEYWORDNOTIFIER_H_ diff --git a/KWD/src/AbstractKeywordDetector.cpp b/shared/KWD/acsdkKWDImplementations/src/AbstractKeywordDetector.cpp similarity index 66% rename from KWD/src/AbstractKeywordDetector.cpp rename to shared/KWD/acsdkKWDImplementations/src/AbstractKeywordDetector.cpp index b2a4180ac0..5bcc6aa423 100644 --- a/KWD/src/AbstractKeywordDetector.cpp +++ b/shared/KWD/acsdkKWDImplementations/src/AbstractKeywordDetector.cpp @@ -15,10 +15,12 @@ #include -#include "KWD/AbstractKeywordDetector.h" +#include "acsdkKWDImplementations/AbstractKeywordDetector.h" +#include "acsdkKWDImplementations/KeywordDetectorStateNotifier.h" +#include "acsdkKWDImplementations/KeywordNotifier.h" namespace alexaClientSDK { -namespace kwd { +namespace acsdkKWDImplementations { using namespace avsCommon; using namespace avsCommon::avs; @@ -35,33 +37,54 @@ static const std::string TAG("AbstractKeywordDetector"); #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) void AbstractKeywordDetector::addKeyWordObserver(std::shared_ptr keyWordObserver) { - std::lock_guard lock(m_keyWordObserversMutex); - m_keyWordObservers.insert(keyWordObserver); + m_keywordNotifier->addObserver(keyWordObserver); } void AbstractKeywordDetector::removeKeyWordObserver(std::shared_ptr keyWordObserver) { - std::lock_guard lock(m_keyWordObserversMutex); - m_keyWordObservers.erase(keyWordObserver); + m_keywordNotifier->removeObserver(keyWordObserver); } void AbstractKeywordDetector::addKeyWordDetectorStateObserver( std::shared_ptr keyWordDetectorStateObserver) { - std::lock_guard lock(m_keyWordDetectorStateObserversMutex); - m_keyWordDetectorStateObservers.insert(keyWordDetectorStateObserver); + m_keywordDetectorStateNotifier->addObserver(keyWordDetectorStateObserver); } void AbstractKeywordDetector::removeKeyWordDetectorStateObserver( std::shared_ptr keyWordDetectorStateObserver) { - std::lock_guard lock(m_keyWordDetectorStateObserversMutex); - m_keyWordDetectorStateObservers.erase(keyWordDetectorStateObserver); + m_keywordDetectorStateNotifier->removeObserver(keyWordDetectorStateObserver); } AbstractKeywordDetector::AbstractKeywordDetector( std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers) : - m_keyWordObservers{keyWordObservers}, - m_keyWordDetectorStateObservers{keyWordDetectorStateObservers}, m_detectorState{KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED} { + m_keywordNotifier = KeywordNotifier::createKeywordNotifierInterface(); + for (auto kwObserver : keyWordObservers) { + m_keywordNotifier->addObserver(kwObserver); + } + + m_keywordDetectorStateNotifier = KeywordDetectorStateNotifier::createKeywordDetectorStateNotifierInterface(); + m_keywordDetectorStateNotifier->setAddObserverFunction( + [this](std::shared_ptr stateObserver) { + std::lock_guard lock(m_detectorStateMutex); + stateObserver->onStateChanged(m_detectorState); + }); + for (auto kwdStateObserver : keyWordDetectorStateObservers) { + m_keywordDetectorStateNotifier->addObserver(kwdStateObserver); + } +} + +AbstractKeywordDetector::AbstractKeywordDetector( + std::shared_ptr keywordNotifier, + std::shared_ptr keyWordDetectorStateNotifier) : + m_keywordNotifier{keywordNotifier}, + m_keywordDetectorStateNotifier{keyWordDetectorStateNotifier}, + m_detectorState{KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED} { + m_keywordDetectorStateNotifier->setAddObserverFunction( + [this](std::shared_ptr stateObserver) { + std::lock_guard lock(m_detectorStateMutex); + stateObserver->onStateChanged(m_detectorState); + }); } void AbstractKeywordDetector::notifyKeyWordObservers( @@ -70,20 +93,22 @@ void AbstractKeywordDetector::notifyKeyWordObservers( AudioInputStream::Index beginIndex, AudioInputStream::Index endIndex, std::shared_ptr> KWDMetadata) const { - std::lock_guard lock(m_keyWordObserversMutex); - for (auto keyWordObserver : m_keyWordObservers) { - keyWordObserver->onKeyWordDetected(stream, keyword, beginIndex, endIndex, KWDMetadata); - } + m_keywordNotifier->notifyObservers( + [stream, keyword, beginIndex, endIndex, KWDMetadata]( + std::shared_ptr observer) { + observer->onKeyWordDetected(stream, keyword, beginIndex, endIndex, KWDMetadata); + }); } void AbstractKeywordDetector::notifyKeyWordDetectorStateObservers( KeyWordDetectorStateObserverInterface::KeyWordDetectorState state) { + std::lock_guard lock(m_detectorStateMutex); if (m_detectorState != state) { m_detectorState = state; - std::lock_guard lock(m_keyWordDetectorStateObserversMutex); - for (auto keyWordDetectorStateObserver : m_keyWordDetectorStateObservers) { - keyWordDetectorStateObserver->onStateChanged(m_detectorState); - } + m_keywordDetectorStateNotifier->notifyObservers( + [state](std::shared_ptr observer) { + observer->onStateChanged(state); + }); } } @@ -149,5 +174,5 @@ bool AbstractKeywordDetector::isByteswappingRequired(avsCommon::utils::AudioForm return isPlatformLittleEndian != isFormatLittleEndian; } -} // namespace kwd +} // namespace acsdkKWDImplementations } // namespace alexaClientSDK diff --git a/shared/KWD/acsdkKWDImplementations/src/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/src/CMakeLists.txt new file mode 100644 index 0000000000..d7d75e6d71 --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/src/CMakeLists.txt @@ -0,0 +1,23 @@ +add_definitions("-DACSDK_LOG_MODULE=acsdkKWDImplementations") + +add_library(acsdkKWDImplementations + AbstractKeywordDetector.cpp + KWDNotifierFactories.cpp + KeywordDetectorStateNotifier.cpp + KeywordNotifier.cpp) + +target_include_directories(acsdkKWDImplementations PRIVATE + "${acsdkKWDImplementations_SOURCE_DIR}/privateInclude" +) + +target_include_directories(acsdkKWDImplementations PUBLIC + "${acsdkKWDImplementations_SOURCE_DIR}/include" +) + +target_link_libraries(acsdkKWDImplementations + acsdkKWDInterfaces + acsdkNotifier + AVSCommon) + +# install target +asdk_install() diff --git a/shared/KWD/acsdkKWDImplementations/src/KWDNotifierFactories.cpp b/shared/KWD/acsdkKWDImplementations/src/KWDNotifierFactories.cpp new file mode 100644 index 0000000000..0d5997e2b1 --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/src/KWDNotifierFactories.cpp @@ -0,0 +1,33 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkKWDImplementations/KeywordDetectorStateNotifier.h" +#include "acsdkKWDImplementations/KeywordNotifier.h" +#include "acsdkKWDImplementations/KWDNotifierFactories.h" + +namespace alexaClientSDK { +namespace acsdkKWDImplementations { + +std::shared_ptr KWDNotifierFactories:: + createKeywordDetectorStateNotifier() { + return KeywordDetectorStateNotifier::createKeywordDetectorStateNotifierInterface(); +} + +std::shared_ptr KWDNotifierFactories::createKeywordNotifier() { + return KeywordNotifier::createKeywordNotifierInterface(); +} + +} // namespace acsdkKWDImplementations +} // namespace alexaClientSDK diff --git a/shared/KWD/acsdkKWDImplementations/src/KeywordDetectorStateNotifier.cpp b/shared/KWD/acsdkKWDImplementations/src/KeywordDetectorStateNotifier.cpp new file mode 100644 index 0000000000..b515463e61 --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/src/KeywordDetectorStateNotifier.cpp @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkKWDImplementations/KeywordDetectorStateNotifier.h" + +namespace alexaClientSDK { +namespace acsdkKWDImplementations { + +std::shared_ptr KeywordDetectorStateNotifier:: + createKeywordDetectorStateNotifierInterface() { + return std::make_shared(); +} + +} // namespace acsdkKWDImplementations +} // namespace alexaClientSDK diff --git a/shared/KWD/acsdkKWDImplementations/src/KeywordNotifier.cpp b/shared/KWD/acsdkKWDImplementations/src/KeywordNotifier.cpp new file mode 100644 index 0000000000..eb545a211c --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/src/KeywordNotifier.cpp @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkKWDImplementations/KeywordNotifier.h" + +namespace alexaClientSDK { +namespace acsdkKWDImplementations { + +std::shared_ptr KeywordNotifier::createKeywordNotifierInterface() { + return std::make_shared(); +} + +} // namespace acsdkKWDImplementations +} // namespace alexaClientSDK diff --git a/shared/KWD/acsdkKWDImplementations/test/AbstractKeywordDetectorTest.cpp b/shared/KWD/acsdkKWDImplementations/test/AbstractKeywordDetectorTest.cpp new file mode 100644 index 0000000000..649c7a6ae6 --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/test/AbstractKeywordDetectorTest.cpp @@ -0,0 +1,380 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "acsdkKWDImplementations/AbstractKeywordDetector.h" + +namespace alexaClientSDK { +namespace acsdkKWDImplementations { +namespace test { + +/// Reader timeout. +static constexpr std::chrono::milliseconds TIMEOUT{1000}; + +/// The size of reader buffer is one page long. +static constexpr size_t TEST_BUFFER_SIZE{4096u}; + +// No Words read from buffer. +static constexpr ssize_t ZERO_WORDS_READ = 0; + +// Number of words to read from buffer. +static constexpr ssize_t WORDS_TO_READ = 1; + +using namespace ::testing; + +/// A test observer that mocks out the KeyWordObserverInterface##onKeyWordDetected() call. +class MockKeyWordObserver : public avsCommon::sdkInterfaces::KeyWordObserverInterface { +public: + MOCK_METHOD5( + onKeyWordDetected, + void( + std::shared_ptr stream, + std::string keyword, + avsCommon::avs::AudioInputStream::Index beginIndex, + avsCommon::avs::AudioInputStream::Index endIndex, + std::shared_ptr> KWDMetadata)); +}; + +/// A test observer that mocks out the KeyWordDetectorStateObserverInterface##onStateChanged() call. +class MockStateObserver : public avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface { +public: + MOCK_METHOD1( + onStateChanged, + void(avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState + keyWordDetectorState)); +}; + +/// A test KeywordNotifier. +class MockKeywordNotifier + : public acsdkNotifierInterfaces::test::MockNotifier {}; + +/// A test KeywordDetectorStateNotifier. +class MockKeywordDetectorStateNotifier + : public acsdkNotifierInterfaces::test::MockNotifier< + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface> {}; + +/** + * A mock Keyword Detector that inherits from KeyWordDetector. + */ +class MockKeyWordDetector : public AbstractKeywordDetector { +public: + /** + * Constructor. + * + * @param keyWordNotifier The object with which to notifiy observers of keyword detections. + * @param KeyWordDetectorStateNotifier The object with which to notify observers of state changes in the engine. + */ + MockKeyWordDetector( + std::shared_ptr keywordNotifier, + std::shared_ptr keywordDetectorStateNotifier) : + AbstractKeywordDetector(keywordNotifier, keywordDetectorStateNotifier) { + } + /** + * Notifies all KeyWordObservers with dummy values. + */ + void sendKeyWordCallToObservers(); + + /** + * Notifies all KeyWordDetectorStateObservers. + * + * @param state The state to notify observers of. + */ + void sendStateChangeCallObservers( + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState state); + + ssize_t protectedReadFromStream( + std::shared_ptr reader, + std::shared_ptr stream, + void* buf, + size_t nWords, + std::chrono::milliseconds timeout, + bool* errorOccurred); + + static bool protectedIsByteswappingRequired(avsCommon::utils::AudioFormat audioFormat); +}; + +void MockKeyWordDetector::sendKeyWordCallToObservers() { + notifyKeyWordObservers(nullptr, "ALEXA", 0, 0); +} + +void MockKeyWordDetector::sendStateChangeCallObservers( + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState state) { + notifyKeyWordDetectorStateObservers(state); +} + +ssize_t MockKeyWordDetector::protectedReadFromStream( + std::shared_ptr reader, + std::shared_ptr stream, + void* buf, + size_t nWords, + std::chrono::milliseconds timeout, + bool* errorOccurred) { + return readFromStream(reader, stream, buf, nWords, timeout, errorOccurred); +} + +bool MockKeyWordDetector::protectedIsByteswappingRequired(avsCommon::utils::AudioFormat audioFormat) { + return isByteswappingRequired(audioFormat); +} + +class AbstractKeyWordDetectorTest : public ::testing::Test { +protected: + std::shared_ptr m_detector; + std::shared_ptr m_keyWordObserver; + std::shared_ptr m_stateObserver; + std::shared_ptr m_keywordNotifier; + std::shared_ptr m_keywordDetectorStateNotifier; + std::shared_ptr m_buffer; + std::unique_ptr m_sds; + std::unique_ptr m_writer; + std::unique_ptr m_reader; + + virtual void SetUp() { + m_keywordNotifier = std::make_shared(); + m_keywordDetectorStateNotifier = std::make_shared(); + m_detector = std::make_shared(m_keywordNotifier, m_keywordDetectorStateNotifier); + m_keyWordObserver = std::make_shared(); + m_stateObserver = std::make_shared(); + + m_buffer = std::make_shared(TEST_BUFFER_SIZE); + m_sds = avsCommon::avs::AudioInputStream::create(m_buffer, 2, 1); + ASSERT_TRUE(m_sds); + m_writer = m_sds->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::BLOCKING); + ASSERT_TRUE(m_writer); + m_reader = m_sds->createReader(avsCommon::avs::AudioInputStream::Reader::Policy::BLOCKING); + ASSERT_TRUE(m_reader); + // Make calls to KWDStateNotifier pass through to observer. + ON_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_)) + .WillByDefault(Invoke( + [this](std::function&)> + notifyFn) { notifyFn(m_stateObserver); })); + + // Make calls to KWNotifier pass through to observer. + ON_CALL(*m_keywordNotifier, notifyObservers(_)) + .WillByDefault(Invoke( + [this](std::function&)> + notifyFn) { notifyFn(m_keyWordObserver); })); + + // Initialize Detector State to Active from Closed. + m_detector->sendStateChangeCallObservers( + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); + } +}; + +/** + * Tests adding a Keyword Observer to the KWD. + */ +TEST_F(AbstractKeyWordDetectorTest, test_addKeyWordObserver) { + EXPECT_CALL(*m_keywordNotifier, addObserver(_)).Times(1); + m_detector->addKeyWordObserver(m_keyWordObserver); +} + +/** + * Tests Notifying a Keyword Observer. + */ +TEST_F(AbstractKeyWordDetectorTest, test_notifyKeyWordObserver) { + // add kw observer + EXPECT_CALL(*m_keywordNotifier, addObserver(_)).Times(1); + m_detector->addKeyWordObserver(m_keyWordObserver); + + EXPECT_CALL(*m_keywordNotifier, notifyObservers(_)).Times(1); + EXPECT_CALL(*m_keyWordObserver, onKeyWordDetected(_, _, _, _, _)).Times(1); + m_detector->sendKeyWordCallToObservers(); +} + +/** + * Tests removing a Keyword Observer to the KWD. + */ +TEST_F(AbstractKeyWordDetectorTest, test_removeKeyWordObserver) { + EXPECT_CALL(*m_keywordNotifier, addObserver(_)).Times(1); + m_detector->addKeyWordObserver(m_keyWordObserver); + + EXPECT_CALL(*m_keywordNotifier, removeObserver(_)).Times(1); + m_detector->removeKeyWordObserver(m_keyWordObserver); +} + +/** + * Tests adding a Detector State Observer to the KWD. + */ +TEST_F(AbstractKeyWordDetectorTest, test_addStateObserver) { + EXPECT_CALL(*m_keywordDetectorStateNotifier, addObserver(_)).Times(1); + m_detector->addKeyWordDetectorStateObserver(m_stateObserver); +} + +/** + * Tests notifying a KeywordDetectorStateObserver. + */ +TEST_F(AbstractKeyWordDetectorTest, test_notifyStateObserver) { + EXPECT_CALL(*m_keywordDetectorStateNotifier, addObserver(_)).Times(1); + m_detector->addKeyWordDetectorStateObserver(m_stateObserver); + + EXPECT_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_)).Times(1); + EXPECT_CALL( + *m_stateObserver, + onStateChanged( + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED)) + .Times(1); + m_detector->sendStateChangeCallObservers( + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED); +} + +/** + * Tests removing a Detector State Observer to the KWD. + */ +TEST_F(AbstractKeyWordDetectorTest, test_removeStateObserver) { + EXPECT_CALL(*m_keywordDetectorStateNotifier, addObserver(_)).Times(1); + m_detector->addKeyWordDetectorStateObserver(m_stateObserver); + + EXPECT_CALL(*m_keywordDetectorStateNotifier, removeObserver(_)).Times(1); + m_detector->removeKeyWordDetectorStateObserver(m_stateObserver); +} + +/** + * Tests that Detector State Observers aren't notified if there is no change in state. + */ +TEST_F(AbstractKeyWordDetectorTest, test_observersDontGetNotifiedOfSameStateTwice) { + m_detector->addKeyWordDetectorStateObserver(m_stateObserver); + + EXPECT_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_)).Times(1); + EXPECT_CALL( + *m_stateObserver, + onStateChanged( + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED)) + .Times(1); + m_detector->sendStateChangeCallObservers( + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED); + + EXPECT_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_)).Times(0); + EXPECT_CALL(*m_stateObserver, onStateChanged(_)).Times(0); + m_detector->sendStateChangeCallObservers( + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED); +} + +/** + * Tests if byte swapping of the audio stream is required by comparing format endianness to platform endianness. + */ +TEST_F(AbstractKeyWordDetectorTest, test_isByteSwappingRequired) { + int num = 1; + char* firstBytePtr = reinterpret_cast(&num); + avsCommon::utils::AudioFormat audioFormat; + + if (*firstBytePtr == 1) { + // Test Platform is little endian. + audioFormat.endianness = avsCommon::utils::AudioFormat::Endianness::LITTLE; + EXPECT_FALSE(m_detector->protectedIsByteswappingRequired(audioFormat)); + audioFormat.endianness = avsCommon::utils::AudioFormat::Endianness::BIG; + EXPECT_TRUE(m_detector->protectedIsByteswappingRequired(audioFormat)); + } else { + // Test Platform is big endian. + audioFormat.endianness = avsCommon::utils::AudioFormat::Endianness::LITTLE; + EXPECT_TRUE(m_detector->protectedIsByteswappingRequired(audioFormat)); + audioFormat.endianness = avsCommon::utils::AudioFormat::Endianness::BIG; + EXPECT_FALSE(m_detector->protectedIsByteswappingRequired(audioFormat)); + } +} + +/** + * Tests that KWD is able to read from stream successfully. + */ +TEST_F(AbstractKeyWordDetectorTest, test_readFromStreamSuccessful) { + // Write random data into the m_sds. + std::vector randomData(50, 0); + m_writer->write(randomData.data(), randomData.size()); + + // Attempt to read from stream with no errors occuring + bool errorOccurred = false; + EXPECT_EQ( + WORDS_TO_READ, + m_detector->protectedReadFromStream( + std::move(m_reader), std::move(m_sds), m_buffer->data(), WORDS_TO_READ, TIMEOUT, &errorOccurred)); + EXPECT_FALSE(errorOccurred); +} + +/** + * Test reading from stream while the stream is closed. + */ +TEST_F(AbstractKeyWordDetectorTest, test_readFromStreamWhileStreamClosed) { + m_reader->close(); + // Attempt to read a word from the closed stream. + // Expect that zero words are read and that an error has occured. + // Expect that state observers are notified of detector state change. + bool errorOccurred = false; + EXPECT_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_)).Times(1); + EXPECT_CALL( + *m_stateObserver, + onStateChanged( + avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED)) + .Times(1); + EXPECT_EQ( + ZERO_WORDS_READ, + m_detector->protectedReadFromStream( + std::move(m_reader), std::move(m_sds), m_buffer->data(), WORDS_TO_READ, TIMEOUT, &errorOccurred)); + EXPECT_TRUE(errorOccurred); +} + +/** + * Test reading from stream when the m_buffer is overrun. + */ +TEST_F(AbstractKeyWordDetectorTest, test_readFromStreamBufferOverrun) { + // Create Reader and Writers for test + auto buffer = std::make_shared(TEST_BUFFER_SIZE); + auto sds = avsCommon::avs::AudioInputStream::create(buffer, 2, 1); + ASSERT_TRUE(sds); + auto writer = sds->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE); + ASSERT_TRUE(writer); + auto reader = sds->createReader(avsCommon::avs::AudioInputStream::Reader::Policy::NONBLOCKING); + ASSERT_TRUE(reader); + + // Write to buffer twice to overrun it. + std::vector randomData(TEST_BUFFER_SIZE, 0); + writer->write(randomData.data(), randomData.size()); + writer->write(randomData.data(), randomData.size()); + + bool errorOccurred = false; + EXPECT_EQ( + avsCommon::avs::AudioInputStream::Reader::Error::OVERRUN, + m_detector->protectedReadFromStream( + std::move(reader), std::move(sds), buffer->data(), WORDS_TO_READ, TIMEOUT, &errorOccurred)); + EXPECT_FALSE(errorOccurred); +} + +/** + * Test reading from stream times out. + */ +TEST_F(AbstractKeyWordDetectorTest, test_readFromStreamTimedOut) { + bool errorOccurred = false; + + EXPECT_EQ( + avsCommon::avs::AudioInputStream::Reader::Error::TIMEDOUT, + m_detector->protectedReadFromStream( + std::move(m_reader), std::move(m_sds), m_buffer->data(), WORDS_TO_READ, TIMEOUT, &errorOccurred)); + EXPECT_FALSE(errorOccurred); +} + +} // namespace test +} // namespace acsdkKWDImplementations +} // namespace alexaClientSDK diff --git a/shared/KWD/acsdkKWDImplementations/test/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/test/CMakeLists.txt new file mode 100644 index 0000000000..524bbc0679 --- /dev/null +++ b/shared/KWD/acsdkKWDImplementations/test/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +set(LIBS acsdkKWDImplementations NotifierTestLib) + +discover_unit_tests("${acsdkKWDImplementations_SOURCE_DIR}/include" "${LIBS}") diff --git a/shared/KWD/acsdkKWDInterfaces/CMakeLists.txt b/shared/KWD/acsdkKWDInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..b1d40777ee --- /dev/null +++ b/shared/KWD/acsdkKWDInterfaces/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.1) +project(acsdkKWDInterfaces LANGUAGES CXX) + +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +add_library(acsdkKWDInterfaces INTERFACE) + +target_include_directories(acsdkKWDInterfaces INTERFACE "${acsdkKWDInterfaces_SOURCE_DIR}/include") + +target_link_libraries(acsdkKWDInterfaces INTERFACE acsdkNotifierInterfaces) + +# install interface +asdk_install_interface() diff --git a/shared/KWD/acsdkKWDInterfaces/include/acsdkKWDInterfaces/KeywordDetectorStateNotifierInterface.h b/shared/KWD/acsdkKWDInterfaces/include/acsdkKWDInterfaces/KeywordDetectorStateNotifierInterface.h new file mode 100644 index 0000000000..40693250f2 --- /dev/null +++ b/shared/KWD/acsdkKWDInterfaces/include/acsdkKWDInterfaces/KeywordDetectorStateNotifierInterface.h @@ -0,0 +1,36 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKKWDINTERFACES_KEYWORDDETECTORSTATENOTIFIERINTERFACE_H_ +#define ACSDKKWDINTERFACES_KEYWORDDETECTORSTATENOTIFIERINTERFACE_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkKWDInterfaces { + +/** + * Interface for registering to observe Bluetooth notifications. + */ +using KeywordDetectorStateNotifierInterface = + acsdkNotifierInterfaces::NotifierInterface; + +} // namespace acsdkKWDInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKKWDINTERFACES_KEYWORDDETECTORSTATENOTIFIERINTERFACE_H_ diff --git a/shared/KWD/acsdkKWDInterfaces/include/acsdkKWDInterfaces/KeywordNotifierInterface.h b/shared/KWD/acsdkKWDInterfaces/include/acsdkKWDInterfaces/KeywordNotifierInterface.h new file mode 100644 index 0000000000..2e13d7a358 --- /dev/null +++ b/shared/KWD/acsdkKWDInterfaces/include/acsdkKWDInterfaces/KeywordNotifierInterface.h @@ -0,0 +1,36 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKKWDINTERFACES_KEYWORDNOTIFIERINTERFACE_H_ +#define ACSDKKWDINTERFACES_KEYWORDNOTIFIERINTERFACE_H_ + +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkKWDInterfaces { + +/** + * Interface for registering to observe Bluetooth notifications. + */ +using KeywordNotifierInterface = + acsdkNotifierInterfaces::NotifierInterface; + +} // namespace acsdkKWDInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKKWDINTERFACES_KEYWORDNOTIFIERINTERFACE_H_ diff --git a/KWD/KWDProvider/CMakeLists.txt b/shared/KWD/acsdkKWDProvider/CMakeLists.txt similarity index 100% rename from KWD/KWDProvider/CMakeLists.txt rename to shared/KWD/acsdkKWDProvider/CMakeLists.txt diff --git a/KWD/KWDProvider/include/KWDProvider/KeywordDetectorProvider.h b/shared/KWD/acsdkKWDProvider/include/acsdkKWDProvider/KWDProvider/KeywordDetectorProvider.h similarity index 50% rename from KWD/KWDProvider/include/KWDProvider/KeywordDetectorProvider.h rename to shared/KWD/acsdkKWDProvider/include/acsdkKWDProvider/KWDProvider/KeywordDetectorProvider.h index 3413d43e88..3385200eb9 100644 --- a/KWD/KWDProvider/include/KWDProvider/KeywordDetectorProvider.h +++ b/shared/KWD/acsdkKWDProvider/include/acsdkKWDProvider/KWDProvider/KeywordDetectorProvider.h @@ -13,18 +13,18 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_KWD_KWDPROVIDER_INCLUDE_KWDPROVIDER_KEYWORDDETECTORPROVIDER_H_ -#define ALEXA_CLIENT_SDK_KWD_KWDPROVIDER_INCLUDE_KWDPROVIDER_KEYWORDDETECTORPROVIDER_H_ +#ifndef ACSDKKWDPROVIDER_KWDPROVIDER_KEYWORDDETECTORPROVIDER_H_ +#define ACSDKKWDPROVIDER_KWDPROVIDER_KEYWORDDETECTORPROVIDER_H_ #include #include #include -#include +#include #include -#include #include -#include +#include +#include namespace alexaClientSDK { namespace kwd { @@ -35,27 +35,56 @@ namespace kwd { class KeywordDetectorProvider { public: /** - * Creates a @c KeywordDetector. + * Creates a @c KeywordDetector. The @c KeywordDetector that is created is determined by the create method + * registered by the @c KWDRegistration Class. Only one @c KeywordDetector can be registered at a time to the + * @c KeywordDetectorProvider. * * @param stream The stream of audio data. This should be formatted in LPCM encoded with 16 bits per sample and * have a sample rate of 16 kHz. Additionally, the data should be in little endian format. * @param audioFormat The format of the audio data located within the stream. * @param keyWordObservers The observers to notify of keyword detections. * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. - * @param pathToInputFolder The path to the inputs folder containing data files needed by this application. * * @return An @c KeywordDetector based on CMake configurations on success or @c nullptr if creation failed. */ - static std::unique_ptr create( + static std::unique_ptr create( std::shared_ptr stream, avsCommon::utils::AudioFormat audioFormat, std::unordered_set> keyWordObservers, std::unordered_set> - keyWordDetectorStateObservers, - const std::string& pathToInputFolder); + keyWordDetectorStateObservers); + + // Signature of functions to create an AbstractKeywordDetector. + using KWDCreateMethod = std::unique_ptr (*)( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> + keyWordDetectorStateObservers); + + /** + * Class that enables registration of a keyword detector's create functions. + */ + class KWDRegistration { + public: + /** + * Register an @c AbstractKeywordDetector to be returned by @c KeywordDetectorProvider. If a @c KeywordDetector + * is already registered then this will log an error and do nothing. + * + * @param createFunction The function to use to create instances of the specified @c AbstractKeywordDetector. + */ + KWDRegistration(KWDCreateMethod createFunction); + }; + +private: + /** + * The keyword detector create method registered. @c m_KWDCreateMethod is initialized by the @c KWDRegistration + * class constructor. + */ + static KWDCreateMethod m_KWDCreateMethod; }; } // namespace kwd } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_KWD_KWDPROVIDER_INCLUDE_KWDPROVIDER_KEYWORDDETECTORPROVIDER_H_ +#endif // ACSDKKWDPROVIDER_KWDPROVIDER_KEYWORDDETECTORPROVIDER_H_ diff --git a/shared/KWD/acsdkKWDProvider/src/CMakeLists.txt b/shared/KWD/acsdkKWDProvider/src/CMakeLists.txt new file mode 100644 index 0000000000..3b84401e87 --- /dev/null +++ b/shared/KWD/acsdkKWDProvider/src/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(KeywordDetectorProvider + KeywordDetectorProvider.cpp + ${KWD_ADAPTER_REGISTRATION_FILE}) + +target_include_directories(KeywordDetectorProvider PUBLIC + "${KeywordDetectorProvider_SOURCE_DIR}/include/acsdkKWDProvider") + +target_link_libraries(KeywordDetectorProvider AVSCommon acsdkKWDImplementations) + +if(TARGET_KWD_LIB) + target_link_libraries(KeywordDetectorProvider ${TARGET_KWD_LIB}) +else() + message(FATAL_ERROR "No KWD Target set") +endif() + +# install target +asdk_install() diff --git a/shared/KWD/acsdkKWDProvider/src/KeywordDetectorProvider.cpp b/shared/KWD/acsdkKWDProvider/src/KeywordDetectorProvider.cpp new file mode 100644 index 0000000000..3bdbc1c448 --- /dev/null +++ b/shared/KWD/acsdkKWDProvider/src/KeywordDetectorProvider.cpp @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "KWDProvider/KeywordDetectorProvider.h" + +using namespace alexaClientSDK; +using namespace alexaClientSDK::kwd; + +/// String to identify log entries originating from this file. +static const std::string TAG{"KeywordDetectorProvider"}; + +/** + * 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) + +KeywordDetectorProvider::KWDCreateMethod m_kwdCreateFunction; + +KeywordDetectorProvider::KWDRegistration::KWDRegistration(KWDCreateMethod createFunction) { + if (m_kwdCreateFunction) { + ACSDK_ERROR(LX(__func__).m("KeywordDetector already registered")); + return; + } + m_kwdCreateFunction = createFunction; +} + +std::unique_ptr KeywordDetectorProvider::create( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> + keyWordDetectorStateObservers) { + if (m_kwdCreateFunction) { + return m_kwdCreateFunction(stream, audioFormat, keyWordObservers, keyWordDetectorStateObservers); + } else { + ACSDK_ERROR(LX(__func__).m("KeywordDetector create not found")); + return nullptr; + } +} diff --git a/shared/acsdkCommunication/CMakeLists.txt b/shared/acsdkCommunication/CMakeLists.txt new file mode 100644 index 0000000000..804ef77d3c --- /dev/null +++ b/shared/acsdkCommunication/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.1) + +project(acsdkCommunication LANGUAGES CXX) + +add_library(acsdkCommunication INTERFACE) + +target_include_directories(acsdkCommunication INTERFACE + "${acsdkCommunication_SOURCE_DIR}/include" + ) + +target_link_libraries(acsdkCommunication INTERFACE acsdkCommunicationInterfaces acsdkNotifier) + +# install interface +asdk_install_interface() + +add_subdirectory("test") \ No newline at end of file diff --git a/shared/acsdkCommunication/include/acsdkCommunication/AlwaysTrueCommunicationValidator.h b/shared/acsdkCommunication/include/acsdkCommunication/AlwaysTrueCommunicationValidator.h new file mode 100644 index 0000000000..b1ceb8fb6c --- /dev/null +++ b/shared/acsdkCommunication/include/acsdkCommunication/AlwaysTrueCommunicationValidator.h @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCOMMUNICATION_ALWAYSTRUECOMMUNICATIONVALIDATOR_H_ +#define ACSDKCOMMUNICATION_ALWAYSTRUECOMMUNICATIONVALIDATOR_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkCommunication { + +/** + * This is an implementation of the CommunicationPropertyValidatorInterface, that will always result to true. + * This class is meant to offer implementors a quick way to create Writable properties without creating a new + * CommunicationPropertyWriter if they don't want to validate the value being written. + */ +template +class AlwaysTrueCommunicationValidator + : public acsdkCommunicationInterfaces::CommunicationPropertyValidatorInterface { +public: + /** + * Default Constructor + */ + AlwaysTrueCommunicationValidator() = default; + + /** + * Default Destructor + */ + ~AlwaysTrueCommunicationValidator() override = default; + + /// @name CommunicationPropertyValidatorInterface methods + /// @{ + bool validateWriteRequest(const std::string& propertyName, T newValue) override { + return true; + } + /// @} +}; +} // namespace acsdkCommunication +} // namespace alexaClientSDK + +#endif // ACSDKCOMMUNICATION_ALWAYSTRUECOMMUNICATIONVALIDATOR_H_ diff --git a/shared/acsdkCommunication/include/acsdkCommunication/InMemoryCommunicationInvokeHandler.h b/shared/acsdkCommunication/include/acsdkCommunication/InMemoryCommunicationInvokeHandler.h new file mode 100644 index 0000000000..3844799b72 --- /dev/null +++ b/shared/acsdkCommunication/include/acsdkCommunication/InMemoryCommunicationInvokeHandler.h @@ -0,0 +1,146 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCOMMUNICATION_INMEMORYCOMMUNICATIONINVOKEHANDLER_H_ +#define ACSDKCOMMUNICATION_INMEMORYCOMMUNICATIONINVOKEHANDLER_H_ + +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCommunication { + +/** + * The in memory implementation of the CommunicationInvokeHandlerInterface. This is a thread safe class that provides + * users the ability to register functions and have them be invoked by other components. + */ +template +class InMemoryCommunicationInvokeHandler + : public virtual acsdkCommunicationInterfaces::CommunicationInvokeHandlerInterface { +public: + /// @name CommunicationInvokeHandlerInterface methods + /// @{ + bool registerFunction( + const std::string& name, + std::shared_ptr> + functionImplementation) override; + alexaClientSDK::avsCommon::utils::error::SuccessResult invoke(const std::string& name, Types... args) + override; + bool deregister( + const std::string& name, + const std::shared_ptr>& + functionImplementation) override; + /// }@ + +private: + // map of names to functions. + std::unordered_map< + std::string, + std::weak_ptr>> + m_functions; + // guard to protect the functions. + std::mutex m_functionsGuard; +}; +template +bool InMemoryCommunicationInvokeHandler::registerFunction( + const std::string& name, + std::shared_ptr> + functionImplementation) { + std::lock_guard lock(m_functionsGuard); + auto it = m_functions.find(name); + if (it != m_functions.end()) { + if (!it->second.expired()) { + ACSDK_ERROR( + alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationHandler", "registerFunction") + .m("Function is already Registered") + .d("function", name)); + return false; + } + // Erase if function is a nullptr; + m_functions.erase(it); + } + if (!functionImplementation) { + ACSDK_ERROR( + alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationHandler", "registerFunction") + .m("FunctionImplementation is a nullptr") + .d("function", name)); + return false; + } + auto weakFunction = std::weak_ptr>( + functionImplementation); + + m_functions.insert({name, weakFunction}); + return true; +} +template +alexaClientSDK::avsCommon::utils::error::SuccessResult InMemoryCommunicationInvokeHandler< + ReturnType, + Types...>::invoke(const std::string& name, Types... args) { + auto it = m_functions.find(name); + if (it == m_functions.end()) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationHandler", "invoke") + .m("Function is not Registered") + .d("function", name)); + return alexaClientSDK::avsCommon::utils::error::SuccessResult::failure(); + } + auto function = it->second.lock(); + if (!function) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationHandler", "invoke") + .m("Function is expired") + .d("function", name)); + std::lock_guard lock(m_functionsGuard); + m_functions.erase(it); + return alexaClientSDK::avsCommon::utils::error::SuccessResult::failure(); + } + auto result = function->functionToBeInvoked(name, args...); + return alexaClientSDK::avsCommon::utils::error::SuccessResult::success(result); +} + +template +bool InMemoryCommunicationInvokeHandler::deregister( + const std::string& name, + const std::shared_ptr>& + functionImplementation) { + if (!functionImplementation) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationHandler", "deregister") + .m("FunctionImplementation is a nullptr") + .d("function", name)); + return false; + } + std::lock_guard lock(m_functionsGuard); + auto it = m_functions.find(name); + if (it == m_functions.end()) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationHandler", "deregister") + .m("Function is not Registered") + .d("function", name)); + return false; + } + if (it->second.lock() != functionImplementation) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationHandler", "deregister") + .m("Function is Registered but does not match") + .d("function", name)); + return false; + } + m_functions.erase(it); + return true; +} +} // namespace acsdkCommunication +} // namespace alexaClientSDK + +#endif // ACSDKCOMMUNICATION_INMEMORYCOMMUNICATIONINVOKEHANDLER_H_ diff --git a/shared/acsdkCommunication/include/acsdkCommunication/InMemoryCommunicationPropertiesHandler.h b/shared/acsdkCommunication/include/acsdkCommunication/InMemoryCommunicationPropertiesHandler.h new file mode 100644 index 0000000000..fc8b1022f9 --- /dev/null +++ b/shared/acsdkCommunication/include/acsdkCommunication/InMemoryCommunicationPropertiesHandler.h @@ -0,0 +1,264 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCOMMUNICATION_INMEMORYCOMMUNICATIONPROPERTIESHANDLER_H_ +#define ACSDKCOMMUNICATION_INMEMORYCOMMUNICATIONPROPERTIESHANDLER_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkCommunication { + +/** + * Struct to keep properties and writeValidators linked to each other. + */ +template +struct PropertyInfo { + std::weak_ptr> property; + std::weak_ptr> writeValidator; +}; +/** + * This is the in memory implementation of the CommunicationPropertiesHandlerInterface. + * This is a thread safe class that provides users the ability to register properties. + */ +template +class InMemoryCommunicationPropertiesHandler + : public virtual acsdkCommunicationInterfaces::CommunicationPropertiesHandlerInterface { +public: + /** + * Default destructor + */ + ~InMemoryCommunicationPropertiesHandler() override = default; + + /** + * Internal class that is used to notify subscribers when a property has been changed. + */ + class WeakSubscriptionProxy + : public acsdkCommunicationInterfaces::CommunicationPropertyChangeSubscriber + , public acsdkNotifier::Notifier> { + public: + /// @name CommunictionPropertyChangeSubscriber methods + /// @{ + void onCommunicationPropertyChange(const std::string& propertyName, T newValue) override { + this->notifyObservers( + [=](const std::shared_ptr>& + observer) { observer->onCommunicationPropertyChange(propertyName, newValue); }); + } + /// }@ + }; + /// @name CommunicationPropertiesHandlerInterface methods + /// @{ + std::shared_ptr> registerProperty( + const std::string& propertyName, + T initValue, + const std::shared_ptr>& + writeValidator = nullptr) override; + void deregisterProperty( + const std::string& propertyName, + const std::shared_ptr>& property) override; + bool writeProperty(const std::string& propertyName, T newValue) override; + + bool readProperty(const std::string& propertyName, T& value) override; + + bool subscribeToPropertyChangeEvent( + const std::string& propertyName, + const std::weak_ptr>& subscriber) + override; + + bool unsubscribeToPropertyChangeEvent( + const std::string& propertyName, + const std::shared_ptr>& subscriber) + override; + /// }@ + +private: + // map of names to property info. + std::unordered_map> m_properties; + // list of property names to subscribers + std::unordered_map> m_subscribers; + // mutex to protect the properties. + std::mutex m_propertiesMutex; +}; +template +std::shared_ptr> InMemoryCommunicationPropertiesHandler:: + registerProperty( + const std::string& propertyName, + T initValue, + const std::shared_ptr>& + writeValidator) { + std::unique_lock lock(m_propertiesMutex); + auto it = m_properties.find(propertyName); + if (it != m_properties.end()) { + if (!it->second.property.expired()) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry( + "InMemoryCommunicationPropertiesHandler", "registerProperty") + .m("Property is already Registered") + .d("property", propertyName)); + return nullptr; + } + // Erase if the property isn't available anymore and reregister + m_properties.erase(it); + } + auto property = acsdkCommunicationInterfaces::CommunicationProperty::create( + propertyName, initValue, writeValidator != nullptr); + std::weak_ptr> weakProperty = + std::weak_ptr>(property); + std::weak_ptr> weakValidator = + std::weak_ptr>(writeValidator); + PropertyInfo currentInfo{weakProperty, weakValidator}; + ACSDK_DEBUG( + alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationPropertiesHandler", "registerProperty") + .m("Property is Registered") + .d("property", propertyName)); + + m_properties.insert({propertyName, currentInfo}); + + auto& subscriberProxy = m_subscribers[propertyName]; + if (!subscriberProxy) { + subscriberProxy = std::make_shared(); + } + property->addSubscriber(subscriberProxy); + + return property; +} + +template +void InMemoryCommunicationPropertiesHandler::deregisterProperty( + const std::string& propertyName, + const std::shared_ptr>& property) { + std::unique_lock lock(m_propertiesMutex); + auto it = m_properties.find(propertyName); + if (it == m_properties.end()) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry( + "InMemoryCommunicationPropertiesHandler", "deregisterProperty") + .m("Property is not Registered") + .d("property", propertyName)); + return; + } + if (it->second.property.lock() != property) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry( + "InMemoryCommunicationPropertiesHandler", "deregisterProperty") + .m("Property is registered but can not be matched") + .d("property", propertyName)); + return; + } + m_properties.erase(it); +} + +template +bool InMemoryCommunicationPropertiesHandler::writeProperty(const std::string& propertyName, T newValue) { + std::unique_lock lock(m_propertiesMutex); + auto it = m_properties.find(propertyName); + if (it == m_properties.end()) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry( + "InMemoryCommunicationPropertiesHandler", "writeProperty") + .m("Property is not Registered") + .d("property", propertyName)); + return false; + } + auto property = it->second.property.lock(); + if (!property) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry( + "InMemoryCommunicationPropertiesHandler", "writeProperty") + .m("Property has expired") + .d("property", propertyName)); + m_properties.erase(it); + return false; + } + if (!property->isWriteable()) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry( + "InMemoryCommunicationPropertiesHandler", "writeProperty") + .m("Property is not writeable") + .d("property", propertyName)); + return false; + } + auto validator = it->second.writeValidator.lock(); + if (!validator) { + ACSDK_ERROR(alexaClientSDK::avsCommon::utils::logger::LogEntry( + "InMemoryCommunicationPropertiesHandler", "writeProperty") + .m("Can't validate property") + .d("property", propertyName)); + return false; + } + lock.unlock(); + if (validator->validateWriteRequest(propertyName, newValue)) { + property->setValue(newValue); + return true; + } + return false; +} +template +bool InMemoryCommunicationPropertiesHandler::readProperty(const std::string& propertyName, T& value) { + std::unique_lock lock(m_propertiesMutex); + auto it = m_properties.find(propertyName); + if (it == m_properties.end()) { + ACSDK_ERROR( + alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationPropertiesHandler", "readProperty") + .m("Property is not Registered") + .d("property", propertyName)); + return false; + } + auto property = it->second.property.lock(); + if (!property) { + ACSDK_ERROR( + alexaClientSDK::avsCommon::utils::logger::LogEntry("InMemoryCommunicationPropertiesHandler", "readProperty") + .m("Property has expired") + .d("property", propertyName)); + m_properties.erase(it); + return false; + } + value = property->getValue(); + return true; +} + +template +bool InMemoryCommunicationPropertiesHandler::subscribeToPropertyChangeEvent( + const std::string& propertyName, + const std::weak_ptr>& subscriber) { + std::unique_lock lock(m_propertiesMutex); + auto& subscriberProxy = m_subscribers[propertyName]; + if (!subscriberProxy) { + subscriberProxy = std::make_shared(); + } + subscriberProxy->addWeakPtrObserver(subscriber); + return !subscriber.expired(); +} + +template +bool InMemoryCommunicationPropertiesHandler::unsubscribeToPropertyChangeEvent( + const std::string& propertyName, + const std::shared_ptr>& subscriber) { + std::unique_lock lock(m_propertiesMutex); + auto& subscriberProxy = m_subscribers[propertyName]; + if (!subscriberProxy) { + subscriberProxy = std::make_shared(); + } + subscriberProxy->removeWeakPtrObserver(subscriber); + return true; +} +} // namespace acsdkCommunication +} // namespace alexaClientSDK + +#endif // ACSDKCOMMUNICATION_INMEMORYCOMMUNICATIONPROPERTIESHANDLER_H_ diff --git a/shared/acsdkCommunication/test/CMakeLists.txt b/shared/acsdkCommunication/test/CMakeLists.txt new file mode 100644 index 0000000000..957df88b49 --- /dev/null +++ b/shared/acsdkCommunication/test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(LIBS + "AVSCommon" + "acsdkCommunication" + ) + +discover_unit_tests("${acsdkCommunication_INCLUDE_DIRS}" "${LIBS}") diff --git a/shared/acsdkCommunication/test/InMemoryCommunicationInvokeHandlerTest.cpp b/shared/acsdkCommunication/test/InMemoryCommunicationInvokeHandlerTest.cpp new file mode 100644 index 0000000000..2130d96ecf --- /dev/null +++ b/shared/acsdkCommunication/test/InMemoryCommunicationInvokeHandlerTest.cpp @@ -0,0 +1,125 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 InMemoryCommunicationInvokeHandlerTest.cpp + +#include +#include +#include + +#include +#include +#include "acsdkCommunication/InMemoryCommunicationInvokeHandler.h" + +namespace alexaClientSDK { +namespace acsdkCommunication { +namespace test { + +using namespace ::testing; +using namespace alexaClientSDK::avsCommon::utils::error; + +class InMemoryCommunicationInvokeHandlerTest : public ::testing::Test {}; + +class TestFunction1 : public acsdkCommunicationInterfaces::FunctionInvokerInterface { +public: + std::string functionToBeInvoked(const std::string& name, int value) override { + return "TestFunction1 " + name + " " + std::to_string(value); + } +}; +class TestFunction2 : public acsdkCommunicationInterfaces::FunctionInvokerInterface { +public: + std::string functionToBeInvoked(const std::string& name, int value) override { + return "TestFunction2 " + name + " " + std::to_string(value); + } +}; + +/** + * Verify the registration + */ +TEST_F(InMemoryCommunicationInvokeHandlerTest, test_registerFunction) { + InMemoryCommunicationInvokeHandler handler; + auto testFunction1 = std::make_shared(TestFunction1()); + auto testFunction2 = std::make_shared(TestFunction2()); + // registration should be successful + ASSERT_TRUE(handler.registerFunction("test", testFunction1)); + // function with the same name should fail. + ASSERT_FALSE(handler.registerFunction("test", testFunction2)); + // We shouldn't be able to register a nullptr function. + ASSERT_FALSE(handler.registerFunction("test2", nullptr)); + + // deregister to clean up. + ASSERT_TRUE(handler.deregister("test", testFunction1)); +} + +/** + * Verify deregistration + */ +TEST_F(InMemoryCommunicationInvokeHandlerTest, test_deregisterFunction) { + InMemoryCommunicationInvokeHandler handler; + auto testFunction1 = std::make_shared(TestFunction1()); + auto testFunction2 = std::make_shared(TestFunction2()); + // register two functions + ASSERT_TRUE(handler.registerFunction("test1", testFunction1)); + ASSERT_TRUE(handler.registerFunction("test2", testFunction2)); + // deregister a function + ASSERT_TRUE(handler.deregister("test1", testFunction1)); + // try to deregister the same function again + ASSERT_FALSE(handler.deregister("test1", testFunction1)); + // try to deregister a registered function with the wrong pointer + ASSERT_FALSE(handler.deregister("test2", testFunction1)); + + // deregister to clean up + ASSERT_TRUE(handler.deregister("test2", testFunction2)); +} +/** + * Verify invoking the functions + */ +TEST_F(InMemoryCommunicationInvokeHandlerTest, test_invokeFunctions) { + InMemoryCommunicationInvokeHandler handler; + const std::string name1 = "test1"; + const std::string name2 = "test2"; + int value1 = 1; + int value2 = 2; + std::string expectedReturnValue1 = "TestFunction1 " + name1 + " " + std::to_string(value1); + std::string expectedReturnValue2 = "TestFunction2 " + name2 + " " + std::to_string(value2); + + auto testFunction1 = std::make_shared(TestFunction1()); + auto testFunction2 = std::make_shared(TestFunction2()); + // register two functions + ASSERT_TRUE(handler.registerFunction("test1", testFunction1)); + ASSERT_TRUE(handler.registerFunction("test2", testFunction2)); + + // invoke functions + SuccessResult returnValue1 = handler.invoke(name1, value1); + SuccessResult returnValue2 = handler.invoke(name2, value2); + + ASSERT_TRUE(returnValue1.isSucceeded()); + ASSERT_EQ(expectedReturnValue1, returnValue1.value()); + + ASSERT_TRUE(returnValue2.isSucceeded()); + ASSERT_EQ(expectedReturnValue2, returnValue2.value()); + + // unregistered function shouldn't be invoked. + SuccessResult returnValue3 = handler.invoke("test3", value1); + ASSERT_FALSE(returnValue3.isSucceeded()); + + // deregister to clean up + ASSERT_TRUE(handler.deregister("test1", testFunction1)); + ASSERT_TRUE(handler.deregister("test2", testFunction2)); +} + +} // namespace test +} // namespace acsdkCommunication +} // namespace alexaClientSDK diff --git a/shared/acsdkCommunication/test/InMemoryCommunicationPropertiesHandlerTest.cpp b/shared/acsdkCommunication/test/InMemoryCommunicationPropertiesHandlerTest.cpp new file mode 100644 index 0000000000..d3645ea844 --- /dev/null +++ b/shared/acsdkCommunication/test/InMemoryCommunicationPropertiesHandlerTest.cpp @@ -0,0 +1,161 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 InMemoryCommunicationPropertiesHandlerTest.cpp + +#include +#include +#include + +#include +#include + +#include "acsdkCommunication/AlwaysTrueCommunicationValidator.h" +#include "acsdkCommunication/InMemoryCommunicationPropertiesHandler.h" + +namespace alexaClientSDK { +namespace acsdkCommunication { +namespace test { + +using namespace ::testing; + +class InMemoryCommunicationPropertiesHandlerTest : public ::testing::Test {}; + +class TestSubscriber : public acsdkCommunicationInterfaces::CommunicationPropertyChangeSubscriber { +public: + MOCK_METHOD2(onCommunicationPropertyChange, void(const std::string&, int)); +}; +class FalseValidator : public acsdkCommunicationInterfaces::CommunicationPropertyValidatorInterface { +public: + // always false validator. + bool validateWriteRequest(const std::string& PropertyName, int newValue) { + return false; + } +}; + +/** + * Verify the registration of a property + */ +TEST_F(InMemoryCommunicationPropertiesHandlerTest, test_registerProperty) { + InMemoryCommunicationPropertiesHandler handler; + + auto property1 = handler.registerProperty("test1", 1); + ASSERT_NE(property1, nullptr); + + // try to reregister a property + auto property2 = handler.registerProperty("test1", 2); + ASSERT_EQ(property2, nullptr); + + // clean up deregister property + handler.deregisterProperty("test1", property1); +} + +/** + * Verify write property + */ +TEST_F(InMemoryCommunicationPropertiesHandlerTest, test_writeProperty) { + InMemoryCommunicationPropertiesHandler handler; + auto falseValidator = std::make_shared(FalseValidator()); + auto trueValidator = + std::make_shared>(AlwaysTrueCommunicationValidator()); + + auto property1 = handler.registerProperty("test1", 1); + ASSERT_NE(property1, nullptr); + + auto property2 = handler.registerProperty("test2", 2, trueValidator); + ASSERT_NE(property2, nullptr); + + auto property3 = handler.registerProperty("test3", 3, falseValidator); + ASSERT_NE(property3, nullptr); + + ASSERT_FALSE(handler.writeProperty("test1", 2)); + + ASSERT_TRUE(handler.writeProperty("test2", 3)); + int value; + handler.readProperty("test2", value); + ASSERT_EQ(value, 3); + + ASSERT_FALSE(handler.writeProperty("test3", 4)); + + handler.deregisterProperty("test1", property1); + handler.deregisterProperty("test2", property2); + handler.deregisterProperty("test3", property3); +} +/** + * Validate write and subscribe + */ +TEST_F(InMemoryCommunicationPropertiesHandlerTest, test_subscribeWriteProperty) { + InMemoryCommunicationPropertiesHandler handler; + auto trueValidator = + std::make_shared>(AlwaysTrueCommunicationValidator()); + std::condition_variable trigger; + bool boolTrigger1 = false; + bool boolTrigger2 = false; + std::mutex mutex1; + + auto subscriber1 = std::make_shared(); + auto subscriber2 = std::make_shared(); + EXPECT_CALL(*subscriber1, onCommunicationPropertyChange(_, _)).WillOnce(InvokeWithoutArgs([&] { + std::unique_lock lock(mutex1); + boolTrigger1 = true; + trigger.notify_all(); + })); + + EXPECT_CALL(*subscriber2, onCommunicationPropertyChange(_, _)).WillOnce(InvokeWithoutArgs([&] { + std::unique_lock lock(mutex1); + boolTrigger2 = true; + trigger.notify_all(); + })); + + // subscribe prior to property being created. + handler.subscribeToPropertyChangeEvent("test1", subscriber1); + + auto property1 = handler.registerProperty("test1", 1, trueValidator); + ASSERT_NE(property1, nullptr); + auto property2 = handler.registerProperty("test2", 2, trueValidator); + ASSERT_NE(property2, nullptr); + + // subscribe after property has been created + handler.subscribeToPropertyChangeEvent("test2", subscriber2); + + int newValue1 = 3; + ASSERT_TRUE(handler.writeProperty("test1", newValue1)); + + int value1; + handler.readProperty("test1", value1); + ASSERT_EQ(value1, newValue1); + + int newValue2 = 4; + ASSERT_TRUE(handler.writeProperty("test2", newValue2)); + + int value2; + handler.readProperty("test2", value2); + ASSERT_EQ(value2, newValue2); + + std::unique_lock lock(mutex1); + trigger.wait(lock, [&] { return boolTrigger1; }); + + trigger.wait(lock, [&] { return boolTrigger2; }); + + ASSERT_TRUE(handler.unsubscribeToPropertyChangeEvent("test1", subscriber1)); + ASSERT_TRUE(handler.unsubscribeToPropertyChangeEvent("test2", subscriber2)); + + handler.deregisterProperty("test1", property1); + handler.deregisterProperty("test2", property2); +} + +} // namespace test +} // namespace acsdkCommunication +} // namespace alexaClientSDK diff --git a/shared/acsdkCommunicationInterfaces/CMakeLists.txt b/shared/acsdkCommunicationInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..5b07c70847 --- /dev/null +++ b/shared/acsdkCommunicationInterfaces/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.1) + +project(acsdkCommunicationInterfaces LANGUAGES CXX) + +add_library(acsdkCommunicationInterfaces INTERFACE) + +target_include_directories(acsdkCommunicationInterfaces INTERFACE + "${acsdkCommunicationInterfaces_SOURCE_DIR}/include" + ) + + +# install interface +asdk_install_interface() \ No newline at end of file diff --git a/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationInvokeHandlerInterface.h b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationInvokeHandlerInterface.h new file mode 100644 index 0000000000..d3b32ee14f --- /dev/null +++ b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationInvokeHandlerInterface.h @@ -0,0 +1,75 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONINVOKEHANDLERINTERFACE_H_ +#define ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONINVOKEHANDLERINTERFACE_H_ + +#include +#include + +#include + +#include "FunctionInvokerInterface.h" + +namespace alexaClientSDK { +namespace acsdkCommunicationInterfaces { + +/** + * The CommunicationInvokeHandlerInterface is used to register, deregister, and invoke functions from another component + * with only a link to the CommunicationInvokeHandler. The implementation of this interface is not responsible for + * keeping FunctionInvokerInterface implementations alive. + */ +template +class CommunicationInvokeHandlerInterface { +public: + /** + * Virtual destructor to assure proper cleanup of derived types. + */ + virtual ~CommunicationInvokeHandlerInterface() = default; + + /** + * Register a new function that other components can trigger with the CommunicationInvokeHandlerInterface + * @param name The name of the function. + * @param functionImplementation The class that implements functionToBeInvoked + * @return true if succeeds, false otherwise + */ + virtual bool registerFunction( + const std::string& name, + std::shared_ptr> functionImplementation) = 0; + /** + * Invokes the registered function specified by the name. If the function isn't registered or the function + * has expired nothing will be invoked. + * @param name The name of the function + * @param Args The args that will be passed to the function. + * @return SuccessResult if the callback succeeds or not. If successful the return value. + */ + virtual alexaClientSDK::avsCommon::utils::error::SuccessResult invoke( + const std::string& name, + ArgTypes...) = 0; + /** + * Deregister the function + * @param name The name of the function to deregister + * @param functionImplementation The function that we are deregistering. Used for confirmation of ownership. + * @return true if deregister succeeds, false otherwise + */ + virtual bool deregister( + const std::string& name, + const std::shared_ptr>& functionImplementation) = 0; +}; + +} // namespace acsdkCommunicationInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONINVOKEHANDLERINTERFACE_H_ diff --git a/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertiesHandlerInterface.h b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertiesHandlerInterface.h new file mode 100644 index 0000000000..1217b68389 --- /dev/null +++ b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertiesHandlerInterface.h @@ -0,0 +1,103 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTIESHANDLERINTERFACE_H_ +#define ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTIESHANDLERINTERFACE_H_ + +#include + +#include "CommunicationProperty.h" +#include "CommunicationPropertyChangeSubscriber.h" +#include "CommunicationPropertyValidatorInterface.h" + +namespace alexaClientSDK { +namespace acsdkCommunicationInterfaces { + +/** + * The CommunicationPropertiesHandlerInterface is used to register, deregister, write property, read property, subscribe + * to property change events, and unsubscribe to change events. The implementation will allow multiple different + * components to have access to properties without have explict ownership. The CommunicationPropertiesHandlerInterface + * isn't responsible for property ownership. + */ +template +class CommunicationPropertiesHandlerInterface { +public: + /** + * Virtual destructor to assure proper cleanup of derived types. + */ + virtual ~CommunicationPropertiesHandlerInterface() = default; + + /** + * Register a new Property + * @param name The name of the new property + * @param initValue The initial value that the property will have. + * @param writeValidator The class that we will use to validate the write operation. If a nullptr + * the property will be read only. + * @return std::shared_ptr> The component that registers the property will be the owner and be in charge + * of keeping the pointer alive. + */ + virtual std::shared_ptr> registerProperty( + const std::string& propertyName, + T initValue, + const std::shared_ptr>& writeValidator = nullptr) = 0; + /** + * deregister the property, deregistration of the property only occurs when the property can be found and the passed + * in property matches the registered property. + * @param name Name of the property to deregister + * @param property The property that we are deregistering. This is used to prove ownership of the property. + * the parameter can be a nullptr. + */ + virtual void deregisterProperty( + const std::string& propertyName, + const std::shared_ptr>& property) = 0; + /** + * Write a new value to the property, this will be validated by the writeValidator. + * @param name The property that we are writing the newValue to. + * @param newValue The new value for the property. + * @return true if write succeeds, false otherwise. + */ + virtual bool writeProperty(const std::string& propertyName, T newValue) = 0; + /** + * Read the value from a property. + * @param name Name of the property we are trying to read. + * @param[out] value The reference where we will populate the read value. + * @return true if successful, false otherwise. + */ + virtual bool readProperty(const std::string& propertyName, T& value) = 0; + /** + * Subscribe to change events for a specific property. No value will be passed back. The user should read the + * value of the property after subscribing. + * @param propertyName Name of property we want to subscribe to. + * @param subscriber The subscriber that will define the action for on change event. + * @return true if successfully subscribed, false otherwise. + */ + virtual bool subscribeToPropertyChangeEvent( + const std::string& propertyName, + const std::weak_ptr>& subscriber) = 0; + /** + * Unsubscribe to change events for a specific property. + * @param propertyName Name of property we want to unsubscribe from. + * @param subscriber The subscriber that will be unsubscribed from the change events. + * @return true if successfully unsubscribed, false otherwise. + */ + virtual bool unsubscribeToPropertyChangeEvent( + const std::string& propertyName, + const std::shared_ptr>& subscriber) = 0; +}; + +} // namespace acsdkCommunicationInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTIESHANDLERINTERFACE_H_ diff --git a/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationProperty.h b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationProperty.h new file mode 100644 index 0000000000..9505bf67d7 --- /dev/null +++ b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationProperty.h @@ -0,0 +1,145 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTY_H_ +#define ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTY_H_ + +#include + +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace acsdkCommunicationInterfaces { + +/** + * The CommunicationProperty is the class that will be returned when we register a new property with the + * CommunicationPropertiesHandlerInterface. This will hold a value of type T. This class allows the owner direct read + * and write access. + */ +template +class CommunicationProperty { +public: + ~CommunicationProperty() { + m_executor.waitForSubmittedTasks(); + m_executor.shutdown(); + } + /** + * setValue allows setting of the value without going through the CommunicationPropertiesHandlerInterface + * @param newValue The newValue which we are setting the value to. + * @return true if value was set, false otherwise. + */ + bool setValue(T newValue) { + std::unique_lock lock(m_propertyMutex); + m_value = newValue; + m_executor.submit([this, newValue]() { notifyOnCommunicationPropertyChange(m_name, newValue); }); + return true; + } + + /** + * Owner of the property can get the value of the property without going through the + * CommunicationPropertiesHandlerInterface + * @return the value of the property + */ + T getValue() { + std::unique_lock lock(m_propertyMutex); + return m_value; + } + + /** + * Create a new Property + * @param name Name of the new property. + * @param initValue The initial value of the property + * @param writeable If the property is writeable or not. + * @return shared_ptr to a new Property. + */ + static std::shared_ptr> create(const std::string& name, T initValue, bool writeable) { + return std::shared_ptr>(new CommunicationProperty(name, initValue, writeable)); + } + + /** + * A function used to determine if the property is writeable or not. + * @return true if writeable, false otherwise + */ + bool isWriteable() { + return m_writeable; + } + + /** + * Add a subscriber to property change events. + * @param subscriber The new subscriber that wants to listen to property change events. + * @return true if subscriber is valid and was added, false otherwise. + */ + bool addSubscriber(const std::weak_ptr>& subscriber) { + m_weakSubscriptionProxy.addWeakPtrObserver(subscriber); + return !subscriber.expired(); + } + + /** + * Remove a subscriber from property change events. + * @param subscriber The subscriber that doesn't want to listen to property change events. + */ + void removeSubscriber(const std::shared_ptr>& subscriber) { + m_weakSubscriptionProxy.removeWeakPtrObserver(subscriber); + } + +private: + /** + * Notify subscribers of a change to a the property value. This is called from an executor. + */ + bool notifyOnCommunicationPropertyChange(const std::string& propertyName, T newValue) { + m_weakSubscriptionProxy.notifyObservers( + [=](const std::shared_ptr>& obs) { + obs->onCommunicationPropertyChange(propertyName, newValue); + }); + return true; + } + + /** + * Private constructor + * @param name Name of the property + * @param initValue Initial value of the property + * @param writeable If the property is writeable or not. + */ + CommunicationProperty(std::string name, T initValue, bool writeable) : + m_name{std::move(name)}, + m_value{std::move(initValue)}, + m_writeable{writeable} { + } + +private: + /// The communication Property to notify on setValue events. + acsdkNotifier::Notifier> m_weakSubscriptionProxy; + + /// The name of property + const std::string m_name; + + /// The value of the property + T m_value; + + /// Is the property writeable + const bool m_writeable; + + /// Mutex to protect the poperty value + std::mutex m_propertyMutex; + + avsCommon::utils::threading::Executor m_executor; +}; + +} // namespace acsdkCommunicationInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTY_H_ diff --git a/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertyChangeSubscriber.h b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertyChangeSubscriber.h new file mode 100644 index 0000000000..bf730e7ff6 --- /dev/null +++ b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertyChangeSubscriber.h @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTYCHANGESUBSCRIBER_H_ +#define ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTYCHANGESUBSCRIBER_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkCommunicationInterfaces { + +/** + * The CommunicationPropertyChangeSubscriber is the interface that is used to subscribe to property change events. + */ +template +class CommunicationPropertyChangeSubscriber { +public: + /** + * default destructor + */ + virtual ~CommunicationPropertyChangeSubscriber() = default; + + /** + * Function that is called when the Communication Property that is subscribed to has the value changed. + * @param propertyName The name of the property + * @param newValue The new value of the property + */ + virtual void onCommunicationPropertyChange(const std::string& propertyName, T newValue) = 0; +}; + +} // namespace acsdkCommunicationInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTYCHANGESUBSCRIBER_H_ diff --git a/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertyValidatorInterface.h b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertyValidatorInterface.h new file mode 100644 index 0000000000..ff6a65f645 --- /dev/null +++ b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/CommunicationPropertyValidatorInterface.h @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTYVALIDATORINTERFACE_H_ +#define ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTYVALIDATORINTERFACE_H_ + +#include + +namespace alexaClientSDK { +namespace acsdkCommunicationInterfaces { + +/** + * The CommunicationPropertyValidatorInterface is used to validate the new value that is being written to a property. + * This allows the component that registered the property to have some control over what is written into their property + * by external components. + */ +template +class CommunicationPropertyValidatorInterface { +public: + /** + * default destructor + */ + virtual ~CommunicationPropertyValidatorInterface() = default; + + /** + * Called when we want to write to a property. Used to validate before we write the newValue + * @param propertyName The name of the property + * @param newValue The new value of the property + * @return true if alright to write value, false otherwise. + */ + virtual bool validateWriteRequest(const std::string& propertyName, T newValue) = 0; +}; + +} // namespace acsdkCommunicationInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCOMMUNICATIONINTERFACES_COMMUNICATIONPROPERTYVALIDATORINTERFACE_H_ \ No newline at end of file diff --git a/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/FunctionInvokerInterface.h b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/FunctionInvokerInterface.h new file mode 100644 index 0000000000..42aca67476 --- /dev/null +++ b/shared/acsdkCommunicationInterfaces/include/acsdkCommunicationInterfaces/FunctionInvokerInterface.h @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKCOMMUNICATIONINTERFACES_FUNCTIONINVOKERINTERFACE_H_ +#define ACSDKCOMMUNICATIONINTERFACES_FUNCTIONINVOKERINTERFACE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace acsdkCommunicationInterfaces { + +/** + * The interface for the functions that can be invoked by external components. The implementation of the interface will + * need to be registered with CommunicationInvokeHandlerInterface. This interface the first template type will be the + * return type and the rest will be the arguments for the function. + */ +template +class FunctionInvokerInterface { +public: + /** + * default destructor + */ + virtual ~FunctionInvokerInterface() = default; + /** + * The function or functions that will be registered and invoked by external components. We will specify different + * functions by using the name param. + * @param name the name of the function we want to invoke. + * @param args variadic args that are passed as arguments to the function. + * @return ReturnType the result of the function. + */ + virtual ReturnType functionToBeInvoked(const std::string& name, Types... args) = 0; +}; + +} // namespace acsdkCommunicationInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKCOMMUNICATIONINTERFACES_FUNCTIONINVOKERINTERFACE_H_ diff --git a/shared/acsdkManufactory/src/CMakeLists.txt b/shared/acsdkManufactory/src/CMakeLists.txt index 601ddda763..1d0ad4ad0f 100644 --- a/shared/acsdkManufactory/src/CMakeLists.txt +++ b/shared/acsdkManufactory/src/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) add_definitions("-DACSDK_LOG_MODULE=Manufactory") -add_library(acsdkManufactory SHARED +add_library(acsdkManufactory CookBook.cpp SharedPointerCache.cpp WeakPointerCache.cpp) diff --git a/shared/acsdkNotifier/include/acsdkNotifier/Notifier.h b/shared/acsdkNotifier/include/acsdkNotifier/Notifier.h deleted file mode 100644 index ce1b73fd4f..0000000000 --- a/shared/acsdkNotifier/include/acsdkNotifier/Notifier.h +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKNOTIFIER_NOTIFIER_H_ -#define ACSDKNOTIFIER_NOTIFIER_H_ - -#include -#include -#include -#include - -#include - -namespace alexaClientSDK { -namespace acsdkNotifier { - -/** - * Notifier maintains a set of observers that are notified with a caller defined function. - * - * @tparam ObserverType The type of observer notified by the template instantiation. - */ -template -class Notifier : public acsdkNotifierInterfaces::NotifierInterface { -public: - /** - * Constructor. - */ - Notifier(); - - /// @name NotifierInterface methods - /// @{ - void addObserver(const std::shared_ptr& observer) override; - void removeObserver(const std::shared_ptr& observer) override; - void notifyObservers(std::function&)> notify) override; - bool notifyObserversInReverse(std::function&)> notify) override; - void setAddObserverFunction(std::function&)> addObserverFunc) override; - /// @} - -private: - /** - * Eliminate the unwanted observer value from @c m_observers. - * - * @param unwanted The unwanted observer value. - */ - void cleanupLocked(const std::shared_ptr& unwanted); - - /// Mutex to serialize access to m_depth and m_observers. Note that a recursive mutex is used - /// here to avoid undefined behavior if notifying an observer callback in to this @c Notifier. - std::recursive_mutex m_mutex; - - /// Depth of calls to @c notifyObservers() and notifyObserversInReverse(). - int m_depth; - - /// The set of observers. Note that a vector is used here to allow for the addition - /// or removal of observers while calls to notifyObservers() are in progress. - std::vector> m_observers; - - /// If set, this function will be called after an observer is added. - std::function&)> m_addObserverFunc; -}; - -template -inline Notifier::Notifier() : m_depth{0} { -} - -template -inline void Notifier::addObserver(const std::shared_ptr& observer) { - std::lock_guard guard(m_mutex); - for (auto& existing : m_observers) { - if (observer == existing) { - return; - } - } - m_observers.push_back(observer); - - if (m_addObserverFunc) { - m_addObserverFunc(observer); - } -} - -template -inline void Notifier::removeObserver(const std::shared_ptr& observer) { - std::lock_guard guard(m_mutex); - if (m_depth > 0) { - for (size_t ix = 0; ix < m_observers.size(); ix++) { - if (m_observers[ix] == observer) { - m_observers[ix] = nullptr; - break; - } - } - } else { - cleanupLocked(observer); - } -} - -template -inline void Notifier::notifyObservers(std::function&)> notify) { - std::lock_guard guard(m_mutex); - m_depth++; - for (size_t ix = 0; ix < m_observers.size(); ix++) { - auto observer = m_observers[ix]; - if (observer) { - notify(observer); - } - } - if (0 == --m_depth) { - cleanupLocked(nullptr); - } -} - -template -inline bool Notifier::notifyObserversInReverse( - std::function&)> notify) { - std::lock_guard guard(m_mutex); - m_depth++; - auto initialSize = m_observers.size(); - for (auto ix = initialSize; ix-- > 0;) { - auto observer = m_observers[ix]; - if (observer) { - notify(observer); - } - } - bool result = m_observers.size() == initialSize; - if (0 == --m_depth) { - cleanupLocked(nullptr); - } - return result; -} - -template -inline void Notifier::setAddObserverFunction( - std::function&)> addObserverFunc) { - std::lock_guard guard(m_mutex); - m_addObserverFunc = addObserverFunc; -} - -template -inline void Notifier::cleanupLocked(const std::shared_ptr& unwanted) { - m_observers.erase( - std::remove_if( - m_observers.begin(), - m_observers.end(), - [unwanted](std::shared_ptr observer) { return observer == unwanted; }), - m_observers.end()); -} - -} // namespace acsdkNotifier -} // namespace alexaClientSDK - -#endif // ACSDKNOTIFIER_NOTIFIER_H_ diff --git a/shared/acsdkNotifier/include/acsdkNotifier/internal/Notifier.h b/shared/acsdkNotifier/include/acsdkNotifier/internal/Notifier.h new file mode 100644 index 0000000000..116ec15a37 --- /dev/null +++ b/shared/acsdkNotifier/include/acsdkNotifier/internal/Notifier.h @@ -0,0 +1,314 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKNOTIFIER_INTERNAL_NOTIFIER_H_ +#define ACSDKNOTIFIER_INTERNAL_NOTIFIER_H_ + +#include +#include +#include +#include + +#include + +namespace alexaClientSDK { +namespace acsdkNotifier { + +/** + * Notifier maintains a set of observers that are notified with a caller defined function. + * + * @tparam ObserverType The type of observer notified by the template instantiation. + */ +template +class Notifier : public acsdkNotifierInterfaces::NotifierInterface { +public: + /** + * Constructor. + */ + Notifier(); + + /// @name NotifierInterface methods + /// @{ + void addObserver(const std::shared_ptr& observer) override; + void removeObserver(const std::shared_ptr& observer) override; + void addWeakPtrObserver(const std::weak_ptr& observer) override; + void removeWeakPtrObserver(const std::weak_ptr& observer) override; + void notifyObservers(std::function&)> notify) override; + bool notifyObserversInReverse(std::function&)> notify) override; + void setAddObserverFunction(std::function&)> addObserverFunc) override; + /// @} + +private: + /** + * Eliminate the unwanted observer value from @c m_observers. This will also cleanup any @c weak_ptr observer that + * has been expired. + * + * @param unwanted The unwanted observer value. + */ + void cleanupLocked(const std::shared_ptr& unwanted); + + /** + * Checks if observer already exists in @c m_observers. + * + * @param unwanted The observer value. + * @return true if observer already exists, false otherwise. + */ + bool isAlreadyExistLocked(const std::shared_ptr& observer); + + /// A class for a Notifier observer + class NotifierObserver { + public: + /** + * Constructor. + * + * @param observer The @c std::shared_ptr observer. + */ + explicit NotifierObserver(const std::shared_ptr& observer); + + /** + * Constructor. + * + * @param observer The @c std::weak_ptr observer. + */ + explicit NotifierObserver(const std::weak_ptr& observer); + + /** + * Gets the observer. + * + * @return The observer as a @c std::shared_ptr. + */ + std::shared_ptr get() const; + + /** + * Clears the observer. + */ + void clear(); + + /** + * Checks if the notifier observer matches the observer that is passed in, or if the notifier observer (if it's + * a weak_ptr observer) has expired. + * + * @param The observer to check against if it's equal. + * @return true if observer is equal to notifier observer or if notifier observer has expired, false otherwise. + */ + bool isEqualOrExpired(const std::shared_ptr& observer) const; + + private: + /// A enum type to specify the observer pointer type + enum class ObserverPointerType { + /// Observer is SHARED_PTR + SHARED_PTR, + /// Observer is WEAK_PTR + WEAK_PTR + }; + + /// Type of observer + ObserverPointerType m_type; + /// shared_ptr of observer, if its type is SHARED_PTR + std::shared_ptr m_sharedPtrObserver; + /// weak_ptr of observer, if its type is WEAK_PTR + std::weak_ptr m_weakPtrObserver; + }; + + /// Mutex to serialize access to m_depth and m_observers. Note that a recursive mutex is used + /// here to avoid undefined behavior if notifying an observer callback in to this @c Notifier. + std::recursive_mutex m_mutex; + + /// Depth of calls to @c notifyObservers() and notifyObserversInReverse(). + int m_depth; + + /// The set of observers. Note that a vector is used here to allow for the addition + /// or removal of observers while calls to notifyObservers() are in progress. + std::vector m_observers; + + /// If set, this function will be called after an observer is added. + std::function&)> m_addObserverFunc; +}; + +template +inline Notifier::NotifierObserver::NotifierObserver(const std::shared_ptr& observer) : + m_type{ObserverPointerType::SHARED_PTR}, + m_sharedPtrObserver{observer} { +} + +template +inline Notifier::NotifierObserver::NotifierObserver(const std::weak_ptr& observer) : + m_type{ObserverPointerType::WEAK_PTR}, + m_weakPtrObserver{observer} { +} + +template +inline std::shared_ptr Notifier::NotifierObserver::get() const { + if (ObserverPointerType::SHARED_PTR == m_type) { + return m_sharedPtrObserver; + } else { + return m_weakPtrObserver.lock(); + } +} + +template +inline void Notifier::NotifierObserver::clear() { + if (ObserverPointerType::SHARED_PTR == m_type) { + m_sharedPtrObserver.reset(); + } else { + m_weakPtrObserver.reset(); + } +} + +template +inline bool Notifier::NotifierObserver::isEqualOrExpired( + const std::shared_ptr& observer) const { + if (m_type == ObserverPointerType::SHARED_PTR) { + return m_sharedPtrObserver == observer; + } else { + return m_weakPtrObserver.expired() || m_weakPtrObserver.lock() == observer; + } +} + +template +inline Notifier::Notifier() : m_depth{0} { +} + +template +inline void Notifier::addObserver(const std::shared_ptr& observer) { + if (!observer) { + return; + } + + std::lock_guard guard(m_mutex); + if (isAlreadyExistLocked(observer)) { + return; + } + m_observers.push_back(NotifierObserver{observer}); + + if (m_addObserverFunc) { + m_addObserverFunc(observer); + } +} + +template +inline void Notifier::removeObserver(const std::shared_ptr& observer) { + std::lock_guard guard(m_mutex); + if (m_depth > 0) { + for (size_t ix = 0; ix < m_observers.size(); ix++) { + auto& notifierObserver = m_observers[ix]; + if (notifierObserver.get() == observer) { + notifierObserver.clear(); + } + } + } else { + cleanupLocked(observer); + } +} + +template +inline void Notifier::addWeakPtrObserver(const std::weak_ptr& observer) { + auto observerSharedPtr = observer.lock(); + if (!observerSharedPtr) { + return; + } + std::lock_guard guard(m_mutex); + if (isAlreadyExistLocked(observerSharedPtr)) { + return; + } + m_observers.push_back(NotifierObserver{observer}); + + if (m_addObserverFunc) { + m_addObserverFunc(observerSharedPtr); + } +} + +template +inline void Notifier::removeWeakPtrObserver(const std::weak_ptr& observer) { + auto observerSharedPtr = observer.lock(); + if (!observerSharedPtr) { + return; + } + removeObserver(observerSharedPtr); +} + +template +inline void Notifier::notifyObservers(std::function&)> notify) { + std::lock_guard guard(m_mutex); + m_depth++; + for (size_t ix = 0; ix < m_observers.size(); ix++) { + const auto& notifierObserver = m_observers[ix]; + auto observer = notifierObserver.get(); + if (observer) { + notify(observer); + } + } + if (0 == --m_depth) { + cleanupLocked(nullptr); + } +} + +template +inline bool Notifier::notifyObserversInReverse( + std::function&)> notify) { + std::lock_guard guard(m_mutex); + m_depth++; + auto initialSize = m_observers.size(); + for (auto ix = initialSize; ix-- > 0;) { + const auto& notifierObserver = m_observers[ix]; + auto observer = notifierObserver.get(); + if (observer) { + notify(observer); + } + } + bool result = m_observers.size() == initialSize; + if (0 == --m_depth) { + cleanupLocked(nullptr); + } + return result; +} + +template +inline void Notifier::setAddObserverFunction( + std::function&)> addObserverFunc) { + std::lock_guard guard(m_mutex); + bool notifyAddedObservers = false; + if (!m_addObserverFunc && addObserverFunc) { + notifyAddedObservers = true; + } + m_addObserverFunc = addObserverFunc; + if (notifyAddedObservers) { + notifyObservers(m_addObserverFunc); + } +} + +template +inline void Notifier::cleanupLocked(const std::shared_ptr& unwanted) { + auto matches = [unwanted](NotifierObserver notifierObserver) { + return notifierObserver.isEqualOrExpired(unwanted); + }; + + m_observers.erase(std::remove_if(m_observers.begin(), m_observers.end(), matches), m_observers.end()); +} + +template +inline bool Notifier::isAlreadyExistLocked(const std::shared_ptr& observer) { + for (const auto& existing : m_observers) { + if (existing.get() == observer) { + return true; + } + } + return false; +} + +} // namespace acsdkNotifier +} // namespace alexaClientSDK + +#endif // ACSDKNOTIFIER_INTERNAL_NOTIFIER_H_ diff --git a/shared/acsdkNotifier/test/NotifierTest.cpp b/shared/acsdkNotifier/test/NotifierTest.cpp index 57f94780f2..1d85433055 100644 --- a/shared/acsdkNotifier/test/NotifierTest.cpp +++ b/shared/acsdkNotifier/test/NotifierTest.cpp @@ -20,7 +20,7 @@ #include #include -#include "acsdkNotifier/Notifier.h" +#include "acsdkNotifier/internal/Notifier.h" namespace alexaClientSDK { namespace acsdkNotifier { @@ -52,9 +52,13 @@ static auto invokeOnSomething = [](const std::shared_ptr& */ TEST_F(NotifierTest, test_simplestNotification) { TestNotifier notifier; - auto observer = std::make_shared(); - EXPECT_CALL(*observer, onSomething()); - notifier.addObserver(observer); + auto observer0 = std::make_shared(); + auto observer1 = std::make_shared(); + std::weak_ptr weakObserver1 = observer1; + EXPECT_CALL(*observer0, onSomething()); + EXPECT_CALL(*observer1, onSomething()); + notifier.addObserver(observer0); + notifier.addWeakPtrObserver(weakObserver1); notifier.notifyObservers(invokeOnSomething); } @@ -66,13 +70,26 @@ TEST_F(NotifierTest, test_notificationOrder) { auto observer0 = std::make_shared(); auto observer1 = std::make_shared(); auto observer2 = std::make_shared(); + auto observer3 = std::make_shared(); + auto observer4 = std::make_shared(); + auto observer5 = std::make_shared(); + std::weak_ptr weakObserver1 = observer1; + std::weak_ptr weakObserver3 = observer3; + std::weak_ptr weakObserver5 = observer5; + InSequence sequence; EXPECT_CALL(*observer0, onSomething()); EXPECT_CALL(*observer1, onSomething()); EXPECT_CALL(*observer2, onSomething()); + EXPECT_CALL(*observer3, onSomething()); + EXPECT_CALL(*observer4, onSomething()); + EXPECT_CALL(*observer5, onSomething()); notifier.addObserver(observer0); - notifier.addObserver(observer1); + notifier.addWeakPtrObserver(weakObserver1); notifier.addObserver(observer2); + notifier.addWeakPtrObserver(weakObserver3); + notifier.addObserver(observer4); + notifier.addWeakPtrObserver(weakObserver5); notifier.notifyObservers(invokeOnSomething); } @@ -84,38 +101,49 @@ TEST_F(NotifierTest, test_duplicateAdditions) { auto observer0 = std::make_shared(); auto observer1 = std::make_shared(); auto observer2 = std::make_shared(); - InSequence sequence; - EXPECT_CALL(*observer0, onSomething()); - EXPECT_CALL(*observer1, onSomething()); - EXPECT_CALL(*observer2, onSomething()); + std::weak_ptr weakObserver0 = observer0; + std::weak_ptr weakObserver1 = observer1; + std::weak_ptr weakObserver2 = observer2; + EXPECT_CALL(*observer0, onSomething()).Times(1); + EXPECT_CALL(*observer1, onSomething()).Times(1); + EXPECT_CALL(*observer2, onSomething()).Times(1); notifier.addObserver(observer0); + notifier.addWeakPtrObserver(weakObserver0); + notifier.addWeakPtrObserver(weakObserver1); notifier.addObserver(observer1); notifier.addObserver(observer2); notifier.addObserver(observer1); notifier.addObserver(observer2); notifier.addObserver(observer1); + notifier.addWeakPtrObserver(weakObserver2); + notifier.addWeakPtrObserver(weakObserver2); notifier.notifyObservers(invokeOnSomething); } /** - * Verify addObserverFunc is called on adding an observer when it is set. + * Verify addObserverFunc is called on adding an observer when it is set before and after setAddObserverFunction. */ TEST_F(NotifierTest, test_setAddObserverFunction) { TestNotifier notifier; auto observer0 = std::make_shared(); + auto observer1 = std::make_shared(); + auto observer2 = std::make_shared(); + auto observer3 = std::make_shared(); + std::weak_ptr weakObserver1 = observer1; + std::weak_ptr weakObserver3 = observer3; + EXPECT_CALL(*observer0, onSomething()).Times(1); + EXPECT_CALL(*observer1, onSomething()).Times(1); + EXPECT_CALL(*observer2, onSomething()).Times(1); + EXPECT_CALL(*observer3, onSomething()).Times(1); - bool result = false; std::function&)> addObserverFunction = - [&result](const std::shared_ptr&) { result = true; }; + [](const std::shared_ptr& observer) { observer->onSomething(); }; notifier.addObserver(observer0); - ASSERT_EQ(result, false); - + notifier.addWeakPtrObserver(weakObserver1); notifier.setAddObserverFunction(addObserverFunction); - - auto observer1 = std::make_shared(); - notifier.addObserver(observer1); - ASSERT_EQ(result, true); + notifier.addObserver(observer2); + notifier.addWeakPtrObserver(weakObserver3); } /** @@ -126,13 +154,18 @@ TEST_F(NotifierTest, test_removingObservers) { auto observer0 = std::make_shared(); auto observer1 = std::make_shared(); auto observer2 = std::make_shared(); + auto observer3 = std::make_shared(); + std::weak_ptr weakObserver1 = observer1; + std::weak_ptr weakObserver3 = observer3; InSequence sequence; EXPECT_CALL(*observer2, onSomething()); notifier.addObserver(observer0); - notifier.addObserver(observer1); + notifier.addWeakPtrObserver(weakObserver1); notifier.addObserver(observer2); + notifier.addWeakPtrObserver(weakObserver3); notifier.removeObserver(observer0); notifier.removeObserver(observer1); + notifier.removeWeakPtrObserver(weakObserver3); notifier.notifyObservers(invokeOnSomething); } @@ -144,13 +177,26 @@ TEST_F(NotifierTest, test_notificationInReverseOrder) { auto observer0 = std::make_shared(); auto observer1 = std::make_shared(); auto observer2 = std::make_shared(); + auto observer3 = std::make_shared(); + auto observer4 = std::make_shared(); + auto observer5 = std::make_shared(); + std::weak_ptr weakObserver1 = observer1; + std::weak_ptr weakObserver3 = observer3; + std::weak_ptr weakObserver5 = observer5; + InSequence sequence; + EXPECT_CALL(*observer5, onSomething()); + EXPECT_CALL(*observer4, onSomething()); + EXPECT_CALL(*observer3, onSomething()); EXPECT_CALL(*observer2, onSomething()); EXPECT_CALL(*observer1, onSomething()); EXPECT_CALL(*observer0, onSomething()); notifier.addObserver(observer0); - notifier.addObserver(observer1); + notifier.addWeakPtrObserver(weakObserver1); notifier.addObserver(observer2); + notifier.addWeakPtrObserver(weakObserver3); + notifier.addObserver(observer4); + notifier.addWeakPtrObserver(weakObserver5); notifier.notifyObserversInReverse(invokeOnSomething); } @@ -162,19 +208,36 @@ TEST_F(NotifierTest, test_removeWithinCallback) { auto observer0 = std::make_shared(); auto observer1 = std::make_shared(); auto observer2 = std::make_shared(); - auto removeObservers = [&observer0, &observer2, ¬ifier]() { + auto observer3 = std::make_shared(); + auto observer4 = std::make_shared(); + auto observer5 = std::make_shared(); + std::weak_ptr weakObserver1 = observer1; + std::weak_ptr weakObserver3 = observer3; + std::weak_ptr weakObserver5 = observer5; + auto removeObservers = [&observer0, &observer2, &weakObserver3, ¬ifier]() { notifier.removeObserver(observer0); notifier.removeObserver(observer2); + notifier.removeWeakPtrObserver(weakObserver3); }; InSequence sequence; + notifier.addObserver(observer0); + notifier.addWeakPtrObserver(weakObserver1); + notifier.addObserver(observer2); + notifier.addWeakPtrObserver(weakObserver3); + notifier.addObserver(observer4); + notifier.addWeakPtrObserver(weakObserver5); + EXPECT_CALL(*observer0, onSomething()).Times(1); EXPECT_CALL(*observer1, onSomething()).WillOnce(Invoke(removeObservers)); EXPECT_CALL(*observer2, onSomething()).Times(0); - EXPECT_CALL(*observer1, onSomething()).Times(1); - notifier.addObserver(observer0); - notifier.addObserver(observer1); - notifier.addObserver(observer2); + EXPECT_CALL(*observer3, onSomething()).Times(0); + EXPECT_CALL(*observer4, onSomething()).Times(1); + EXPECT_CALL(*observer5, onSomething()).Times(1); notifier.notifyObservers(invokeOnSomething); + + EXPECT_CALL(*observer1, onSomething()).Times(1); + EXPECT_CALL(*observer4, onSomething()).Times(1); + EXPECT_CALL(*observer5, onSomething()).Times(1); notifier.notifyObservers(invokeOnSomething); } @@ -189,13 +252,14 @@ TEST_F(NotifierTest, test_removeAndAdditionWithinReverseOrderCallback) { auto observer0 = std::make_shared(); auto observer1 = std::make_shared(); auto observer2 = std::make_shared(); - auto removeObservers = [&observer0, &observer2, ¬ifier]() { + std::weak_ptr weakObserver2 = observer2; + auto removeObservers = [&observer0, &weakObserver2, ¬ifier]() { notifier.removeObserver(observer0); - notifier.removeObserver(observer2); + notifier.removeWeakPtrObserver(weakObserver2); }; - auto addObservers = [&observer0, &observer2, ¬ifier]() { + auto addObservers = [&observer0, &weakObserver2, ¬ifier]() { notifier.addObserver(observer0); - notifier.addObserver(observer2); + notifier.addWeakPtrObserver(weakObserver2); }; InSequence sequence; EXPECT_CALL(*observer2, onSomething()).Times(1); @@ -204,11 +268,39 @@ TEST_F(NotifierTest, test_removeAndAdditionWithinReverseOrderCallback) { EXPECT_CALL(*observer1, onSomething()).WillOnce(Invoke(addObservers)); notifier.addObserver(observer0); notifier.addObserver(observer1); - notifier.addObserver(observer2); + notifier.addWeakPtrObserver(weakObserver2); ASSERT_TRUE(notifier.notifyObserversInReverse(invokeOnSomething)); ASSERT_FALSE(notifier.notifyObserversInReverse(invokeOnSomething)); } +/** + * Verify that when weak_ptr observer is expired (the underlying shared_ptr is reset), that the weak_ptr observer will + * not get the notification. + */ +TEST_F(NotifierTest, test_resetSharedPtrWeakPtrCallbackShallNotBeCalled) { + TestNotifier notifier; + auto observer0 = std::make_shared(); + auto observer1 = std::make_shared(); + std::weak_ptr weakObserver0 = observer0; + std::weak_ptr weakObserver1 = observer1; + int count = 0; + + auto invokeCallback = [&count](const std::shared_ptr& observer) { + count++; + observer->onSomething(); + }; + + InSequence sequence; + EXPECT_CALL(*observer0, onSomething()).Times(1); + EXPECT_CALL(*observer1, onSomething()).Times(2); + notifier.addWeakPtrObserver(observer0); + notifier.addWeakPtrObserver(observer1); + notifier.notifyObservers(invokeCallback); + observer0.reset(); + notifier.notifyObservers(invokeCallback); + ASSERT_EQ(3, count); +} + } // namespace test } // namespace acsdkNotifier } // namespace alexaClientSDK diff --git a/shared/acsdkNotifierInterfaces/CMakeLists.txt b/shared/acsdkNotifierInterfaces/CMakeLists.txt index a8be251fe2..76feeac1b5 100644 --- a/shared/acsdkNotifierInterfaces/CMakeLists.txt +++ b/shared/acsdkNotifierInterfaces/CMakeLists.txt @@ -8,5 +8,7 @@ target_include_directories(acsdkNotifierInterfaces INTERFACE "${acsdkNotifierInterfaces_SOURCE_DIR}/include" ) +add_subdirectory("test") + # install interface asdk_install_interface() diff --git a/shared/acsdkNotifierInterfaces/include/acsdkNotifierInterfaces/NotifierInterface.h b/shared/acsdkNotifierInterfaces/include/acsdkNotifierInterfaces/internal/NotifierInterface.h similarity index 65% rename from shared/acsdkNotifierInterfaces/include/acsdkNotifierInterfaces/NotifierInterface.h rename to shared/acsdkNotifierInterfaces/include/acsdkNotifierInterfaces/internal/NotifierInterface.h index 9b42663418..b079ab4edf 100644 --- a/shared/acsdkNotifierInterfaces/include/acsdkNotifierInterfaces/NotifierInterface.h +++ b/shared/acsdkNotifierInterfaces/include/acsdkNotifierInterfaces/internal/NotifierInterface.h @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ACSDKNOTIFIERINTERFACES_NOTIFIERINTERFACE_H_ -#define ACSDKNOTIFIERINTERFACES_NOTIFIERINTERFACE_H_ +#ifndef ACSDKNOTIFIERINTERFACES_INTERNAL_NOTIFIERINTERFACE_H_ +#define ACSDKNOTIFIERINTERFACES_INTERNAL_NOTIFIERINTERFACE_H_ #include #include @@ -39,6 +39,9 @@ class NotifierInterface { * Add an observer. Duplicate additions are ignored. * * @param observer The observer to add. + * + * @deprecated In the future, @c Notifier will no longer maintain the life cycle of its @c observers. Please start + * using the new @c addWeakPtrObserver() API instead. */ virtual void addObserver(const std::shared_ptr& observer) = 0; @@ -46,11 +49,31 @@ class NotifierInterface { * Remove an observer. Invalid requests (nullptr or non member observers) are ignored. * * @param observer The observer to remove. + * + * @deprecated In the future, @c Notifier will no longer maintain the life cycle of its @c observers. Please start + * using the new @c removeWeakPtrObserver() API instead. */ virtual void removeObserver(const std::shared_ptr& observer) = 0; /** - * Notify the observers in the order that they were added via addObserver(). + * Add an observer with weak_ptr. Duplicate additions are ignored. + * + * @param observer The observer to add. + * + * @note Lifecycle of the observer will not be managed by the Notifier. If the observer object is expired, then + * no callback will be called to that object. + */ + virtual void addWeakPtrObserver(const std::weak_ptr& observer) = 0; + + /** + * Remove an observer with weak_ptr. Invalid requests (nullptr or non member observers) are ignored. + * + * @param observer The observer to remove. + */ + virtual void removeWeakPtrObserver(const std::weak_ptr& observer) = 0; + + /** + * Notify the observers in the order that they were added. * * @param notify The function to invoke to notify an observer. */ @@ -69,6 +92,9 @@ class NotifierInterface { * Set the function to be called after an observer is added (for example, to notify the newly-added observer * of the current state). * + * If there's any observers that were added before @c setAddObserverFunction is called, those added observers will + * be notified as well. + * * @warn Use caution when setting this function. The function MUST be reentrant, or else you run the risk * of deadlock. When an observer adds itself to a @c NotifierInterface, this function will be called in the * same context. @@ -81,4 +107,4 @@ class NotifierInterface { } // namespace acsdkNotifierInterfaces } // namespace alexaClientSDK -#endif // ACSDKNOTIFIERINTERFACES_NOTIFIERINTERFACE_H_ +#endif // ACSDKNOTIFIERINTERFACES_INTERNAL_NOTIFIERINTERFACE_H_ diff --git a/shared/acsdkNotifierInterfaces/test/CMakeLists.txt b/shared/acsdkNotifierInterfaces/test/CMakeLists.txt new file mode 100644 index 0000000000..a64ee4d7cb --- /dev/null +++ b/shared/acsdkNotifierInterfaces/test/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +if (BUILD_TESTING) + add_library(NotifierTestLib INTERFACE) + + target_include_directories(NotifierTestLib INTERFACE "${acsdkNotifierInterfaces_SOURCE_DIR}/test/include") + + target_link_libraries(NotifierTestLib INTERFACE acsdkNotifierInterfaces gmock_main) +endif() diff --git a/shared/acsdkNotifierInterfaces/test/include/acsdkNotifierInterfaces/internal/MockNotifier.h b/shared/acsdkNotifierInterfaces/test/include/acsdkNotifierInterfaces/internal/MockNotifier.h new file mode 100644 index 0000000000..f56ebd778d --- /dev/null +++ b/shared/acsdkNotifierInterfaces/test/include/acsdkNotifierInterfaces/internal/MockNotifier.h @@ -0,0 +1,60 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 ACSDKNOTIFIERINTERFACES_INTERNAL_MOCKNOTIFIER_H_ +#define ACSDKNOTIFIERINTERFACES_INTERNAL_MOCKNOTIFIER_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace acsdkNotifierInterfaces { +namespace test { + +/** + * Mock class that implements NotifierInterface. + * + * To make Expect calls against the observer you will need to set a ON_CALL WillByDefault assertion that will pass the + * args to the observer. + * + * ON_CALL(MockNotifier, notifyObservers(_)) + .WillByDefault(Invoke( + [this](std::function&)> + notifyFn) { notifyFn(observer); })); + */ +template +class MockNotifier : public acsdkNotifierInterfaces::NotifierInterface { +public: + MOCK_METHOD1_T(addObserver, void(const std::shared_ptr& observer)); + + MOCK_METHOD1_T(removeObserver, void(const std::shared_ptr& observer)); + + MOCK_METHOD1_T(addWeakPtrObserver, void(const std::weak_ptr& observer)); + + MOCK_METHOD1_T(removeWeakPtrObserver, void(const std::weak_ptr& observer)); + + MOCK_METHOD1_T(notifyObservers, void(std::function&)>)); + + MOCK_METHOD1_T(notifyObserversInReverse, bool(std::function&)>)); + + MOCK_METHOD1_T(setAddObserverFunction, void(std::function&)>)); +}; + +} // namespace test +} // namespace acsdkNotifierInterfaces +} // namespace alexaClientSDK + +#endif // ACSDKNOTIFIERINTERFACES_INTERNAL_MOCKNOTIFIER_H_ diff --git a/shared/acsdkShared/src/CMakeLists.txt b/shared/acsdkShared/src/CMakeLists.txt index eab7c8af24..0fced332ae 100644 --- a/shared/acsdkShared/src/CMakeLists.txt +++ b/shared/acsdkShared/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkShared") -add_library(acsdkShared SHARED +add_library(acsdkShared SharedComponent.cpp) target_include_directories(acsdkShared PUBLIC diff --git a/shared/acsdkShutdownManager/include/acsdkShutdownManager/ShutdownNotifier.h b/shared/acsdkShutdownManager/include/acsdkShutdownManager/ShutdownNotifier.h index 3399c6d040..bbcd228e83 100644 --- a/shared/acsdkShutdownManager/include/acsdkShutdownManager/ShutdownNotifier.h +++ b/shared/acsdkShutdownManager/include/acsdkShutdownManager/ShutdownNotifier.h @@ -18,7 +18,7 @@ #include -#include +#include #include #include diff --git a/shared/acsdkShutdownManager/src/CMakeLists.txt b/shared/acsdkShutdownManager/src/CMakeLists.txt index ea2b86f138..bf7b55a923 100644 --- a/shared/acsdkShutdownManager/src/CMakeLists.txt +++ b/shared/acsdkShutdownManager/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkShutdownManager") -add_library(acsdkShutdownManager SHARED +add_library(acsdkShutdownManager ShutdownManager.cpp ShutdownNotifier.cpp) diff --git a/shared/acsdkShutdownManagerInterfaces/include/acsdkShutdownManagerInterfaces/ShutdownNotifierInterface.h b/shared/acsdkShutdownManagerInterfaces/include/acsdkShutdownManagerInterfaces/ShutdownNotifierInterface.h index e829ff57c6..a3d92a52f8 100644 --- a/shared/acsdkShutdownManagerInterfaces/include/acsdkShutdownManagerInterfaces/ShutdownNotifierInterface.h +++ b/shared/acsdkShutdownManagerInterfaces/include/acsdkShutdownManagerInterfaces/ShutdownNotifierInterface.h @@ -18,7 +18,7 @@ #include -#include +#include #include namespace alexaClientSDK { diff --git a/shared/acsdkShutdownManagerInterfaces/test/acsdkShutdownManagerInterfaces/MockShutdownNotifier.h b/shared/acsdkShutdownManagerInterfaces/test/acsdkShutdownManagerInterfaces/MockShutdownNotifier.h index 8d2f07e506..266585efe5 100644 --- a/shared/acsdkShutdownManagerInterfaces/test/acsdkShutdownManagerInterfaces/MockShutdownNotifier.h +++ b/shared/acsdkShutdownManagerInterfaces/test/acsdkShutdownManagerInterfaces/MockShutdownNotifier.h @@ -32,6 +32,10 @@ class MockShutdownNotifier : public acsdkShutdownManagerInterfaces::ShutdownNoti MOCK_METHOD1(removeObserver, void(const std::shared_ptr& observer)); + MOCK_METHOD1(addWeakPtrObserver, void(const std::weak_ptr& observer)); + + MOCK_METHOD1(removeWeakPtrObserver, void(const std::weak_ptr& observer)); + MOCK_METHOD1( notifyObservers, void(std::function&)>)); diff --git a/shared/acsdkStartupManager/include/acsdkStartupManager/StartupNotifier.h b/shared/acsdkStartupManager/include/acsdkStartupManager/StartupNotifier.h index 8202db581c..c2c398e6ac 100644 --- a/shared/acsdkStartupManager/include/acsdkStartupManager/StartupNotifier.h +++ b/shared/acsdkStartupManager/include/acsdkStartupManager/StartupNotifier.h @@ -18,7 +18,7 @@ #include -#include +#include #include "acsdkStartupManagerInterfaces/RequiresStartupInterface.h" #include "acsdkStartupManagerInterfaces/StartupNotifierInterface.h" diff --git a/shared/acsdkStartupManager/src/CMakeLists.txt b/shared/acsdkStartupManager/src/CMakeLists.txt index 8d46013113..8601f0a6d5 100644 --- a/shared/acsdkStartupManager/src/CMakeLists.txt +++ b/shared/acsdkStartupManager/src/CMakeLists.txt @@ -1,5 +1,5 @@ add_definitions("-DACSDK_LOG_MODULE=acsdkStartupManager") -add_library(acsdkStartupManager SHARED +add_library(acsdkStartupManager StartupManager.cpp StartupNotifier.cpp) diff --git a/shared/acsdkStartupManagerInterfaces/include/acsdkStartupManagerInterfaces/StartupNotifierInterface.h b/shared/acsdkStartupManagerInterfaces/include/acsdkStartupManagerInterfaces/StartupNotifierInterface.h index 1dbcce26d8..41522029f7 100644 --- a/shared/acsdkStartupManagerInterfaces/include/acsdkStartupManagerInterfaces/StartupNotifierInterface.h +++ b/shared/acsdkStartupManagerInterfaces/include/acsdkStartupManagerInterfaces/StartupNotifierInterface.h @@ -18,7 +18,7 @@ #include -#include +#include #include "acsdkStartupManagerInterfaces/RequiresStartupInterface.h" diff --git a/tools/Install/android.sh b/tools/Install/android.sh index 04813f2de5..3710f97986 100644 --- a/tools/Install/android.sh +++ b/tools/Install/android.sh @@ -65,6 +65,7 @@ CURL_VER=${CURL_DEFAULT_VERSION:-7.67.0} OPENSSL_VER=1_1_0h NGHTTP2_VER=1.32.0 FFMPEG_VER=4.0 +LIBARCHIVE_VER=3.5.1 # CMake parameters used to build the SDK. set_cmake_var() { @@ -100,7 +101,10 @@ set_cmake_var() { -DSQLITE_INCLUDE_DIR="${SQLITE3_INCLUDE_DIR}" \ -DFFMPEG_LIB_PATH=${INSTALL_TARGET_LIB} \ -DFFMPEG_INCLUDE_DIR=${INSTALL_TARGET}/include \ - -DTARGET_RPATH=${INSTALL_TARGET_LIB}) + -DTARGET_RPATH=${INSTALL_TARGET_LIB} \ + -DLibArchive_LIBRARIES="${INSTALL_TARGET_LIB}/libarchive.so" \ + -DLibArchive_INCLUDE_DIRS="${INSTALL_TARGET_INCLUDE}" \ + -DCMAKE_CXX_FLAGS="-I${LIBARCHIVE_LIBRARY_SOURCE}/contrib/android/include") #-DBUILD_SHARED_LIBS="ON" \ } @@ -391,6 +395,8 @@ install_dependencies() { download_dependency "OPENSSL_LIBRARY_SOURCE" "Libraries/openssl" "https://github.com/openssl/openssl/archive/OpenSSL_${OPENSSL_VER}.tar.gz" "openssl-OpenSSL_${OPENSSL_VER}" download_dependency "SQLITE3_LIBRARY_SOURCE" "Libraries/sqlite3" "https://sqlite.org/2018/sqlite-autoconf-3240000.tar.gz" "sqlite-autoconf-3240000" download_dependency "FFMPEG_LIBRARY_SOURCE" "Libraries/ffmpeg" "https://www.ffmpeg.org/releases/ffmpeg-${FFMPEG_VER}.tar.gz" "ffmpeg-${FFMPEG_VER}" + # Download libarchive + download_dependency "LIBARCHIVE_LIBRARY_SOURCE" "Libraries/libarchive" "https://www.libarchive.org/downloads/libarchive-${LIBARCHIVE_VER}.tar.gz" "libarchive-${LIBARCHIVE_VER}" ################################################## # Download packages @@ -599,6 +605,37 @@ install_dependencies() { echo "Built on $(date)" > ${DONE_FILE} fi + ################################################## + # Build libarchive + ################################################## + echo "Start building libarchive..." + LIBARCHIVE_BUILD_TARGET="${BUILD_TARGET}/libarchive" + DONE_FILE="${LIBARCHIVE_BUILD_TARGET}/.done" + + if [ -d "${LIBARCHIVE_LIBRARY_SOURCE}" ] && [ ! -f "${DONE_FILE}" ]; then + if [ ! -f "${LIBARCHIVE_BUILD_TARGET}/Makefile" ]; then + mkdir -p "${LIBARCHIVE_BUILD_TARGET}" + pushd "${LIBARCHIVE_BUILD_TARGET}" + TESTCPPFLAGS="-I${LIBARCHIVE_LIBRARY_SOURCE}/contrib/android/include" + echo "test CPPFLAGS" + echo ${TESTCPPFLAGS} + echo "Start configuring libarchive..." + "${LIBARCHIVE_LIBRARY_SOURCE}/configure" \ + --host="${TOOLCHAIN_HOST}" \ + --build="${TOOLCHAIN_BUILD}" \ + --prefix="${INSTALL_TARGET}" \ + CPPFLAGS="-I${LIBARCHIVE_LIBRARY_SOURCE}/contrib/android/include" + popd + fi + pushd "${LIBARCHIVE_BUILD_TARGET}" + make -j ${NUM_THREADS} + removeSymbolsFromRelObjFiles . + make install + popd + + echo "Built on $(date)" > ${DONE_FILE} + fi + ################################################## # Build sqlite3 ################################################## diff --git a/tools/Install/genConfig.sh b/tools/Install/genConfig.sh index ef3e782010..f1ec8d16c7 100755 --- a/tools/Install/genConfig.sh +++ b/tools/Install/genConfig.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # -# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). # You may not use this file except in compliance with the License. @@ -25,10 +25,18 @@ LOCALE=${LOCALE:-'en-US'} INDEX_MANUFACTURER_NAME=0 # The device description. INDEX_DEVICE_DESCRIPTION=1 +# The path to sensory model file. +INDEX_SENSORY_MODEL_FILE_PATH=2 + +# Defaults for PKCS11 +PKCS11_MODULE_PATH="__undefined__" +PKCS11_TOKEN_NAME="__undefined__" +PKCS11_USER_PIN="__undefined__" +PKCS11_KEY_NAME="__undefined__" function printUsageAndExit() { echo 'Usage: genConfig.sh ' - echo ' [-D=]+' + echo ' [-D=]+' echo '1) can be downloaded from developer portal and must contain the following:' echo ' "clientId": ""' echo ' "productId": ""' @@ -40,6 +48,12 @@ function printUsageAndExit() { 'Avaiable variables are:' echo ' "SDK_CONFIG_MANUFACTURER_NAME": The name of the device manufacturer. This variable is required.' echo ' "SDK_CONFIG_DEVICE_DESCRIPTION": The description of the device. This variable is required.' + echo ' "SDK_SENSORY_MODEL_FILE_PATH": The path to the sensory KWD model. This variable is optional for Sensory KWD.\n' \ + ' Please see https://github.com/Sensory/alexa-rpi#model-selection for more information.' + echo ' "PKCS11_MODULE_PATH": PKCS11 Module Path. This variable is required if PKCS11 is enabled.' + echo ' "PKCS11_TOKEN_NAME": PKCS11 Token Name. This variable is required if PKCS11 is enabled.' + echo ' "PKCS11_USER_PIN": PKCS11 User PIN. This variable is required if PKCS11 is enabled.' + echo ' "PKCS11_KEY_NAME": PKCS11 Key Object Name. This variable is required if PKCS11 is enabled.' exit 1 } @@ -49,21 +63,21 @@ then printUsageAndExit fi -CONFIG_JSON_FILE=$1 +CONFIG_JSON_FILE="$1" if [ ! -f "$CONFIG_JSON_FILE" ]; then echo "[ERROR] Config json file not found!" printUsageAndExit fi -DEVICE_SERIAL_NUMBER=$2 +DEVICE_SERIAL_NUMBER="$2" if [[ ! "$DEVICE_SERIAL_NUMBER" =~ [0-9a-zA-Z_]+ ]]; then echo '[ERROR] Device serial number is invalid!' printUsageAndExit fi -CONFIG_DB_PATH=$3 +CONFIG_DB_PATH="$3" -SDK_SRC_PATH=$4 +SDK_SRC_PATH="$4" if [ ! -d "$SDK_SRC_PATH" ]; then echo '[ERROR] Alexa Device Source directory not found!' printUsageAndExit @@ -74,11 +88,9 @@ if [ ! -f "$INPUT_CONFIG_FILE" ]; then printUsageAndExit fi -OUTPUT_CONFIG_FILE=$5 -# Check if output file exists, if yes, create empty file. -if [ -f $OUTPUT_CONFIG_FILE ]; then - echo -n "" > $OUTPUT_CONFIG_FILE -fi +OUTPUT_CONFIG_FILE="$5" +# Remove output file if it already exists. +rm -f "$OUTPUT_CONFIG_FILE" shift 5 declare -a extra_variables @@ -98,6 +110,21 @@ for variable in "$@"; do SDK_CONFIG_MANUFACTURER_NAME ) extra_variables[${INDEX_MANUFACTURER_NAME}]=${variable_value} ;; + SDK_SENSORY_MODEL_FILE_PATH ) + extra_variables[${INDEX_SENSORY_MODEL_FILE_PATH}]=${variable_value} + ;; + PKCS11_MODULE_PATH ) + PKCS11_MODULE_PATH="${variable_value}" + ;; + PKCS11_TOKEN_NAME ) + PKCS11_TOKEN_NAME="${variable_value}" + ;; + PKCS11_USER_PIN ) + PKCS11_USER_PIN="${variable_value}" + ;; + PKCS11_KEY_NAME ) + PKCS11_KEY_NAME="${variable_value}" + ;; * ) echo "[ERROR] Unknown configuration variable ${variable_name}" printUsageAndExit @@ -187,6 +214,18 @@ SDK_LWA_AUTHORIZATION_ADAPTER_DATABASE_FILE_PATH=$CONFIG_DB_PATH/lwaAuthorizatio SDK_CONFIG_MANUFACTURER_NAME=${extra_variables[${INDEX_MANUFACTURER_NAME}]} SDK_CONFIG_DEVICE_DESCRIPTION=${extra_variables[${INDEX_DEVICE_DESCRIPTION}]} +# Variable for Sensory Keyword Detection +if [[ ${extra_variables[${INDEX_SENSORY_MODEL_FILE_PATH}]+foobar} ]] +then + SDK_SENSORY_MODEL_FILE_PATH=${extra_variables[${INDEX_SENSORY_MODEL_FILE_PATH}]} +fi + +# Variables for HSM +SDK_PKCS11_MODULE_PATH="$PKCS11_MODULE_PATH" +SDK_PKCS11_TOKEN_NAME="$PKCS11_TOKEN_NAME" +SDK_PKCS11_KEY_NAME="$PKCS11_KEY_NAME" +SDK_PKCS11_USER_PIN="$PKCS11_USER_PIN" + ######################################################################################################################## # End of setting variables for generating $OUTPUT_CONFIG_FILE # All variables that needs to be substituted must be defined above "set +a" line! @@ -201,4 +240,7 @@ with open("${INPUT_CONFIG_FILE}", "r") as f, open("${OUTPUT_CONFIG_FILE}", "w") o.write(Template(f.read()).safe_substitute(os.environ)) EOF +# Set output file owner-only readable +chmod 400 "${OUTPUT_CONFIG_FILE}" + echo 'Completed generation of config file:' ${OUTPUT_CONFIG_FILE} diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index 0c2f06bc1c..d68ea09c2e 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -1,5 +1,5 @@ # -# Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# Copyright Amazon.com, Inc. or its affiliates. All 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,12 +21,10 @@ fi SOUND_CONFIG="$HOME/.asoundrc" START_SCRIPT="$INSTALL_BASE/startsample.sh" START_PREVIEW_SCRIPT="$INSTALL_BASE/startpreview.sh" -CMAKE_PLATFORM_SPECIFIC=(-DSENSORY_KEY_WORD_DETECTOR=ON \ - -DGSTREAMER_MEDIA_PLAYER=ON -DPORTAUDIO=ON \ +CMAKE_PLATFORM_SPECIFIC=(-DGSTREAMER_MEDIA_PLAYER=ON \ + -DPORTAUDIO=ON \ -DPORTAUDIO_LIB_PATH="$THIRD_PARTY_PATH/portaudio/lib/.libs/libportaudio.$LIB_SUFFIX" \ -DPORTAUDIO_INCLUDE_DIR="$THIRD_PARTY_PATH/portaudio/include" \ - -DSENSORY_KEY_WORD_DETECTOR_LIB_PATH=$THIRD_PARTY_PATH/alexa-rpi/lib/libsnsr.a \ - -DSENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR=$THIRD_PARTY_PATH/alexa-rpi/include \ -DCURL_INCLUDE_DIR=${THIRD_PARTY_PATH}/curl-${CURL_VER}/include \ -DCURL_LIBRARY=${THIRD_PARTY_PATH}/curl-${CURL_VER}/lib/.libs/libcurl.so) @@ -40,7 +38,9 @@ install_dependencies() { run_os_specifics() { build_port_audio build_curl - build_kwd_engine + echo + echo "==============> TAP-TO-TALK IS ENABLED ==============" + echo configure_sound } @@ -64,17 +64,6 @@ configure_sound() { EOF } -build_kwd_engine() { - #get sensory and build - echo - echo "==============> CLONING AND BUILDING SENSORY ==============" - echo - - cd $THIRD_PARTY_PATH - git clone git://github.com/Sensory/alexa-rpi.git - bash ./alexa-rpi/bin/license.sh -} - build_curl() { #get curl and build echo @@ -93,13 +82,13 @@ generate_start_script() { cat << EOF > "$START_SCRIPT" cd "$BUILD_PATH/SampleApp/src" - PA_ALSA_PLUGHW=1 ./SampleApp "$OUTPUT_CONFIG_FILE" "$THIRD_PARTY_PATH/alexa-rpi/models" DEBUG9 + PA_ALSA_PLUGHW=1 ./SampleApp "$OUTPUT_CONFIG_FILE" DEBUG9 EOF cat << EOF > "$START_PREVIEW_SCRIPT" cd "$BUILD_PATH/applications/acsdkPreviewAlexaClient/src" - PA_ALSA_PLUGHW=1 ./PreviewAlexaClient "$OUTPUT_CONFIG_FILE" "$THIRD_PARTY_PATH/alexa-rpi/models" DEBUG9 + PA_ALSA_PLUGHW=1 ./PreviewAlexaClient "$OUTPUT_CONFIG_FILE" DEBUG9 EOF } @@ -108,8 +97,6 @@ generate_test_script() { echo echo "==============> BUILDING Tests ==============" echo - mkdir -p "$UNIT_TEST_MODEL_PATH" - cp "$UNIT_TEST_MODEL" "$UNIT_TEST_MODEL_PATH" cd $BUILD_PATH make all test -j2 EOF diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh old mode 100644 new mode 100755 index 084f9c9837..5ae9a0d2d1 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # -# Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). # You may not use this file except in compliance with the License. @@ -26,8 +26,6 @@ PORT_AUDIO_DOWNLOAD_URL="http://www.portaudio.com/archives/$PORT_AUDIO_FILE" CURL_VER=7.67.0 CURL_DOWNLOAD_URL="https://github.com/curl/curl/releases/download/curl-7_67_0/curl-${CURL_VER}.tar.gz" -TEST_MODEL_DOWNLOAD="https://github.com/Sensory/alexa-rpi/blob/master/models/spot-alexa-rpi-31000.snsr" - BUILD_TESTS=${BUILD_TESTS:-'true'} CURRENT_DIR="$(pwd)" @@ -44,8 +42,6 @@ BUILD_PATH="$INSTALL_BASE/$BUILD_FOLDER" SOUNDS_PATH="$INSTALL_BASE/$SOUNDS_FOLDER" DB_PATH="$INSTALL_BASE/$DB_FOLDER" CONFIG_DB_PATH="$DB_PATH" -UNIT_TEST_MODEL_PATH="$INSTALL_BASE/avs-device-sdk/KWD/inputs/SensoryModels/" -UNIT_TEST_MODEL="$THIRD_PARTY_PATH/alexa-rpi/models/spot-alexa-rpi-31000.snsr" INPUT_CONFIG_FILE="$SOURCE_PATH/avs-device-sdk/Integration/AlexaClientSDKConfig.json" OUTPUT_CONFIG_FILE="$BUILD_PATH/Integration/AlexaClientSDKConfig.json" TEMP_CONFIG_FILE="$BUILD_PATH/Integration/tmp_AlexaClientSDKConfig.json" @@ -64,6 +60,12 @@ DEVICE_DESCRIPTION=${DEVICE_DESCRIPTION:-"Test Device"} GSTREAMER_AUDIO_SINK="autoaudiosink" +# Defaults for HSM integration +ACSDK_PKCS11_MODULE= +ACSDK_PKCS11_KEY= +ACSDK_PKCS11_PIN= +ACSDK_PKCS11_TOKEN= + build_port_audio() { # build port audio echo @@ -108,6 +110,10 @@ show_help() { echo ' -a The file that contains Android installation configurations (e.g. androidConfig.txt)' echo ' -d The description of the device.' echo ' -m The device manufacturer name.' + echo ' -p Absolute path to PKCS#11 interface library.' + echo ' -t PKCS#11 token name.' + echo ' -i PKCS#11 user pin to access key object functions.' + echo ' -k PKCS#11 key object label.' echo ' -h Display this help and exit' } @@ -124,7 +130,7 @@ if [ ! -f "$CONFIG_JSON_FILE" ]; then fi shift 1 -OPTIONS=s:a:m:d:h +OPTIONS=s:a:m:d:hp:k:i:t: while getopts "$OPTIONS" opt ; do case $opt in s ) @@ -148,6 +154,18 @@ while getopts "$OPTIONS" opt ; do show_help exit 1 ;; + p ) + ACSDK_PKCS11_MODULE="$OPTARG" + ;; + k ) + ACSDK_PKCS11_KEY="$OPTARG" + ;; + i ) + ACSDK_PKCS11_PIN="$OPTARG" + ;; + t ) + ACSDK_PKCS11_TOKEN="$OPTARG" + ;; esac done @@ -156,6 +174,49 @@ if [[ ! "$DEVICE_SERIAL_NUMBER" =~ [0-9a-zA-Z_]+ ]]; then exit 1 fi +if [ -z "$ACSDK_PKCS11_MODULE" ] && [ -z "$ACSDK_PKCS11_KEY" ] && [ -z "$ACSDK_PKCS11_PIN" ] && [ -z "$ACSDK_PKCS11_TOKEN" ] +then + echo "PKCS11 parameters are not specified. Hardware security module integration is disabled." + ACSDK_PKCS11=OFF + ACSDK_PKCS11_MODULE=__undefined__ + ACSDK_PKCS11_KEY=__undefined__ + ACSDK_PKCS11_PIN=__undefined__ + ACSDK_PKCS11_TOKEN=__undefined__ +else + echo "PKCS11 parameters are specified. Hardware security module integration is enabled." + ACSDK_PKCS11=ON + + if [ -z "$ACSDK_PKCS11_MODULE" ] + then + echo "PKCS11 module path is not specified, but other PKCS11 parameters are present." + exit 1 + elif [ ! -f "$ACSDK_PKCS11_MODULE" ] + then + echo "PKCS11 module path is specified, but library is not found." + exit 1 + fi + + if [ -z "$ACSDK_PKCS11_KEY" ] + then + echo "PKCS11 key name is not specified, but other PKCS11 parameters are present." + exit 1 + fi + + if [ -z "$ACSDK_PKCS11_PIN" ] + then + echo "PKCS11 pin is not specified, but other PKCS11 parameters are present." + exit 1 + fi + + if [ -z "$ACSDK_PKCS11_TOKEN" ] + then + echo "PKCS11 token name is not specified, but other PKCS11 parameters are present." + exit 1 + fi + +fi + + # The target platform for the build. PLATFORM=${PLATFORM:-$(get_platform)} @@ -258,33 +319,32 @@ then cd $BUILD_PATH cmake "$SOURCE_PATH/avs-device-sdk" \ -DCMAKE_BUILD_TYPE=DEBUG \ + -DPKCS11=$ACSDK_PKCS11 \ "${CMAKE_PLATFORM_SPECIFIC[@]}" cd $BUILD_PATH make SampleApp -j2 make PreviewAlexaClient -j2 + make all -j2 else cd $BUILD_PATH make SampleApp -j2 make PreviewAlexaClient -j2 + make all -j2 fi echo echo "==============> SAVING CONFIGURATION FILE ==============" echo -GSTREAMER_CONFIG=$(cat <<-END - { - "gstreamerMediaPlayer":{ - "audioSink":"$GSTREAMER_AUDIO_SINK" - }, -END -) +GSTREAMER_CONFIG="{\\n \"gstreamerMediaPlayer\":{\\n \"audioSink\":\"$GSTREAMER_AUDIO_SINK\"\\n }," cd $INSTALL_BASE bash genConfig.sh config.json $DEVICE_SERIAL_NUMBER $CONFIG_DB_PATH $SOURCE_PATH/avs-device-sdk $TEMP_CONFIG_FILE \ - -DSDK_CONFIG_MANUFACTURER_NAME="$DEVICE_MANUFACTURER_NAME" -DSDK_CONFIG_DEVICE_DESCRIPTION="$DEVICE_DESCRIPTION" + -DSDK_CONFIG_MANUFACTURER_NAME="$DEVICE_MANUFACTURER_NAME" -DSDK_CONFIG_DEVICE_DESCRIPTION="$DEVICE_DESCRIPTION" \ + -DPKCS11_MODULE_PATH=$ACSDK_PKCS11_MODULE -DPKCS11_TOKEN_NAME=$ACSDK_PKCS11_TOKEN \ + -DPKCS11_USER_PIN=$ACSDK_PKCS11_PIN -DPKCS11_KEY_NAME=$ACSDK_PKCS11_KEY # Replace the first opening bracket in the AlexaClientSDKConfig.json file with GSTREAMER_CONFIG variable. awk -v config="$GSTREAMER_CONFIG" 'NR==1,/{/{sub(/{/,config)}1' $TEMP_CONFIG_FILE > $OUTPUT_CONFIG_FILE diff --git a/tools/Testing.cmake b/tools/Testing.cmake index 8fc58ddfc2..95540396e1 100644 --- a/tools/Testing.cmake +++ b/tools/Testing.cmake @@ -1,3 +1,5 @@ +include(CheckCXXCompilerFlag) + if(POLICY CMP0057) cmake_policy(SET CMP0057 NEW) endif() @@ -25,6 +27,10 @@ macro(discover_unit_tests includes libraries) get_filename_component(testname ${testsourcefile} NAME_WE) add_executable(${testname} ${testsourcefile}) add_dependencies(unit ${testname}) + CHECK_CXX_COMPILER_FLAG("-Wno-deprecated-declarations" HAS_NO_DEPRECATED_DECLARATIONS) + if (HAS_NO_DEPRECATED_DECLARATIONS) + target_compile_options(${testname} PRIVATE -Wno-deprecated-declarations) + endif() target_include_directories(${testname} PRIVATE ${includes}) # Do not include gtest_main due to double free issue # - https://github.com/google/googletest/issues/930 From 7f9b5b7885640cf0df676ef3a777d456332d284b Mon Sep 17 00:00:00 2001 From: Lucas Frossard Date: Wed, 23 Mar 2022 23:39:46 +0000 Subject: [PATCH 02/38] Correct accidental addition of Apache license text at top of some earcon files. --- .../include/Audio/Data/create_header.bash | 21 +++-------------- .../Data/med_alerts_notification_01.mp3.h | 23 ++++--------------- .../Data/med_alerts_notification_02.mp3.h | 23 ++++--------------- .../Data/med_alerts_notification_03.mp3.h | 23 ++++--------------- .../Audio/Data/med_comms_call_connected.mp3.h | 23 ++++--------------- .../Data/med_comms_call_disconnected.mp3.h | 23 ++++--------------- .../med_comms_call_incoming_ringtone.mp3.h | 23 ++++--------------- .../Data/med_comms_drop_in_incoming.mp3.h | 23 ++++--------------- .../Data/med_comms_outbound_ringtone.mp3.h | 23 ++++--------------- .../Data/med_state_bluetooth_connected.mp3.h | 23 ++++--------------- .../med_state_bluetooth_disconnected.mp3.h | 23 ++++--------------- .../Data/med_state_privacy_mode_off.wav.h | 23 ++++--------------- .../Data/med_state_privacy_mode_on.wav.h | 23 ++++--------------- .../Data/med_system_alerts_melodic_01.mp3.h | 23 ++++--------------- .../med_system_alerts_melodic_01_short.wav.h | 23 ++++--------------- .../Data/med_system_alerts_melodic_02.mp3.h | 23 ++++--------------- .../med_system_alerts_melodic_02_short.wav.h | 23 ++++--------------- .../Audio/Data/med_ui_endpointing.wav.h | 23 ++++--------------- .../Audio/Data/med_ui_endpointing_touch.wav.h | 23 ++++--------------- .../include/Audio/Data/med_ui_wakesound.wav.h | 23 ++++--------------- .../Audio/Data/med_ui_wakesound_touch.wav.h | 23 ++++--------------- .../Audio/Data/med_utility_500ms_blank.wav.h | 23 ++++--------------- NOTICE.txt | 8 +++---- 23 files changed, 91 insertions(+), 421 deletions(-) diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash b/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash index 7e8ca9bc67..ad1c51c01c 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash @@ -16,21 +16,6 @@ GUARD=`echo "ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO CURR_YEAR=`date +"%Y"` cat < "${FULL_OUTPUT}" -/* - * Copyright ${CURR_YEAR} Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS @@ -39,9 +24,9 @@ cat < "${FULL_OUTPUT}" * Copyright ${CURR_YEAR} Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ EOF diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_01.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_01.mp3.h index a600c5b23c..131f23495d 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_01.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_01.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_ALERTS_NOTIFICATION_01_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_02.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_02.mp3.h index 824a9233b8..49d934637b 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_02.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_02.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_ALERTS_NOTIFICATION_02_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_03.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_03.mp3.h index 6550cae399..5f7b50dad5 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_03.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_alerts_notification_03.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_ALERTS_NOTIFICATION_03_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_connected.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_connected.mp3.h index 3262f03e00..b5c9d2de4a 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_connected.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_connected.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_COMMS_CALL_CONNECTED_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_disconnected.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_disconnected.mp3.h index 4737615077..6405379783 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_disconnected.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_disconnected.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_COMMS_CALL_DISCONNECTED_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_incoming_ringtone.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_incoming_ringtone.mp3.h index b113913e57..301580da0b 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_incoming_ringtone.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_call_incoming_ringtone.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_COMMS_CALL_INCOMING_RINGTONE_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_drop_in_incoming.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_drop_in_incoming.mp3.h index de8c7324f2..8d0f386f6e 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_drop_in_incoming.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_drop_in_incoming.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_COMMS_DROP_IN_INCOMING_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_outbound_ringtone.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_outbound_ringtone.mp3.h index e8c9e4b133..0d47a1dce6 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_outbound_ringtone.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_comms_outbound_ringtone.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_COMMS_OUTBOUND_RINGTONE_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_bluetooth_connected.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_bluetooth_connected.mp3.h index 2f4ccd702f..290b65deca 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_bluetooth_connected.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_bluetooth_connected.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_STATE_BLUETOOTH_CONNECTED_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_bluetooth_disconnected.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_bluetooth_disconnected.mp3.h index 7b1e21d03b..7a4388d2ec 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_bluetooth_disconnected.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_bluetooth_disconnected.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_STATE_BLUETOOTH_DISCONNECTED_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_privacy_mode_off.wav.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_privacy_mode_off.wav.h index b612e56b16..1cf654b459 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_privacy_mode_off.wav.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_privacy_mode_off.wav.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_STATE_PRIVACY_MODE_OFF_WAV_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_privacy_mode_on.wav.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_privacy_mode_on.wav.h index 2807f7aa33..ce63d85f27 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_privacy_mode_on.wav.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_state_privacy_mode_on.wav.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_STATE_PRIVACY_MODE_ON_WAV_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_01.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_01.mp3.h index 5a98458b3a..ee502709c1 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_01.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_01.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_SYSTEM_ALERTS_MELODIC_01_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_01_short.wav.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_01_short.wav.h index a9cdc37a0a..d2dbdaa525 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_01_short.wav.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_01_short.wav.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_SYSTEM_ALERTS_MELODIC_01_SHORT_WAV_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_02.mp3.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_02.mp3.h index 8a9374b126..6211b25d29 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_02.mp3.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_02.mp3.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_SYSTEM_ALERTS_MELODIC_02_MP3_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_02_short.wav.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_02_short.wav.h index 2fc368e6cf..3354b36697 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_02_short.wav.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_system_alerts_melodic_02_short.wav.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_SYSTEM_ALERTS_MELODIC_02_SHORT_WAV_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_endpointing.wav.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_endpointing.wav.h index c91d9d2bfc..a3c27f6e77 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_endpointing.wav.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_endpointing.wav.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_UI_ENDPOINTING_WAV_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_endpointing_touch.wav.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_endpointing_touch.wav.h index 53d8a84dab..1677f1c0a3 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_endpointing_touch.wav.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_endpointing_touch.wav.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_UI_ENDPOINTING_TOUCH_WAV_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_wakesound.wav.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_wakesound.wav.h index 25d5d38947..4076e5316d 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_wakesound.wav.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_wakesound.wav.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_UI_WAKESOUND_WAV_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_wakesound_touch.wav.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_wakesound_touch.wav.h index e18bf16737..4e3251b302 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_wakesound_touch.wav.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_ui_wakesound_touch.wav.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_UI_WAKESOUND_TOUCH_WAV_H_ diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_utility_500ms_blank.wav.h b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_utility_500ms_blank.wav.h index 52365d075d..12839fcec0 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_utility_500ms_blank.wav.h +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/med_utility_500ms_blank.wav.h @@ -1,29 +1,14 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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. - */ - /* * ****************** * ALEXA AUDIO ASSETS * ****************** * - * Copyright 2019-2020 Amazon.com, Inc. or its affiliates ("Amazon"). + * Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). * All Rights Reserved. * - * These materials are licensed to you as "Alexa Materials" under the Alexa Voice - * Service Agreement, which is currently available at - * https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements. + * These materials are licensed to you as “AVS Materials" under the Amazon + * Developer Services Agreement, which is currently available at + * https://developer.amazon.com/support/legal/da */ #ifndef ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_RESOURCES_AUDIO_INCLUDE_AUDIO_DATA_MED_UTILITY_500MS_BLANK_WAV_H_ diff --git a/NOTICE.txt b/NOTICE.txt index 23a4222929..5af4c0c79a 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -59,12 +59,12 @@ of the Amazon Software License is located at: ALEXA AUDIO ASSETS ****************** -Copyright 2018 Amazon.com, Inc. or its affiliates (“Amazon”). +Copyright 2019-2022 Amazon.com, Inc. or its affiliates ("Amazon"). All Rights Reserved. -These materials are licensed to you as "Program Materials" under the Program -Materials License Agreement, which is currently available at -https://developer.amazon.com/support/legal/pml. +These materials are licensed to you as “AVS Materials" under the Amazon +Developer Services Agreement, which is currently available at +https://developer.amazon.com/support/legal/da ********************** THIRD PARTY COMPONENTS From b32be0a1139be1aa22f1df5f59ba394d3b6a9697 Mon Sep 17 00:00:00 2001 From: Varun Pandey Date: Thu, 29 Sep 2022 13:59:39 -0700 Subject: [PATCH 03/38] Version 3.0.0 alexa-client-sdk Changes in this update: Feature enhancements, updates, and resolved issues from all releases are available on the [Amazon developer portal](https://developer.amazon.com/docs/alexa/avs-device-sdk/release-notes.html) --- .../ACL/Transport/MessageRequestHandler.h | 4 +- .../ACL/Transport/MessageRouterFactory.h | 1 - .../Transport/MessageRouterFactoryInterface.h | 1 - ACL/src/AVSConnectionManager.cpp | 2 +- ACL/src/Transport/DownchannelHandler.cpp | 2 +- ACL/src/Transport/ExchangeHandler.cpp | 2 +- ACL/src/Transport/HTTP2Transport.cpp | 8 +- ACL/src/Transport/HTTP2TransportFactory.cpp | 2 +- ACL/src/Transport/MessageRequestHandler.cpp | 48 +- ACL/src/Transport/MessageRequestQueue.cpp | 2 +- ACL/src/Transport/MessageRouter.cpp | 10 +- ACL/src/Transport/MessageRouterFactory.cpp | 1 - ACL/src/Transport/MimeResponseSink.cpp | 2 +- ACL/src/Transport/PingHandler.cpp | 2 +- ACL/src/Transport/PostConnectSequencer.cpp | 18 +- .../Transport/PostConnectSequencerFactory.cpp | 2 +- .../SynchronizedMessageRequestQueue.cpp | 2 +- .../Transport/Common/MockHTTP2Connection.cpp | 6 +- .../Transport/Common/MockMimeResponseSink.cpp | 2 +- ACL/test/Transport/HTTP2TransportTest.cpp | 4 +- ACL/test/Transport/MessageRouterTest.cpp | 2 +- ADSL/src/DirectiveProcessor.cpp | 3 +- ADSL/src/DirectiveRouter.cpp | 2 +- ADSL/src/DirectiveSequencer.cpp | 2 +- ADSL/src/MessageInterpreter.cpp | 2 +- AFML/src/AudioActivityTracker.cpp | 6 +- AFML/src/Channel.cpp | 4 +- AFML/src/FocusManagementComponent.cpp | 2 +- AFML/src/FocusManager.cpp | 12 +- AFML/src/VisualActivityTracker.cpp | 6 +- .../AVS/AVSDiscoveryEndpointAttributes.h | 20 + .../AVSCommon/AVS/CapabilityChangeNotifier.h | 4 +- .../AVS/CapabilityChangeNotifierInterface.h | 4 +- .../AVSCommon/AVS/CapabilityConfiguration.h | 8 +- .../include/AVSCommon/AVS/MessageRequest.h | 29 + AVSCommon/AVS/src/AVSContext.cpp | 2 +- AVSCommon/AVS/src/AVSDirective.cpp | 2 +- .../AVS/src/AbstractAVSConnectionManager.cpp | 2 +- AVSCommon/AVS/src/AlexaClientSDKInit.cpp | 2 +- .../AVS/src/Attachment/AttachmentManager.cpp | 2 +- .../AVS/src/Attachment/AttachmentUtils.cpp | 2 +- .../Attachment/InProcessAttachmentWriter.cpp | 2 +- AVSCommon/AVS/src/BlockingPolicy.cpp | 7 +- AVSCommon/AVS/src/CapabilityAgent.cpp | 6 +- AVSCommon/AVS/src/CapabilityResources.cpp | 2 +- .../ActionsToDirectiveMapping.cpp | 2 +- .../CapabilitySemantics.cpp | 2 +- .../StatesToRangeMapping.cpp | 2 +- .../StatesToValueMapping.cpp | 2 +- AVSCommon/AVS/src/ComponentConfiguration.cpp | 2 +- AVSCommon/AVS/src/DialogUXStateAggregator.cpp | 20 +- AVSCommon/AVS/src/DirectiveRoutingRule.cpp | 2 +- AVSCommon/AVS/src/EditableMessageRequest.cpp | 2 +- AVSCommon/AVS/src/EventBuilder.cpp | 2 +- .../AVS/src/ExceptionEncounteredSender.cpp | 2 +- .../Initialization/SDKPrimitivesProvider.cpp | 2 +- AVSCommon/AVS/src/MessageRequest.cpp | 60 +- AVSCommon/AVS/src/WaitableMessageRequest.cpp | 2 +- .../test/Attachment/AttachmentReaderTest.cpp | 4 +- .../test/Attachment/AttachmentWriterTest.cpp | 4 +- .../AVS/test/DialogUXStateAggregatorTest.cpp | 3 +- AVSCommon/AVS/test/MessageRequestTest.cpp | 97 +- AVSCommon/CMakeLists.txt | 7 +- .../AlexaInterfaceMessageSenderInterface.h | 72 +- .../AudioInputProcessorObserverInterface.h | 8 + .../Bluetooth/BluetoothDeviceInterface.h | 11 + .../Services/BluetoothServiceInterface.h | 4 +- .../CallStateObserverInterface.h | 4 + .../SDKInterfaces/GUIActivityEvent.h | 94 + .../GUIActivityEventObserverInterface.h | 44 + .../PostConnectOperationInterface.h | 12 +- .../SDKInterfaces/SpeakerManagerInterface.h | 19 + .../SpeechInteractionHandlerInterface.h | 1 + .../Bluetooth/MockBluetoothDevice.h | 12 + .../Endpoints/MockEndpointBuilder.h | 57 +- .../SDKInterfaces/MockSpeakerManager.h | 7 + .../Utils/Configuration/ConfigurationNode.h | 9 + .../AVSCommon/Utils/Error/FinallyGuard.h | 11 + .../AVSCommon/Utils/JSON/JSONGenerator.h | 9 +- .../LibcurlUtils/CurlMultiHandleWrapper.h | 20 +- .../LibcurlUtils/LibcurlHTTP2Connection.h | 19 +- .../include/AVSCommon/Utils/Logger/LogEntry.h | 139 +- .../include/AVSCommon/Utils/Logger/Logger.h | 312 +- .../Utils/Logger/LoggerSinkManager.h | 3 +- .../AVSCommon/Utils/Logger/ThreadMoniker.h | 84 +- .../AVSCommon/Utils/MediaPlayer/ErrorTypes.h | 1 + .../Utils/MediaPlayer/MediaDescription.h | 3 +- .../Utils/Metrics/MetricRecorderInterface.h | 9 +- .../AVSCommon/Utils/PlatformDefinitions.h | 72 + .../Power/AggregatedPowerResourceManager.h | 2 + .../AVSCommon/Utils/SDS/BufferLayout.h | 13 +- .../include/AVSCommon/Utils/SDS/Reader.h | 6 +- .../AVSCommon/Utils/Threading/Executor.h | 269 +- .../Utils/Threading/ExecutorFactory.h | 40 + .../Utils/Threading/ExecutorInterface.h | 78 + .../AVSCommon/Utils/Threading/TaskThread.h | 11 +- .../AVSCommon/Utils/Threading/ThreadPool.h | 17 +- .../AVSCommon/Utils/Threading/WorkerThread.h | 13 +- .../AVSCommon/Utils/Timing/MultiTimer.h | 18 +- .../include/AVSCommon/Utils/Timing/Timer.h | 349 +- .../AVSCommon/Utils/Timing/TimerDelegate.h | 4 +- .../include/AVSCommon/Utils}/TypeIndex.h | 35 +- .../Utils/Threading/private/SharedExecutor.h | 155 + AVSCommon/Utils/src/BluetoothEventBus.cpp | 2 +- .../src/Configuration/ConfigurationNode.cpp | 15 +- AVSCommon/Utils/src/DeviceInfo.cpp | 2 +- AVSCommon/Utils/src/Executor.cpp | 104 - .../src/FileSystem/FileSystemUtilsLinux.cpp | 7 +- .../src/FileSystem/FileSystemUtilsWindows.cpp | 9 +- AVSCommon/Utils/src/FileUtils.cpp | 2 +- .../Utils/src/FormattedAudioStreamAdapter.cpp | 2 +- .../src/HTTP2/HTTP2MimeRequestEncoder.cpp | 2 +- .../src/HTTP2/HTTP2MimeResponseDecoder.cpp | 2 +- AVSCommon/Utils/src/ID3Tags/ID3v2Tags.cpp | 2 +- AVSCommon/Utils/src/JSON/JSONGenerator.cpp | 35 +- AVSCommon/Utils/src/JSON/JSONUtils.cpp | 2 +- .../Utils/src/LibcurlUtils/CallbackData.cpp | 2 +- .../LibcurlUtils/CurlEasyHandleWrapper.cpp | 2 +- .../LibcurlUtils/CurlMultiHandleWrapper.cpp | 24 +- .../HTTPContentFetcherFactory.cpp | 2 +- AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp | 2 +- AVSCommon/Utils/src/LibcurlUtils/HttpPut.cpp | 2 +- .../LibCurlHttpContentFetcher.cpp | 7 +- .../LibcurlUtils/LibcurlHTTP2Connection.cpp | 49 +- .../LibcurlHTTP2ConnectionFactory.cpp | 2 +- .../src/LibcurlUtils/LibcurlHTTP2Request.cpp | 2 +- .../Utils/src/LibcurlUtils/LibcurlUtils.cpp | 2 +- AVSCommon/Utils/src/Logger/LogEntry.cpp | 70 +- AVSCommon/Utils/src/Logger/LogEntryBuffer.cpp | 2 +- AVSCommon/Utils/src/Logger/Logger.cpp | 8 +- .../Utils/src/Logger/LoggerSinkManager.cpp | 3 +- AVSCommon/Utils/src/Logger/LoggerUtils.cpp | 1 + AVSCommon/Utils/src/Logger/ModuleLogger.cpp | 6 +- AVSCommon/Utils/src/Logger/ThreadMoniker.cpp | 145 +- AVSCommon/Utils/src/MacAddressString.cpp | 1 + .../Utils/src/MediaPlayer/PlaybackContext.cpp | 2 +- .../MediaPlayer/PooledMediaPlayerFactory.cpp | 2 +- .../PooledMediaResourceProvider.cpp | 2 +- .../src/Metrics/DataPointCounterBuilder.cpp | 2 +- .../src/Metrics/DataPointDurationBuilder.cpp | 2 +- AVSCommon/Utils/src/Metrics/MetricEvent.cpp | 2 +- .../Utils/src/Metrics/MetricEventBuilder.cpp | 2 +- AVSCommon/Utils/src/Metrics/UplData.cpp | 2 +- AVSCommon/Utils/src/MultiTimer.cpp | 43 +- .../src/Network/InternetConnectionMonitor.cpp | 2 +- .../Power/AggregatedPowerResourceManager.cpp | 18 +- AVSCommon/Utils/src/Power/PowerMonitor.cpp | 2 +- AVSCommon/Utils/src/Power/PowerResource.cpp | 2 +- AVSCommon/Utils/src/RequiresShutdown.cpp | 2 +- AVSCommon/Utils/src/RetryTimer.cpp | 4 +- AVSCommon/Utils/src/Stopwatch.cpp | 2 +- AVSCommon/Utils/src/Stream/Streambuf.cpp | 29 +- AVSCommon/Utils/src/StringUtils.cpp | 2 +- AVSCommon/Utils/src/TaskThread.cpp | 27 +- AVSCommon/Utils/src/ThreadPool.cpp | 36 +- .../Threading/ConditionVariableWrapper.cpp | 2 +- AVSCommon/Utils/src/Threading/Executor.cpp | 87 + .../Utils/src/Threading/ExecutorFactory.cpp | 46 + .../Utils/src/Threading/SharedExecutor.cpp | 203 + AVSCommon/Utils/src/TimePoint.cpp | 2 +- AVSCommon/Utils/src/TimeUtils.cpp | 60 +- AVSCommon/Utils/src/Timer.cpp | 95 +- AVSCommon/Utils/src/Timing/TimerDelegate.cpp | 52 +- AVSCommon/Utils/src/UUIDGeneration.cpp | 12 +- AVSCommon/Utils/src/WavUtils.cpp | 10 +- AVSCommon/Utils/src/WorkerThread.cpp | 14 +- .../AggregatedPowerResourceManagerTest.cpp | 6 +- AVSCommon/Utils/test/CMakeLists.txt | 5 +- AVSCommon/Utils/test/Common/Common.cpp | 4 +- .../test/Common/TestableMessageObserver.cpp | 2 +- .../test/Common/Timing/StopTaskTimer.cpp | 4 +- AVSCommon/Utils/test/ExecutorTest.cpp | 196 +- AVSCommon/Utils/test/FileSystemUtilsTest.cpp | 2 +- AVSCommon/Utils/test/GmockExtensionTest.cpp | 221 + AVSCommon/Utils/test/JSONUtilTest.cpp | 4 +- .../Utils/test/LibCurlHTTP2ConnectionTest.cpp | 68 + AVSCommon/Utils/test/LoggerTest.cpp | 35 +- AVSCommon/Utils/test/MIMEParserTest.cpp | 39 +- .../test/PooledMediaPlayerFactoryTest.cpp | 2 +- AVSCommon/Utils/test/SafeTimeAccessTest.cpp | 2 +- AVSCommon/Utils/test/SharedDataStreamTest.cpp | 6 +- AVSCommon/Utils/test/TaskThreadTest.cpp | 56 +- AVSCommon/Utils/test/TimeUtilsTest.cpp | 26 + AVSCommon/Utils/test/TimerTest.cpp | 4 +- AVSCommon/Utils/test/UUIDGenerationTest.cpp | 6 +- AVSCommon/Utils/test/WorkerThreadTest.cpp | 1 - .../Utils/test/acsdk/Test/GmockExtensions.h | 361 + .../AVSGatewayManager/AVSGatewayManager.h | 17 +- .../PostConnectVerifyGatewaySender.h | 17 +- AVSGatewayManager/src/AVSGatewayManager.cpp | 32 +- .../src/PostConnectVerifyGatewaySender.cpp | 47 +- .../src/Storage/AVSGatewayManagerStorage.cpp | 2 +- .../AndroidUtilities/PlatformSpecificValues.h | 35 - .../src/AndroidSLESBufferQueue.cpp | 2 +- .../src/AndroidSLESEngine.cpp | 2 +- .../src/AndroidSLESMicrophone.cpp | 2 +- .../src/AndroidSLESObject.cpp | 2 +- .../include/DefaultClient/DefaultClient.h | 115 +- .../DefaultClient/DefaultClientBuilder.h | 473 + .../DefaultClient/DefaultClientComponent.h | 14 +- .../ExternalCapabilitiesBuilderInterface.h | 30 +- .../DefaultClient/src/CMakeLists.txt | 21 +- .../src/ConnectionRetryTrigger.cpp | 2 +- .../DefaultClient/src/DefaultClient.cpp | 192 +- .../src/DefaultClientBuilder.cpp | 271 + .../src/DefaultClientComponent.cpp | 20 +- .../src/EqualizerRuntimeSetup.cpp | 2 +- .../StubApplicationAudioPipelineFactory.cpp | 2 +- .../include/Audio/Data/create_header.bash | 13 + .../Resources/Audio/src/CMakeLists.txt | 3 +- .../SDKComponent/src/SDKComponent.cpp | 2 +- .../src/SystemSoundPlayer.cpp | 2 +- .../include/BlueZ/BlueZBluetoothDevice.h | 2 + .../BlueZ/src/BlueZA2DPSink.cpp | 2 +- .../BlueZ/src/BlueZA2DPSource.cpp | 2 +- .../BlueZ/src/BlueZAVRCPController.cpp | 2 +- .../BlueZ/src/BlueZAVRCPTarget.cpp | 2 +- .../BlueZ/src/BlueZBluetoothDevice.cpp | 9 +- .../BlueZ/src/BlueZBluetoothDeviceManager.cpp | 2 +- .../BlueZ/src/BlueZDeviceManager.cpp | 2 +- .../BlueZ/src/BlueZHFP.cpp | 2 +- .../BlueZ/src/BlueZHID.cpp | 2 +- .../BlueZ/src/BlueZHostController.cpp | 2 +- .../BlueZ/src/BlueZSPP.cpp | 2 +- .../BlueZ/src/DBusConnection.cpp | 2 +- .../BlueZ/src/DBusObjectBase.cpp | 2 +- .../BlueZ/src/DBusPropertiesProxy.cpp | 2 +- .../BlueZ/src/DBusProxy.cpp | 2 +- .../BlueZ/src/GVariantMapReader.cpp | 2 +- .../BlueZ/src/GVariantTupleReader.cpp | 2 +- .../BlueZ/src/MPRISPlayer.cpp | 2 +- .../BlueZ/src/MediaEndpoint.cpp | 2 +- .../BlueZ/src/PairingAgent.cpp | 2 +- .../src/PulseAudioBluetoothInitializer.cpp | 4 +- CHANGELOG.md | 6 + CMakeLists.txt | 11 +- .../CapabilitiesDelegate.h | 46 +- .../DiscoveryEventSender.h | 21 +- .../Utils/DiscoveryUtils.h | 12 + .../src/CapabilitiesDelegate.cpp | 312 +- .../src/DiscoveryEventSender.cpp | 63 +- .../src/PostConnectCapabilitiesPublisher.cpp | 8 +- .../SQLiteCapabilitiesDelegateStorage.cpp | 2 +- .../src/Utils/DiscoveryUtils.cpp | 18 +- .../test/CapabilitiesDelegateTest.cpp | 642 +- CapabilityAgents/AIP/include/AIP/ASRProfile.h | 28 +- .../AIP/include/AIP/AudioInputProcessor.h | 18 +- .../AIP/src/AudioInputProcessor.cpp | 129 +- CapabilityAgents/AIP/src/CMakeLists.txt | 3 +- .../AIP/test/AudioInputProcessorTest.cpp | 102 +- CapabilityAgents/AIP/test/CMakeLists.txt | 2 +- CapabilityAgents/AIP/test/MockObserver.h | 1 + .../Alexa/AlexaEventProcessedNotifier.h | 4 +- .../src/AlexaInterfaceCapabilityAgent.cpp | 4 +- .../Alexa/src/AlexaInterfaceMessageSender.cpp | 11 +- .../src/ApiGatewayCapabilityAgent.cpp | 4 +- CapabilityAgents/CMakeLists.txt | 3 +- .../InteractionModelNotifier.h | 4 +- .../src/InteractionModelCapabilityAgent.cpp | 49 +- .../InteractionModelCapabilityAgentTest.cpp | 79 + .../InteractionModelNotifierInterface.h | 4 +- .../src/ModeControllerAttributeBuilder.cpp | 2 +- .../src/ModeControllerCapabilityAgent.cpp | 8 +- .../src/PlaybackCommand.cpp | 2 +- .../src/PlaybackController.cpp | 9 +- .../PlaybackController/src/PlaybackRouter.cpp | 2 +- .../src/PowerControllerCapabilityAgent.cpp | 8 +- .../src/RangeControllerAttributeBuilder.cpp | 2 +- .../src/RangeControllerCapabilityAgent.cpp | 8 +- ...ftwareComponentReporterCapabilityAgent.cpp | 2 +- .../SpeakerManager/CMakeLists.txt | 8 +- .../SpeakerManager/CMakeLists.txt | 7 + .../SpeakerManager/doc/Namespaces.dox | 23 + .../SpeakerManager/doc/SpeakerManager.dox | 27 + .../include/acsdk/SpeakerManager/Factories.h | 143 + .../SpeakerManagerConfigInterface.h | 85 + .../SpeakerManagerStorageInterface.h | 20 +- .../SpeakerManagerStorageState.h | 18 +- .../private}/ChannelVolumeManager.h | 23 +- .../private}/DefaultChannelVolumeFactory.h | 24 +- .../SpeakerManager/private}/SpeakerManager.h | 117 +- .../private/SpeakerManagerConfig.h | 92 + .../private}/SpeakerManagerConfigHelper.h | 58 +- .../private}/SpeakerManagerConstants.h | 15 +- .../private}/SpeakerManagerMiscStorage.h | 38 +- .../SpeakerManager/src/CMakeLists.txt | 22 + .../src/ChannelVolumeFactory.cpp | 28 + .../src/ChannelVolumeManager.cpp | 39 +- .../src/DefaultChannelVolumeFactory.cpp | 12 +- .../SpeakerManager/src/Factories.cpp | 94 + .../src/SpeakerManager.cpp | 337 +- .../src/SpeakerManagerConfig.cpp | 144 + .../src/SpeakerManagerConfigHelper.cpp | 94 + .../src/SpeakerManagerMiscStorage.cpp | 70 +- .../SpeakerManager/test/CMakeLists.txt | 9 + .../test/ChannelVolumeManagerTest.cpp | 12 +- .../SpeakerManager/test/Namespaces.dox | 24 + .../test/SpeakerManagerConfigHelperTest.cpp | 167 +- .../test/SpeakerManagerConfigTest.cpp | 276 + .../test/SpeakerManagerMiscStorageTest.cpp | 27 +- .../test/SpeakerManagerTest.cpp | 1556 +++- .../test/SpeakerManagerTest.dox | 34 + .../test/MockSpeakerManagerConfig.h | 44 + .../test/MockSpeakerManagerObserver.h | 46 + .../test/MockSpeakerManagerStorage.h | 41 + .../SpeakerManagerComponent/CMakeLists.txt | 4 + .../doc/Namespaces.dox | 25 + .../doc/SpeakerManagerComponent.dox | 27 + .../ChannelVolumeFactoryComponent.h | 54 + .../SpeakerManager/SpeakerManagerComponent.h | 28 +- .../src/CMakeLists.txt | 10 + .../src/ChannelVolumeFactoryComponent.cpp} | 16 +- .../src/SpeakerManagerComponent.cpp | 80 + .../SpeakerManager/src/CMakeLists.txt | 17 - .../src/SpeakerManagerConfigHelper.cpp | 104 - .../SpeakerManager/test/CMakeLists.txt | 7 - .../src/SpeechSynthesizer.cpp | 30 +- .../test/SpeechSynthesizerTest.cpp | 98 +- CapabilityAgents/System/src/LocaleHandler.cpp | 4 +- .../System/src/ReportStateHandler.cpp | 8 +- .../System/src/RevokeAuthorizationHandler.cpp | 2 +- .../System/src/SoftwareInfoSendRequest.cpp | 2 +- .../System/src/SoftwareInfoSender.cpp | 2 +- .../System/src/StateReportGenerator.cpp | 2 +- .../System/src/SystemCapabilityProvider.cpp | 2 +- .../System/src/TimeZoneHandler.cpp | 6 +- .../System/src/UserInactivityMonitor.cpp | 4 +- .../TemplateRuntime/src/CMakeLists.txt | 13 - .../TemplateRuntime/src/TemplateRuntime.cpp | 820 -- .../TemplateRuntime/test/CMakeLists.txt | 8 - .../src/ToggleControllerAttributeBuilder.cpp | 2 +- .../src/ToggleControllerCapabilityAgent.cpp | 8 +- Captions/Component/src/CaptionsComponent.cpp | 2 +- .../Implementation/src/CaptionManager.cpp | 2 +- .../src/LibwebvttParserAdapter.cpp | 2 +- Captions/Interface/src/CaptionLine.cpp | 2 +- CertifiedSender/src/CertifiedSender.cpp | 2 +- CertifiedSender/src/SQLiteMessageStorage.cpp | 2 +- CertifiedSender/test/CertifiedSenderTest.cpp | 4 +- .../test/Common/MockCertifiedSender.cpp | 1 - .../include/ContextManager/ContextManager.h | 11 +- ContextManager/src/ContextManager.cpp | 231 +- ContextManager/test/ContextManagerTest.cpp | 2 +- Diagnostics/src/AudioInjectorMicrophone.cpp | 2 +- Diagnostics/src/DevicePropertyAggregator.cpp | 20 +- Diagnostics/src/DeviceProtocolTracer.cpp | 2 +- Diagnostics/src/DiagnosticsUtils.cpp | 2 +- Diagnostics/src/FileBasedAudioInjector.cpp | 2 +- Endpoints/src/DefaultEndpointBuilder.cpp | 2 +- Endpoints/src/Endpoint.cpp | 2 +- Endpoints/src/EndpointBuilder.cpp | 2 +- Endpoints/src/EndpointRegistrationManager.cpp | 10 +- .../test/EndpointRegistrationManagerTest.cpp | 36 +- Integration/AlexaClientSDKConfig.json | 31 +- .../include/Integration/ACLTestContext.h | 152 - .../include/Integration/AipStateObserver.h | 49 - .../Integration/AuthDelegateTestContext.h | 134 - .../include/Integration/AuthObserver.h | 50 - .../Integration/ClientMessageHandler.h | 69 - .../Integration/ConnectionStatusObserver.h | 76 - Integration/include/Integration/JsonHeader.h | 49 - .../Integration/ObservableMessageRequest.h | 74 - .../include/Integration/SDKTestContext.h | 66 - .../include/Integration/TestAlertObserver.h | 53 - .../Integration/TestDirectiveHandler.h | 180 - .../TestExceptionEncounteredSender.h | 111 - .../include/Integration/TestMediaPlayer.h | 112 - .../include/Integration/TestMessageSender.h | 137 - .../TestSpeechSynthesizerObserver.h | 65 - .../inputs/alexa_recognize_joke_test.wav | Bin 403284 -> 0 bytes .../inputs/alexa_recognize_silence_test.wav | Bin 1690702 -> 0 bytes .../inputs/alexa_recognize_wiki_test.wav | Bin 236638 -> 0 bytes .../inputs/recognize_cancel_timer_test.wav | Bin 245674 -> 0 bytes .../inputs/recognize_flashbriefing_test.wav | Bin 39134 -> 0 bytes Integration/inputs/recognize_joke_test.wav | Bin 73760 -> 0 bytes Integration/inputs/recognize_lions_test.wav | Bin 196592 -> 0 bytes .../inputs/recognize_long_timer_test.wav | Bin 108954 -> 0 bytes Integration/inputs/recognize_silence_test.wav | Bin 16479378 -> 0 bytes .../inputs/recognize_sing_song_test.wav | Bin 38124 -> 0 bytes Integration/inputs/recognize_stop_test.wav | Bin 220118 -> 0 bytes .../inputs/recognize_stop_timer_test.wav | Bin 240400 -> 0 bytes Integration/inputs/recognize_test.wav | Bin 72382 -> 0 bytes Integration/inputs/recognize_timer_test.wav | Bin 311098 -> 0 bytes .../inputs/recognize_very_long_timer_test.wav | Bin 314852 -> 0 bytes .../inputs/recognize_volume_up_test.wav | Bin 32062 -> 0 bytes Integration/inputs/recognize_weather_test.wav | Bin 220832 -> 0 bytes .../inputs/recognize_whats_up_test.wav | Bin 25706 -> 0 bytes Integration/inputs/recognize_wiki_test.wav | Bin 105046 -> 0 bytes Integration/inputs/silence_test.wav | Bin 7936 -> 0 bytes .../inputs/utterance_time_success.opus | Bin 28880 -> 0 bytes Integration/src/ACLTestContext.cpp | 142 - Integration/src/AipStateObserver.cpp | 53 - Integration/src/AuthDelegateTestContext.cpp | 145 - Integration/src/AuthObserver.cpp | 47 - Integration/src/CMakeLists.txt | 31 - Integration/src/ClientMessageHandler.cpp | 46 - Integration/src/ConnectionStatusObserver.cpp | 57 - Integration/src/ObservableMessageRequest.cpp | 90 - Integration/src/SDKTestContext.cpp | 64 - Integration/src/TestAlertObserver.cpp | 46 - Integration/src/TestDirectiveHandler.cpp | 105 - .../src/TestExceptionEncounteredSender.cpp | 105 - Integration/src/TestMediaPlayer.cpp | 151 - Integration/src/TestMessageSender.cpp | 111 - .../src/TestSpeechSynthesizerObserver.cpp | 66 - Integration/test/AlertsIntegrationTest.cpp | 1449 ---- .../test/AlexaAuthorizationDelegateTest.cpp | 114 - .../test/AlexaCommunicationsLibraryTest.cpp | 455 - .../AlexaDirectiveSequencerLibraryTest.cpp | 1134 --- .../AudioInputProcessorIntegrationTest.cpp | 1906 ---- .../test/AudioPlayerIntegrationTest.cpp | 725 -- Integration/test/CMakeLists.txt | 123 - Integration/test/NetworkIntegrationTest.cpp | 250 - .../test/ServerDisconnectIntegrationTest.cpp | 322 - .../test/SpeechSynthesizerIntegrationTest.cpp | 1060 --- .../config/InterruptModelConfiguration.h | 4 +- InterruptModel/src/InterruptModel.cpp | 2 +- .../src/AndroidSLESMediaPlayer.cpp | 2 +- .../src/AndroidSLESMediaQueue.cpp | 6 +- .../src/AndroidSLESSpeaker.cpp | 2 +- .../src/FFmpegAttachmentInputController.cpp | 2 +- .../src/FFmpegDecoder.cpp | 2 +- .../src/FFmpegStreamInputController.cpp | 2 +- .../src/FFmpegUrlInputController.cpp | 2 +- .../src/PlaybackConfiguration.cpp | 2 +- .../test/AndroidSLESMediaPlayerTest.cpp | 2 +- .../test/AndroidSLESMediaQueueTest.cpp | 2 +- .../test/AndroidSLESSpeakerTest.cpp | 2 +- .../test/FFmpegDecoderTest.cpp | 2 +- .../src/AttachmentReaderSource.cpp | 2 +- .../src/BaseStreamSource.cpp | 2 +- .../src/IStreamSource.cpp | 2 +- .../GStreamerMediaPlayer/src/MediaPlayer.cpp | 2 +- .../GStreamerMediaPlayer/src/Normalizer.cpp | 2 +- .../src/OffsetManager.cpp | 2 +- .../test/MediaPlayerTest.cpp | 2 +- Metrics/MetricRecorder/src/MetricRecorder.cpp | 4 +- .../SampleMetricSink/src/SampleMetricSink.cpp | 2 +- .../include/Metrics/BaseUplCalculator.h | 2 + .../UplCalculator/src/BaseUplCalculator.cpp | 8 +- .../UplCalculator/src/MediaUplCalculator.cpp | 2 +- .../UplCalculator/src/TtsUplCalculator.cpp | 2 +- Metrics/UplCalculator/src/UplMetricSink.cpp | 2 +- NOTICE.txt | 322 +- PlaylistParser/src/ContentDecrypter.cpp | 6 +- PlaylistParser/src/FFMpegInputBuffer.cpp | 2 +- PlaylistParser/src/Id3TagsRemover.cpp | 2 +- .../src/IterativePlaylistParser.cpp | 14 +- PlaylistParser/src/M3UParser.cpp | 3 +- PlaylistParser/src/PlaylistParser.cpp | 8 +- PlaylistParser/src/PlaylistUtils.cpp | 2 +- .../src/UrlContentToAttachmentConverter.cpp | 12 +- README.md | 20 +- SECURITY.md | 2 +- .../CBLAuthDelegateStorageInterface.h | 93 - .../CBLAuthRequesterInterface.h | 53 - .../CBLAuthDelegate/src/CMakeLists.txt | 35 - SampleApp/CMakeLists.txt | 22 - .../SampleApp/InputControllerHandler.h | 54 - .../include/SampleApp/LocaleAssetsManager.h | 157 - .../SampleApp/PlatformSpecificValues.h | 46 - SampleApp/src/CMakeLists.txt | 172 - SampleApp/src/InputControllerHandler.cpp | 53 - SampleApplications/CMakeLists.txt | 25 + .../include/CBLAuthDelegate/CBLAuthDelegate.h | 8 +- .../SQLiteCBLAuthDelegateStorage.h | 14 +- .../CBLAuthDelegate/src/CBLAuthDelegate.cpp | 2 +- .../src/SQLiteCBLAuthDelegateStorage.cpp | 6 +- SampleApplications/Common/CMakeLists.txt | 11 + .../Common/Console}/CMakeLists.txt | 2 +- .../acsdk/Sample/Console}/ConsolePrinter.h | 12 +- .../Common/Console/src/CMakeLists.txt | 14 + .../Common/Console}/src/ConsolePrinter.cpp | 18 +- .../Common/Endpoint/CMakeLists.txt | 4 + .../DefaultEndpointModeControllerHandler.h | 12 +- .../DefaultEndpointRangeControllerHandler.h | 12 +- .../DefaultEndpointToggleControllerHandler.h | 12 +- .../EndpointAlexaChannelControllerHandler.h | 89 + .../EndpointAlexaKeypadControllerHandler.h | 71 + .../Endpoint/EndpointAlexaLauncherHandler.h | 122 + .../EndpointAlexaPlaybackControllerHandler.h | 123 + .../EndpointAlexaRecordControllerHandler.h | 71 + .../EndpointAlexaRemoteVideoPlayerHandler.h | 83 + .../EndpointAlexaSeekControllerHandler.h | 71 + .../EndpointAlexaVideoRecorderHandler.h | 77 + .../Endpoint/EndpointCapabilitiesBuilder.h | 144 + .../Sample/Endpoint/EndpointFocusAdapter.h | 155 + .../Endpoint/EndpointInputControllerHandler.h | 82 + ...putControllerEndpointCapabilitiesBuilder.h | 56 + .../Common/Endpoint/src/CMakeLists.txt | 121 + .../DefaultEndpointModeControllerHandler.cpp | 13 +- .../DefaultEndpointRangeControllerHandler.cpp | 13 +- ...DefaultEndpointToggleControllerHandler.cpp | 13 +- .../EndpointAlexaChannelControllerHandler.cpp | 180 + .../EndpointAlexaKeypadControllerHandler.cpp | 82 + .../src/EndpointAlexaLauncherHandler.cpp | 160 + ...EndpointAlexaPlaybackControllerHandler.cpp | 194 + .../EndpointAlexaRecordControllerHandler.cpp | 84 + .../EndpointAlexaRemoteVideoPlayerHandler.cpp | 122 + .../EndpointAlexaSeekControllerHandler.cpp | 98 + .../src/EndpointAlexaVideoRecorderHandler.cpp | 98 + .../src/EndpointCapabilitiesBuilder.cpp | 331 + .../Endpoint/src/EndpointFocusAdapter.cpp | 138 + .../src/EndpointInputControllerHandler.cpp | 87 + ...tControllerEndpointCapabilitiesBuilder.cpp | 94 + .../Common/InteractionManager/CMakeLists.txt | 4 + .../InteractionManager}/InteractionManager.h | 91 +- .../InteractionManager/KeywordObserver.h | 82 + .../Sample/InteractionManager}/UIManager.h | 34 +- .../InteractionManager/src/CMakeLists.txt | 32 + .../src/InteractionManager.cpp | 271 +- .../src/KeywordObserver.cpp | 10 +- .../InteractionManager}/src/UIManager.cpp | 195 +- .../Common/LibSampleApp/CMakeLists.txt | 4 + .../include/SampleApp/CaptionPresenter.h | 6 +- .../include/SampleApp/ConsoleReader.h | 6 +- .../SampleApp/ExternalCapabilitiesBuilder.h | 25 +- .../include/SampleApp/GuiRenderer.h | 38 +- .../include/SampleApp/KeywordObserver.h | 6 +- .../include/SampleApp/LocaleAssetsManager.h | 299 + .../SampleApp/RevokeAuthorizationObserver.h | 6 +- .../include/SampleApp/SDKDiagnostics.h | 6 +- .../include/SampleApp/SampleApplication.h | 44 +- .../SampleApp/SampleApplicationComponent.h | 32 +- .../SampleApp/SampleApplicationReturnCodes.h | 6 +- .../SampleApp/SampleEqualizerModeController.h | 6 +- .../include/SampleApp/UserInputManager.h | 22 +- .../Common/LibSampleApp/src/CMakeLists.txt | 139 + .../LibSampleApp}/src/CaptionPresenter.cpp | 5 +- .../LibSampleApp}/src/ConsoleReader.cpp | 6 +- .../src/ExternalCapabilitiesBuilder.cpp | 54 +- .../Common/LibSampleApp}/src/GuiRenderer.cpp | 22 +- .../LibSampleApp}/src/LocaleAssetsManager.cpp | 177 +- .../src/RevokeAuthorizationObserver.cpp | 0 .../LibSampleApp}/src/SDKDiagnostics.cpp | 2 +- .../LibSampleApp}/src/SampleApplication.cpp | 316 +- .../src/SampleApplicationComponent.cpp | 31 +- .../src/SampleEqualizerModeController.cpp | 5 +- .../LibSampleApp}/src/UserInputManager.cpp | 45 +- .../Common/PeripheralEndpoint/CMakeLists.txt | 4 + .../PeripheralEndpointModeControllerHandler.h | 12 +- ...PeripheralEndpointPowerControllerHandler.h | 12 +- ...PeripheralEndpointRangeControllerHandler.h | 12 +- ...eripheralEndpointToggleControllerHandler.h | 12 +- .../PeripheralEndpoint/src/CMakeLists.txt | 34 + .../src/NullPeripheralEndpoint.cpp | 14 + ...eripheralEndpointModeControllerHandler.cpp | 14 +- ...ripheralEndpointPowerControllerHandler.cpp | 14 +- ...ripheralEndpointRangeControllerHandler.cpp | 14 +- ...ipheralEndpointToggleControllerHandler.cpp | 15 +- .../PortAudioMicrophoneAdapter/CMakeLists.txt | 13 + .../Microphone/PortAudioMicrophoneAdapter.h | 35 + .../Microphone}/PortAudioMicrophoneWrapper.h | 12 +- .../src/PortAudioMicrophoneAdapter.cpp | 32 + .../src/PortAudioMicrophoneWrapper.cpp | 200 + .../CMakeLists.txt | 4 + .../TemplateRuntimePresentationAdapter.h | 248 + ...timePresentationAdapterObserverInterface.h | 75 + .../src/CMakeLists.txt | 18 + .../TemplateRuntimePresentationAdapter.cpp | 281 + .../ConsoleSampleApplication/CMakeLists.txt | 4 + .../src/CMakeLists.txt | 4 + .../ConsoleSampleApplication}/src/main.cpp | 1 + .../IPCServerSampleApplication/.clang-format | 102 + .../IPCServerSampleApplication/.gitignore | 7 + .../IPCServerSampleApplication/CMakeLists.txt | 26 + .../Communication/CMakeLists.txt | 7 + .../Communication/MessageListenerInterface.h | 45 + .../Communication/MessagingInterface.h | 54 + .../Communication/MessagingServerInterface.h | 66 + .../MessagingServerObserverInterface.h | 45 + .../include/Communication/WebSocketConfig.h | 138 + .../Communication/WebSocketSDKLogger.h | 124 + .../include/Communication/WebSocketServer.h | 176 + .../Communication/src/CMakeLists.txt | 43 + .../Communication/src/WebSocketSDKLogger.cpp | 76 + .../Communication/src/WebSocketServer.cpp | 274 + .../LibIPCServerSampleApp/CMakeLists.txt | 9 + .../AlexaPresentation/APLDocumentSession.h | 257 + .../APLDocumentSessionManager.h | 94 + .../APLDocumentSessionManagerInterface.h | 102 + .../AlexaPresentation/APLPayloadParser.h | 76 + .../APLRuntimeInterfaceImpl.h | 98 + .../APLRuntimePresentationAdapter.h | 351 + .../AlexaPresentation/APLViewhostInterface.h | 157 + .../APLViewhostObserverInterface.h | 160 + .../AlexaPresentation/AplClientBridge.h | 459 + .../AlexaPresentation/IPCAPLAgent.h | 102 + .../CachingDownloadManager.h | 189 + .../IPCServerSampleApp/ConfigValidator.h | 66 + .../IPCServerSampleApp/ConnectionObserver.h | 90 + .../IPCServerSampleApp/ConsolePrinter.h | 97 + .../IPCServerSampleApp/DownloadMonitor.h | 54 + .../LiveView/AplLiveViewExtension.h | 123 + .../AplLiveViewExtensionObserverInterface.h | 57 + .../ExternalCapabilitiesBuilder.h | 113 + .../IPCServerSampleApp/GUI/CaptureState.h | 76 + .../GUI/GUIActivityEventNotifier.h | 63 + .../GUI/GUIActivityEventNotifierInterface.h | 46 + .../IPCServerSampleApp/GUI/GUIClient.h | 569 ++ .../GUI/GUIClientInterface.h | 190 + .../IPCServerSampleApp/GUI/GUIManager.h | 534 ++ .../GUI/GUIServerInterface.h | 305 + .../IPCServerSampleApp/GUI/NavigationEvent.h | 74 + ...TemplateRuntimePresentationAdapterBridge.h | 88 + .../include/IPCServerSampleApp/GUILogBridge.h | 89 + .../IPC/Components/APLClientHandler.h | 100 + .../IPC/Components/AlexaCaptionsHandler.h | 91 + .../IPC/Components/AudioFocusManagerHandler.h | 101 + .../IPC/Components/DoNotDisturbHandler.h | 91 + .../Components/InteractionManagerHandler.h | 84 + .../IPC/Components/LiveViewCameraHandler.h | 104 + .../IPC/Components/LoggerHandler.h | 84 + .../IPC/Components/SessionSetupHandler.h | 98 + .../IPC/Components/SystemHandler.h | 116 + .../IPC/Components/TemplateRuntimeHandler.h | 96 + .../IPC/Components/WindowManagerHandler.h | 104 + .../APLClientHandlerInterface.h | 96 + .../AudioFocusManagerHandlerInterface.h | 75 + .../DoNotDisturbHandlerInterface.h | 59 + .../InteractionManagerHandlerInterface.h | 73 + .../LiveViewCameraHandlerInterface.h | 70 + .../LoggerHandlerInterface.h | 55 + .../SessionSetupHandlerInterface.h | 76 + .../SystemHandlerInterface.h | 74 + .../TemplateRuntimeHandlerInterface.h | 56 + .../WindowManagerHandlerInterface.h | 130 + .../IPC/IPCDispatcherInterface.h | 45 + .../IPCServerSampleApp/IPC/IPCHandlerBase.h | 93 + .../IPC/IPCHandlerRegistrationInterface.h | 64 + .../IPCServerSampleApp/IPC/IPCRouter.h | 145 + .../IPC/IPCVersionManager.h | 91 + .../IPC/Namespaces/APLClientNamespace.h | 85 + .../IPC/Namespaces/AlexaCaptionsNamespace.h | 82 + .../Namespaces/AudioFocusManagerNamespace.h | 95 + .../IPC/Namespaces/CommunicationsNamespace.h | 103 + .../IPC/Namespaces/DoNotDisturbNamespace.h | 61 + .../Namespaces/InteractionManagerNamespace.h | 41 + .../IPC/Namespaces/LiveViewCameraNamespace.h | 109 + .../IPC/Namespaces/LoggerNamespace.h | 42 + .../IPC/Namespaces/SessionSetupNamespace.h | 88 + .../IPC/Namespaces/SystemNamespace.h | 143 + .../IPC/Namespaces/TemplateRuntimeNamespace.h | 141 + .../IPC/Namespaces/WindowManagerNamespace.h | 135 + .../IPCServerSampleApp/KeywordObserver.h | 82 + .../LiveViewControllerPresentationAdapter.h | 253 + .../IPCServerSampleApp/LocaleAssetsManager.h | 297 + .../IPCServerSampleApp/Messages/Message.h | 264 + .../Messages/MessageInterface.h | 95 + .../PortAudioMicrophoneWrapper.h | 132 + .../RenderCaptionsInterface.h | 45 + .../RevokeAuthorizationObserver.h | 53 + .../IPCServerSampleApp/SampleApplication.h | 277 + .../SampleApplicationComponent.h | 86 + .../SampleApplicationReturnCodes.h | 38 + .../SampleEqualizerModeController.h | 59 + .../SmartScreenCaptionPresenter.h | 58 + .../SmartScreenCaptionStateManager.h | 59 + .../IPCServerSampleApp/TelemetrySink.h | 62 + .../IPCServerSampleApp/TimezoneHelper.h | 74 + .../AlexaPresentation/APLDocumentSession.cpp | 710 ++ .../APLDocumentSessionManager.cpp | 150 + .../AlexaPresentation/APLPayloadParser.cpp | 187 + .../APLRuntimeInterfaceImpl.cpp | 118 + .../APLRuntimePresentationAdapter.cpp | 597 ++ .../src/AlexaPresentation/AplClientBridge.cpp | 939 ++ .../src/AlexaPresentation/IPCAPLAgent.cpp | 102 + .../LibIPCServerSampleApp/src/CMakeLists.txt | 202 + .../src/CachingDownloadManager.cpp | 323 + .../src/ConfigValidator.cpp | 157 + .../src/ConnectionObserver.cpp | 59 + .../src/ConsolePrinter.cpp | 127 + .../src/DownloadMonitor.cpp | 48 + .../LiveView/AplLiveViewExtension.cpp | 246 + .../src/ExternalCapabilitiesBuilder.cpp | 197 + .../src/GUI/GUIActivityEventNotifier.cpp | 48 + .../src/GUI/GUIClient.cpp | 1055 +++ .../src/GUI/GUIManager.cpp | 1315 +++ ...mplateRuntimePresentationAdapterBridge.cpp | 101 + .../src/GUILogBridge.cpp | 65 + .../src/IPC/Components/APLClientHandler.cpp | 172 + .../IPC/Components/AlexaCaptionsHandler.cpp | 136 + .../Components/AudioFocusManagerHandler.cpp | 141 + .../IPC/Components/DoNotDisturbHandler.cpp | 122 + .../Components/InteractionManagerHandler.cpp | 124 + .../IPC/Components/LiveViewCameraHandler.cpp | 149 + .../src/IPC/Components/LoggerHandler.cpp | 105 + .../IPC/Components/SessionSetupHandler.cpp | 146 + .../src/IPC/Components/SystemHandler.cpp | 169 + .../IPC/Components/TemplateRuntimeHandler.cpp | 146 + .../IPC/Components/WindowManagerHandler.cpp | 173 + .../src/IPC/IPCHandlerBase.cpp | 98 + .../src/IPC/IPCRouter.cpp | 197 + .../src/IPC/IPCVersionManager.cpp | 123 + .../src/KeywordObserver.cpp | 131 + .../LiveViewControllerPresentationAdapter.cpp | 573 ++ .../src/LocaleAssetsManager.cpp | 482 ++ .../src/PortAudioMicrophoneWrapper.cpp | 17 +- .../src/RevokeAuthorizationObserver.cpp | 37 + .../src/SampleApplication.cpp | 1615 ++++ .../src/SampleApplicationComponent.cpp | 117 + .../src/SampleEqualizerModeController.cpp | 40 + .../src/SmartScreenCaptionPresenter.cpp | 135 + .../src/SmartScreenCaptionStateManager.cpp | 80 + .../src/TelemetrySink.cpp | 70 + .../src/TimezoneHelper.cpp | 88 + .../LibIPCServerSampleApp/test/CMakeLists.txt | 6 + .../test/IPCVersionManagerTest.cpp | 167 + .../test/SmartScreenCaptionPresenterTest.cpp | 90 + .../SmartScreenCaptionStateManagerTest.cpp | 203 + .../ThirdParty/.clang-format | 5 + .../googletest-release-1.8.0/.gitignore | 2 + .../googletest-release-1.8.0/.travis.yml | 46 + .../googletest-release-1.8.0/CMakeLists.txt | 16 + .../googletest-release-1.8.0/README.md | 142 + .../googletest-release-1.8.0/appveyor.yml | 71 + .../googlemock/CHANGES | 126 + .../googlemock/CMakeLists.txt | 202 + .../googlemock/CONTRIBUTORS | 40 + .../googlemock/LICENSE | 28 + .../googlemock/Makefile.am | 224 + .../googlemock/README.md | 333 + .../googlemock/build-aux/.keep | 0 .../googlemock/configure.ac | 146 + .../googlemock/docs/CheatSheet.md | 562 ++ .../googlemock/docs/CookBook.md | 3675 ++++++++ .../googlemock/docs/DesignDoc.md | 280 + .../googlemock/docs/DevGuide.md | 132 + .../googlemock/docs/Documentation.md | 12 + .../googlemock/docs/ForDummies.md | 439 + .../docs/FrequentlyAskedQuestions.md | 628 ++ .../googlemock/docs/KnownIssues.md | 19 + .../googlemock/docs/v1_5/CheatSheet.md | 525 ++ .../googlemock/docs/v1_5/CookBook.md | 3250 +++++++ .../googlemock/docs/v1_5/Documentation.md | 11 + .../googlemock/docs/v1_5/ForDummies.md | 439 + .../docs/v1_5/FrequentlyAskedQuestions.md | 624 ++ .../googlemock/docs/v1_6/CheatSheet.md | 534 ++ .../googlemock/docs/v1_6/CookBook.md | 3342 +++++++ .../googlemock/docs/v1_6/Documentation.md | 12 + .../googlemock/docs/v1_6/ForDummies.md | 439 + .../docs/v1_6/FrequentlyAskedQuestions.md | 628 ++ .../googlemock/docs/v1_7/CheatSheet.md | 556 ++ .../googlemock/docs/v1_7/CookBook.md | 3432 ++++++++ .../googlemock/docs/v1_7/Documentation.md | 12 + .../googlemock/docs/v1_7/ForDummies.md | 439 + .../docs/v1_7/FrequentlyAskedQuestions.md | 628 ++ .../googlemock/include/gmock/gmock-actions.h | 1205 +++ .../include/gmock/gmock-cardinalities.h | 147 + .../include/gmock/gmock-generated-actions.h | 2377 +++++ .../gmock/gmock-generated-actions.h.pump | 794 ++ .../gmock/gmock-generated-function-mockers.h | 1095 +++ .../gmock-generated-function-mockers.h.pump | 291 + .../include/gmock/gmock-generated-matchers.h | 2179 +++++ .../gmock/gmock-generated-matchers.h.pump | 672 ++ .../gmock/gmock-generated-nice-strict.h | 397 + .../gmock/gmock-generated-nice-strict.h.pump | 161 + .../googlemock/include/gmock/gmock-matchers.h | 4399 ++++++++++ .../include/gmock/gmock-more-actions.h | 246 + .../include/gmock/gmock-more-matchers.h | 58 + .../include/gmock/gmock-spec-builders.h | 1847 ++++ .../googlemock/include/gmock/gmock.h | 94 + .../internal/custom/gmock-generated-actions.h | 8 + .../custom/gmock-generated-actions.h.pump | 10 + .../gmock/internal/custom/gmock-matchers.h | 39 + .../gmock/internal/custom/gmock-port.h | 46 + .../internal/gmock-generated-internal-utils.h | 279 + .../gmock-generated-internal-utils.h.pump | 136 + .../gmock/internal/gmock-internal-utils.h | 511 ++ .../include/gmock/internal/gmock-port.h | 91 + .../googlemock/make/Makefile | 101 + .../googlemock/msvc/2005/gmock.sln | 32 + .../googlemock/msvc/2005/gmock.vcproj | 191 + .../googlemock/msvc/2005/gmock_config.vsprops | 15 + .../googlemock/msvc/2005/gmock_main.vcproj | 187 + .../googlemock/msvc/2005/gmock_test.vcproj | 201 + .../googlemock/msvc/2010/gmock.sln | 32 + .../googlemock/msvc/2010/gmock.vcxproj | 82 + .../googlemock/msvc/2010/gmock_config.props | 19 + .../googlemock/msvc/2010/gmock_main.vcxproj | 88 + .../googlemock/msvc/2010/gmock_test.vcxproj | 101 + .../googlemock/msvc/2015/gmock.sln | 32 + .../googlemock/msvc/2015/gmock.vcxproj | 84 + .../googlemock/msvc/2015/gmock_config.props | 19 + .../googlemock/msvc/2015/gmock_main.vcxproj | 90 + .../googlemock/msvc/2015/gmock_test.vcxproj | 103 + .../googlemock/scripts/fuse_gmock_files.py | 240 + .../googlemock/scripts/generator/LICENSE | 203 + .../googlemock/scripts/generator/README | 35 + .../scripts/generator/README.cppclean | 115 + .../scripts/generator/cpp/__init__.py | 0 .../googlemock/scripts/generator/cpp/ast.py | 1733 ++++ .../scripts/generator/cpp/gmock_class.py | 227 + .../scripts/generator/cpp/gmock_class_test.py | 448 + .../scripts/generator/cpp/keywords.py | 59 + .../scripts/generator/cpp/tokenize.py | 287 + .../googlemock/scripts/generator/cpp/utils.py | 41 + .../googlemock/scripts/generator/gmock_gen.py | 31 + .../googlemock/scripts/gmock-config.in | 303 + .../googlemock/scripts/gmock_doctor.py | 640 ++ .../googlemock/scripts/upload.py | 1387 +++ .../googlemock/scripts/upload_gmock.py | 78 + .../googlemock/src/gmock-all.cc | 47 + .../googlemock/src/gmock-cardinalities.cc | 156 + .../googlemock/src/gmock-internal-utils.cc | 174 + .../googlemock/src/gmock-matchers.cc | 498 ++ .../googlemock/src/gmock-spec-builders.cc | 823 ++ .../googlemock/src/gmock.cc | 183 + .../googlemock/src/gmock_main.cc | 54 + .../googlemock/test/gmock-actions_test.cc | 1411 +++ .../test/gmock-cardinalities_test.cc | 428 + .../test/gmock-generated-actions_test.cc | 1228 +++ .../gmock-generated-function-mockers_test.cc | 622 ++ .../gmock-generated-internal-utils_test.cc | 127 + .../test/gmock-generated-matchers_test.cc | 1286 +++ .../test/gmock-internal-utils_test.cc | 699 ++ .../googlemock/test/gmock-matchers_test.cc | 5652 ++++++++++++ .../test/gmock-more-actions_test.cc | 708 ++ .../googlemock/test/gmock-nice-strict_test.cc | 424 + .../googlemock/test/gmock-port_test.cc | 43 + .../test/gmock-spec-builders_test.cc | 2644 ++++++ .../googlemock/test/gmock_all_test.cc | 51 + .../googlemock/test/gmock_ex_test.cc | 81 + .../googlemock/test/gmock_leak_test.py | 108 + .../googlemock/test/gmock_leak_test_.cc | 100 + .../googlemock/test/gmock_link2_test.cc | 40 + .../googlemock/test/gmock_link_test.cc | 40 + .../googlemock/test/gmock_link_test.h | 669 ++ .../googlemock/test/gmock_output_test.py | 180 + .../googlemock/test/gmock_output_test_.cc | 291 + .../test/gmock_output_test_golden.txt | 310 + .../googlemock/test/gmock_stress_test.cc | 322 + .../googlemock/test/gmock_test.cc | 220 + .../googlemock/test/gmock_test_utils.py | 112 + .../googletest/.gitignore | 2 + .../googletest/CHANGES | 157 + .../googletest/CMakeLists.txt | 286 + .../googletest/CONTRIBUTORS | 37 + .../googletest/LICENSE | 28 + .../googletest/Makefile.am | 310 + .../googletest/README.md | 280 + .../googletest/build-aux/.keep | 0 .../googletest/cmake/internal_utils.cmake | 254 + .../googletest/codegear/gtest.cbproj | 138 + .../googletest/codegear/gtest.groupproj | 54 + .../googletest/codegear/gtest_all.cc | 38 + .../googletest/codegear/gtest_link.cc | 40 + .../googletest/codegear/gtest_main.cbproj | 82 + .../googletest/codegear/gtest_unittest.cbproj | 88 + .../googletest/configure.ac | 68 + .../googletest/docs/AdvancedGuide.md | 2182 +++++ .../googletest/docs/DevGuide.md | 126 + .../googletest/docs/Documentation.md | 14 + .../googletest/docs/FAQ.md | 1087 +++ .../googletest/docs/Primer.md | 502 ++ .../googletest/docs/PumpManual.md | 177 + .../googletest/docs/Samples.md | 14 + .../googletest/docs/V1_5_AdvancedGuide.md | 2096 +++++ .../googletest/docs/V1_5_Documentation.md | 12 + .../googletest/docs/V1_5_FAQ.md | 886 ++ .../googletest/docs/V1_5_Primer.md | 497 ++ .../googletest/docs/V1_5_PumpManual.md | 177 + .../googletest/docs/V1_5_XcodeGuide.md | 93 + .../googletest/docs/V1_6_AdvancedGuide.md | 2178 +++++ .../googletest/docs/V1_6_Documentation.md | 14 + .../googletest/docs/V1_6_FAQ.md | 1038 +++ .../googletest/docs/V1_6_Primer.md | 501 ++ .../googletest/docs/V1_6_PumpManual.md | 177 + .../googletest/docs/V1_6_Samples.md | 14 + .../googletest/docs/V1_6_XcodeGuide.md | 93 + .../googletest/docs/V1_7_AdvancedGuide.md | 2181 +++++ .../googletest/docs/V1_7_Documentation.md | 14 + .../googletest/docs/V1_7_FAQ.md | 1082 +++ .../googletest/docs/V1_7_Primer.md | 501 ++ .../googletest/docs/V1_7_PumpManual.md | 177 + .../googletest/docs/V1_7_Samples.md | 14 + .../googletest/docs/V1_7_XcodeGuide.md | 93 + .../googletest/docs/XcodeGuide.md | 93 + .../include/gtest/gtest-death-test.h | 294 + .../googletest/include/gtest/gtest-message.h | 250 + .../include/gtest/gtest-param-test.h | 1444 +++ .../include/gtest/gtest-param-test.h.pump | 510 ++ .../googletest/include/gtest/gtest-printers.h | 993 +++ .../googletest/include/gtest/gtest-spi.h | 232 + .../include/gtest/gtest-test-part.h | 179 + .../include/gtest/gtest-typed-test.h | 263 + .../googletest/include/gtest/gtest.h | 2236 +++++ .../include/gtest/gtest_pred_impl.h | 358 + .../googletest/include/gtest/gtest_prod.h | 58 + .../gtest/internal/custom/gtest-port.h | 69 + .../gtest/internal/custom/gtest-printers.h | 42 + .../include/gtest/internal/custom/gtest.h | 41 + .../internal/gtest-death-test-internal.h | 319 + .../include/gtest/internal/gtest-filepath.h | 206 + .../include/gtest/internal/gtest-internal.h | 1238 +++ .../include/gtest/internal/gtest-linked_ptr.h | 243 + .../internal/gtest-param-util-generated.h | 5146 +++++++++++ .../gtest-param-util-generated.h.pump | 286 + .../include/gtest/internal/gtest-param-util.h | 731 ++ .../include/gtest/internal/gtest-port-arch.h | 93 + .../include/gtest/internal/gtest-port.h | 2554 ++++++ .../include/gtest/internal/gtest-string.h | 167 + .../include/gtest/internal/gtest-tuple.h | 1020 +++ .../include/gtest/internal/gtest-tuple.h.pump | 347 + .../include/gtest/internal/gtest-type-util.h | 3331 +++++++ .../gtest/internal/gtest-type-util.h.pump | 297 + .../googletest/m4/acx_pthread.m4 | 363 + .../googletest/m4/gtest.m4 | 74 + .../googletest/make/Makefile | 82 + .../googletest/msvc/gtest-md.sln | 45 + .../googletest/msvc/gtest-md.vcproj | 126 + .../googletest/msvc/gtest.sln | 45 + .../googletest/msvc/gtest.vcproj | 126 + .../googletest/msvc/gtest_main-md.vcproj | 129 + .../googletest/msvc/gtest_main.vcproj | 129 + .../googletest/msvc/gtest_prod_test-md.vcproj | 164 + .../googletest/msvc/gtest_prod_test.vcproj | 164 + .../googletest/msvc/gtest_unittest-md.vcproj | 147 + .../googletest/msvc/gtest_unittest.vcproj | 147 + .../googletest/samples/prime_tables.h | 123 + .../googletest/samples/sample1.cc | 68 + .../googletest/samples/sample1.h | 43 + .../googletest/samples/sample10_unittest.cc | 144 + .../googletest/samples/sample1_unittest.cc | 153 + .../googletest/samples/sample2.cc | 56 + .../googletest/samples/sample2.h | 85 + .../googletest/samples/sample2_unittest.cc | 109 + .../googletest/samples/sample3-inl.h | 172 + .../googletest/samples/sample3_unittest.cc | 151 + .../googletest/samples/sample4.cc | 46 + .../googletest/samples/sample4.h | 53 + .../googletest/samples/sample4_unittest.cc | 45 + .../googletest/samples/sample5_unittest.cc | 199 + .../googletest/samples/sample6_unittest.cc | 224 + .../googletest/samples/sample7_unittest.cc | 130 + .../googletest/samples/sample8_unittest.cc | 173 + .../googletest/samples/sample9_unittest.cc | 160 + .../googletest/scripts/common.py | 83 + .../googletest/scripts/fuse_gtest_files.py | 253 + .../googletest/scripts/gen_gtest_pred_impl.py | 730 ++ .../googletest/scripts/gtest-config.in | 274 + .../googletest/scripts/pump.py | 855 ++ .../googletest/scripts/release_docs.py | 158 + .../googletest/scripts/test/Makefile | 59 + .../googletest/scripts/upload.py | 1387 +++ .../googletest/scripts/upload_gtest.py | 78 + .../googletest/src/gtest-all.cc | 48 + .../googletest/src/gtest-death-test.cc | 1342 +++ .../googletest/src/gtest-filepath.cc | 387 + .../googletest/src/gtest-internal-inl.h | 1183 +++ .../googletest/src/gtest-port.cc | 1259 +++ .../googletest/src/gtest-printers.cc | 373 + .../googletest/src/gtest-test-part.cc | 110 + .../googletest/src/gtest-typed-test.cc | 118 + .../googletest/src/gtest.cc | 5388 ++++++++++++ .../googletest/src/gtest_main.cc | 38 + .../test/gtest-death-test_ex_test.cc | 93 + .../googletest/test/gtest-death-test_test.cc | 1427 +++ .../googletest/test/gtest-filepath_test.cc | 662 ++ .../googletest/test/gtest-linked_ptr_test.cc | 154 + .../googletest/test/gtest-listener_test.cc | 311 + .../googletest/test/gtest-message_test.cc | 159 + .../googletest/test/gtest-options_test.cc | 215 + .../googletest/test/gtest-param-test2_test.cc | 65 + .../googletest/test/gtest-param-test_test.cc | 1055 +++ .../googletest/test/gtest-param-test_test.h | 57 + .../googletest/test/gtest-port_test.cc | 1304 +++ .../googletest/test/gtest-printers_test.cc | 1635 ++++ .../googletest/test/gtest-test-part_test.cc | 208 + .../googletest/test/gtest-tuple_test.cc | 320 + .../googletest/test/gtest-typed-test2_test.cc | 45 + .../googletest/test/gtest-typed-test_test.cc | 380 + .../googletest/test/gtest-typed-test_test.h | 66 + .../test/gtest-unittest-api_test.cc | 341 + .../googletest/test/gtest_all_test.cc | 47 + .../test/gtest_break_on_failure_unittest.py | 212 + .../test/gtest_break_on_failure_unittest_.cc | 88 + .../test/gtest_catch_exceptions_test.py | 237 + .../test/gtest_catch_exceptions_test_.cc | 311 + .../googletest/test/gtest_color_test.py | 130 + .../googletest/test/gtest_color_test_.cc | 71 + .../googletest/test/gtest_env_var_test.py | 117 + .../googletest/test/gtest_env_var_test_.cc | 126 + .../googletest/test/gtest_environment_test.cc | 192 + .../googletest/test/gtest_filter_unittest.py | 636 ++ .../googletest/test/gtest_filter_unittest_.cc | 140 + .../googletest/test/gtest_help_test.py | 172 + .../googletest/test/gtest_help_test_.cc | 46 + .../test/gtest_list_tests_unittest.py | 207 + .../test/gtest_list_tests_unittest_.cc | 157 + .../googletest/test/gtest_main_unittest.cc | 45 + .../googletest/test/gtest_no_test_unittest.cc | 56 + .../googletest/test/gtest_output_test.py | 340 + .../googletest/test/gtest_output_test_.cc | 1062 +++ .../test/gtest_output_test_golden_lin.txt | 743 ++ .../test/gtest_pred_impl_unittest.cc | 2427 ++++++ .../test/gtest_premature_exit_test.cc | 127 + .../googletest/test/gtest_prod_test.cc | 57 + .../googletest/test/gtest_repeat_test.cc | 253 + .../googletest/test/gtest_shuffle_test.py | 325 + .../googletest/test/gtest_shuffle_test_.cc | 103 + .../googletest/test/gtest_sole_header_test.cc | 57 + .../googletest/test/gtest_stress_test.cc | 256 + .../googletest/test/gtest_test_utils.py | 320 + .../test/gtest_throw_on_failure_ex_test.cc | 92 + .../test/gtest_throw_on_failure_test.py | 171 + .../test/gtest_throw_on_failure_test_.cc | 72 + .../test/gtest_uninitialized_test.py | 70 + .../test/gtest_uninitialized_test_.cc | 43 + .../googletest/test/gtest_unittest.cc | 7706 +++++++++++++++++ .../test/gtest_xml_outfile1_test_.cc | 49 + .../test/gtest_xml_outfile2_test_.cc | 49 + .../test/gtest_xml_outfiles_test.py | 132 + .../test/gtest_xml_output_unittest.py | 308 + .../test/gtest_xml_output_unittest_.cc | 181 + .../googletest/test/gtest_xml_test_utils.py | 194 + .../googletest/test/production.cc | 36 + .../googletest/test/production.h | 55 + .../xcode/Config/DebugProject.xcconfig | 30 + .../xcode/Config/FrameworkTarget.xcconfig | 17 + .../googletest/xcode/Config/General.xcconfig | 41 + .../xcode/Config/ReleaseProject.xcconfig | 32 + .../xcode/Config/StaticLibraryTarget.xcconfig | 18 + .../xcode/Config/TestTarget.xcconfig | 8 + .../googletest/xcode/Resources/Info.plist | 30 + .../xcode/Samples/FrameworkSample/Info.plist | 28 + .../WidgetFramework.xcodeproj/project.pbxproj | 457 + .../xcode/Samples/FrameworkSample/runtests.sh | 62 + .../xcode/Samples/FrameworkSample/widget.cc | 63 + .../xcode/Samples/FrameworkSample/widget.h | 59 + .../Samples/FrameworkSample/widget_test.cc | 68 + .../googletest/xcode/Scripts/runtests.sh | 65 + .../xcode/Scripts/versiongenerate.py | 100 + .../xcode/gtest.xcodeproj/project.pbxproj | 1135 +++ .../googletest-release-1.8.0/travis.sh | 15 + .../ThirdParty/googletest.cmake | 9 + .../config/IPCServerSampleAppConfig.json | 264 + .../config/certificates/ca_settings.cnf | 39 + .../config/certificates/cert_settings.cnf | 7 + .../config/certificates/generate.sh | 26 + .../SampleConfig_HubLargeLandscape.json | 159 + .../samples/SampleConfig_HubOrientable.json | 174 + .../config/samples/SampleConfig_HubRound.json | 159 + .../SampleConfig_ResizableDesktopApp.json | 166 + .../samples/SampleConfig_TvFullscreen.json | 165 + .../SampleConfig_TvFullscreenAndOverlay.json | 217 + .../IPCServerSampleAppConfigSchema.json | 545 ++ .../src/CMakeLists.txt | 4 + .../IPCServerSampleApplication/src/main.cpp | 51 +- .../tools/Testing.cmake | 77 + .../tools/Testing/android_test.py | 114 + .../src/CloudControlledSettingProtocol.cpp | 2 +- .../src/DeviceControlledSettingProtocol.cpp | 2 +- Settings/src/SettingEventRequestObserver.cpp | 2 +- Settings/src/SettingEventSender.cpp | 2 +- Settings/src/SharedAVSSettingProtocol.cpp | 8 +- .../Storage/SQLiteDeviceSettingStorage.cpp | 2 +- Settings/src/Types/LocaleWakeWordsSetting.cpp | 12 +- Settings/src/Types/NetworkInfo.cpp | 2 +- Settings/test/SettingEventSenderTest.cpp | 22 +- SpeechEncoder/CMakeLists.txt | 10 - .../SpeechEncoder/OpusEncoderContext.h | 151 - .../OpusEncoderContext/src/CMakeLists.txt | 30 - .../include/SpeechEncoder/EncoderContext.h | 126 - .../include/SpeechEncoder/SpeechEncoder.h | 147 - SpeechEncoder/src/CMakeLists.txt | 18 - SpeechEncoder/src/SpeechEncoder.cpp | 248 - SpeechEncoder/test/CMakeLists.txt | 1 - Storage/SQLiteStorage/src/SQLiteDatabase.cpp | 2 +- .../SQLiteStorage/src/SQLiteMiscStorage.cpp | 2 +- Storage/SQLiteStorage/src/SQLiteStatement.cpp | 2 +- Storage/SQLiteStorage/src/SQLiteUtils.cpp | 2 +- .../PostConnectSynchronizeStateSender.h | 5 + .../src/PostConnectSynchronizeStateSender.cpp | 95 +- .../src/SynchronizeStateSenderFactory.cpp | 2 +- ThirdParty/googletest.cmake | 8 +- applications/CMakeLists.txt | 11 +- .../CMakeLists.txt | 4 + .../AlexaPresentationFeatureClient.h | 140 + .../AlexaPresentationFeatureClientBuilder.h | 80 + .../src/AlexaPresentationFeatureClient.cpp | 194 + .../AlexaPresentationFeatureClientBuilder.cpp | 92 + .../src/CMakeLists.txt | 20 + applications/FeatureClients/CMakeLists.txt | 11 + .../CMakeLists.txt | 4 + .../LiveViewControllerFeatureClient.h | 89 + .../LiveViewControllerFeatureClientBuilder.h | 90 + .../src/CMakeLists.txt | 22 + .../src/LiveViewControllerFeatureClient.cpp | 150 + ...LiveViewControllerFeatureClientBuilder.cpp | 95 + .../CMakeLists.txt | 4 + .../PresentationOrchestratorFeatureClient.h | 108 + ...entationOrchestratorFeatureClientBuilder.h | 65 + .../src/CMakeLists.txt | 19 + .../PresentationOrchestratorFeatureClient.cpp | 141 + ...tationOrchestratorFeatureClientBuilder.cpp | 63 + .../CMakeLists.txt | 4 + .../VisualCharacteristicsFeatureClient.h | 108 + ...isualCharacteristicsFeatureClientBuilder.h | 66 + .../src/CMakeLists.txt | 19 + .../VisualCharacteristicsFeatureClient.cpp | 164 + ...ualCharacteristicsFeatureClientBuilder.cpp | 70 + .../CMakeLists.txt | 4 + .../VisualStateTrackerFeatureClient.h | 92 + .../VisualStateTrackerFeatureClientBuilder.h | 63 + .../src/CMakeLists.txt | 20 + .../src/VisualStateTrackerFeatureClient.cpp | 127 + ...VisualStateTrackerFeatureClientBuilder.cpp | 66 + .../AndroidApplicationAudioPipelineFactory.h | 10 +- ...ApplicationAudioPipelineFactoryComponent.h | 4 +- ...AndroidApplicationAudioPipelineFactory.cpp | 14 +- .../src/AudioInputStreamFactory.cpp | 2 +- .../AuthorizationDelegateComponent.h | 58 - .../src/AuthorizationDelegateComponent.cpp | 35 - .../src/CMakeLists.txt | 14 - .../CustomApplicationAudioPipelineFactory.cpp | 2 +- .../src/DeviceSettingsManagerBuilder.cpp | 2 +- ...DefaultSampleApplicationOptionsComponent.h | 16 +- .../src/CMakeLists.txt | 3 +- ...faultSampleApplicationOptionsComponent.cpp | 32 - ...treamerApplicationAudioPipelineFactory.cpp | 2 +- .../SpeechEncoderComponent.h | 42 - .../acsdkNullSpeechEncoder/src/CMakeLists.txt | 13 +- .../src/SpeechEncoderComponent.cpp | 12 +- .../acsdkOpusSpeechEncoder/src/CMakeLists.txt | 17 +- .../src/SpeechEncoderComponent.cpp | 32 +- .../acsdkPreviewAlexaClient/CMakeLists.txt | 20 - .../acsdkPreviewAlexaClient/README.md | 13 - .../PreviewAlexaClient.h | 239 - .../PreviewAlexaClientComponent.h | 160 - .../src/CMakeLists.txt | 57 - .../src/PreviewAlexaClient.cpp | 1605 ---- .../src/PreviewAlexaClientComponent.cpp | 276 - .../SampleApplicationCBLAuthRequester.h | 45 +- .../src/CMakeLists.txt | 3 +- .../src/SampleApplicationCBLAuthRequester.cpp | 49 +- .../CMakeLists.txt | 1 + .../UIAuthNotifierInterface.h | 55 + .../UIStateAggregatorInterface.h | 45 + .../acsdkSensoryAdapter/CMakeLists.txt | 11 - .../acsdkKWD/src/KWDComponent.cpp | 68 - .../src/SensoryRegistration.cpp | 55 - .../Sensory/SensoryKeywordDetector.h | 183 - .../acsdkSensoryAdapter/src/CMakeLists.txt | 16 - .../src/SensoryKeywordDetector.cpp | 421 - .../acsdkSensoryAdapter/test/CMakeLists.txt | 5 - .../test/SensoryKeywordDetectorTest.cpp | 522 -- .../APLCapabilityCommon/.clang-format | 102 + .../APLCapabilityCommon/CMakeLists.txt | 5 + .../APLCapabilityCommon/APLPayloadParser.h | 101 + .../BaseAPLCapabilityAgent.h | 503 ++ .../src/APLPayloadParser.cpp | 216 + .../src/BaseAPLCapabilityAgent.cpp | 1104 +++ .../APLCapabilityCommon/src/CMakeLists.txt | 16 + .../test/BaseAPLCapabilityAgentTest.cpp | 1118 +++ .../APLCapabilityCommon/test/CMakeLists.txt | 21 + .../CMakeLists.txt | 14 + .../APLCapabilityAgentInterface.h | 132 + .../APLCapabilityAgentNotifierInterface.h | 30 + .../APLCapabilityAgentObserverInterface.h | 102 + .../APLCommandExecutionEvent.h | 71 + .../APLDocumentObserverInterface.h | 156 + .../APLDocumentSessionInterface.h | 118 + .../APLEventPayload.h | 236 + .../APLRuntimeInterface.h | 91 + .../APLTimeoutType.h | 50 + .../APLVideoConfiguration.h | 77 + .../PresentationOptions.h | 56 + .../PresentationSession.h | 58 + .../PresentationToken.h | 27 + .../VisualStateProviderInterface.h | 53 + .../test/CMakeLists.txt | 6 + .../MockAPLCapabilityAgent.h | 61 + .../MockAPLCapabilityAgentObserver.h | 59 + .../MockAPLDocumentObserver.h | 82 + .../MockAPLDocumentSession.h | 49 + .../MockAPLRuntime.h | 46 + .../MockVisualStateProvider.h | 40 + .../APLCapabilityCommon/CMakeLists.txt | 4 + .../acsdkAlerts/include/acsdkAlerts/Alert.h | 37 +- .../acsdkAlerts/Storage/SQLiteAlertStorage.h | 9 - capabilities/Alerts/acsdkAlerts/src/Alert.cpp | 132 +- .../Alerts/acsdkAlerts/src/AlertScheduler.cpp | 79 +- .../acsdkAlerts/src/AlertsCapabilityAgent.cpp | 34 +- .../Alerts/acsdkAlerts/src/CMakeLists.txt | 2 +- .../acsdkAlerts/src/Renderer/Renderer.cpp | 12 +- .../src/Storage/SQLiteAlertStorage.cpp | 180 +- .../Alerts/acsdkAlerts/test/AlertTest.cpp | 17 + .../test/SQLiteAlertStorageTest.cpp | 7 - .../AlexaChannelController/.clang-format | 101 + .../AlexaChannelController/CMakeLists.txt | 7 + .../AlexaChannelControllerFactory.h | 82 + .../AlexaChannelControllerCapabilityAgent.h | 271 + .../AlexaChannelControllerCapabilityAgent.cpp | 668 ++ .../src/AlexaChannelControllerFactory.cpp | 53 + .../AlexaChannelController/src/CMakeLists.txt | 16 + ...xaChannelControllerCapabilityAgentTest.cpp | 676 ++ .../test/CMakeLists.txt | 14 + .../CMakeLists.txt | 12 + .../ChannelControllerInterface.h | 155 + .../ChannelControllerObserverInterface.h | 46 + .../ChannelType.h | 164 + .../AlexaChannelController/CMakeLists.txt | 5 + .../AlexaKeypadController/.clang-format | 102 + .../AlexaKeypadController/CMakeLists.txt | 5 + .../acsdkAlexaKeypadController/CMakeLists.txt | 7 + .../AlexaKeypadControllerFactory.h | 71 + .../AlexaKeypadControllerCapabilityAgent.h | 175 + .../AlexaKeypadControllerCapabilityAgent.cpp | 332 + .../src/AlexaKeypadControllerFactory.cpp | 44 + .../src/CMakeLists.txt | 16 + ...exaKeypadControllerCapabilityAgentTest.cpp | 280 + .../test/CMakeLists.txt | 14 + .../CMakeLists.txt | 4 + .../AlexaKeypadControllerInterface.h | 107 + .../Keystroke.h | 94 + .../src/CMakeLists.txt | 12 + .../src/Keystroke.cpp | 84 + capabilities/AlexaLauncher/.clang-format | 102 + capabilities/AlexaLauncher/CMakeLists.txt | 5 + .../acsdkAlexaLauncher/CMakeLists.txt | 7 + .../acsdkAlexaLauncher/AlexaLauncherFactory.h | 78 + .../AlexaLauncherCapabilityAgent.h | 253 + .../src/AlexaLauncherCapabilityAgent.cpp | 481 + .../src/AlexaLauncherFactory.cpp | 45 + .../acsdkAlexaLauncher/src/CMakeLists.txt | 16 + .../test/AlexaLauncherCapabilityAgentTest.cpp | 302 + .../acsdkAlexaLauncher/test/CMakeLists.txt | 23 + .../CMakeLists.txt | 15 + .../AlexaLauncherInterface.h | 145 + .../AlexaLauncherObserverInterface.h | 48 + .../AlexaLauncherTargetState.h | 39 + .../AlexaPlaybackController/.clang-format | 102 + .../AlexaPlaybackController/CMakeLists.txt | 9 + .../CMakeLists.txt | 8 + .../AlexaPlaybackControllerFactory.h | 82 + .../AlexaPlaybackControllerCapabilityAgent.h | 216 + ...AlexaPlaybackControllerCapabilityAgent.cpp | 486 ++ .../src/AlexaPlaybackControllerFactory.cpp | 53 + .../src/CMakeLists.txt | 16 + ...aPlaybackControllerCapabilityAgentTest.cpp | 951 ++ .../test/CMakeLists.txt | 9 + .../CMakeLists.txt | 4 + .../AlexaPlaybackControllerInterface.h | 214 + ...AlexaPlaybackControllerObserverInterface.h | 45 + .../PlaybackOperation.h | 69 + .../PlaybackState.h | 54 + .../src/CMakeLists.txt | 13 + .../src/PlaybackOperation.cpp | 45 + .../src/PlaybackState.cpp | 34 + capabilities/AlexaPresentation/.clang-format | 102 + .../AlexaPresentation/CMakeLists.txt | 5 + .../AlexaPresentationFactory.h | 73 + .../private/AlexaPresentation.h | 164 + .../src/AlexaPresentation.cpp | 235 + .../src/AlexaPresentationFactory.cpp | 52 + .../AlexaPresentation/src/CMakeLists.txt | 14 + .../test/AlexaPresentationTest.cpp | 236 + .../AlexaPresentation/test/CMakeLists.txt | 20 + .../CMakeLists.txt | 15 + ...lexaPresentationCapabilityAgentInterface.h | 48 + .../APLDocumentObserverInterface.h | 135 + .../APLDocumentSessionInterface.h | 100 + .../APLRuntimeInterface.h | 93 + .../PresentationOptions.h | 86 + .../PresentationSession.h | 58 + .../test/CMakeLists.txt | 9 + .../MockAlexaPresentationCapabilityAgent.h | 39 + capabilities/AlexaPresentation/CMakeLists.txt | 4 + .../AlexaPresentationAPL/.clang-format | 102 + .../AlexaPresentationAPL/CMakeLists.txt | 5 + .../AlexaPresentationAPLFactory.h | 86 + .../private/AlexaPresentationAPL.h | 128 + .../AlexaPresentationAPLVideoConfigParser.h | 60 + .../src/AlexaPresentationAPL.cpp | 311 + .../src/AlexaPresentationAPLFactory.cpp | 57 + .../AlexaPresentationAPLVideoConfigParser.cpp | 99 + .../AlexaPresentationAPL/src/CMakeLists.txt | 14 + ...xaPresentationAPLVideoConfigParserTest.cpp | 130 + .../AlexaPresentationAPL/test/CMakeLists.txt | 18 + .../AlexaPresentationAPL/CMakeLists.txt | 5 + .../AlexaRecordController/.clang-format | 101 + .../AlexaRecordController}/CMakeLists.txt | 3 +- .../AlexaRecordControllerFactory.h | 80 + .../AlexaRecordControllerCapabilityAgent.h | 218 + .../AlexaRecordControllerCapabilityAgent.cpp | 404 + .../src/AlexaRecordControllerFactory.cpp | 44 + .../AlexaRecordController/src/CMakeLists.txt | 16 + ...exaRecordControllerCapabilityAgentTest.cpp | 425 + .../AlexaRecordController/test/CMakeLists.txt | 14 + .../CMakeLists.txt | 12 + .../RecordControllerInterface.h | 113 + .../AlexaRecordController/CMakeLists.txt | 5 + .../AlexaRemoteVideoPlayer/.clang-format | 101 + .../AlexaRemoteVideoPlayer}/CMakeLists.txt | 3 +- .../AlexaRemoteVideoPlayerFactory.h | 76 + .../AlexaRemoteVideoPlayerCapabilityAgent.h | 218 + .../AlexaRemoteVideoPlayerCapabilityAgent.cpp | 758 ++ .../src/AlexaRemoteVideoPlayerFactory.cpp | 45 + .../AlexaRemoteVideoPlayer/src/CMakeLists.txt | 16 + ...xaRemoteVideoPlayerCapabilityAgentTest.cpp | 464 + .../test/CMakeLists.txt | 14 + .../CMakeLists.txt | 13 + .../RemoteVideoPlayerConfiguration.h | 109 + .../RemoteVideoPlayerInterface.h | 117 + .../RemoteVideoPlayerTypes.h | 121 + .../AlexaRemoteVideoPlayer/CMakeLists.txt | 5 + .../AlexaSeekController/.clang-format | 102 + .../AlexaSeekController/CMakeLists.txt | 5 + .../acsdkAlexaSeekController/CMakeLists.txt | 7 + .../AlexaSeekControllerFactory.h | 78 + .../AlexaSeekControllerCapabilityAgent.h | 217 + .../AlexaSeekControllerCapabilityAgent.cpp | 369 + .../src/AlexaSeekControllerFactory.cpp | 46 + .../src/CMakeLists.txt | 16 + ...AlexaSeekControllerCapabilityAgentTest.cpp | 284 + .../test/CMakeLists.txt | 15 + .../CMakeLists.txt | 15 + .../AlexaSeekControllerInterface.h | 139 + capabilities/AlexaVideoCommon/.clang-format | 101 + capabilities/AlexaVideoCommon/CMakeLists.txt | 2 + .../VideoContent}/CMakeLists.txt | 2 +- .../acsdk/VideoContent/VideoEntityTypes.h | 513 ++ .../VideoContent/src/CMakeLists.txt | 11 + .../VideoContent/src/VideoEntityTypes.cpp | 641 ++ .../VideoContent/test/CMakeLists.txt | 8 + .../test/VideoEntityTypesTest.cpp | 382 + capabilities/AlexaVideoRecorder/.clang-format | 101 + .../AlexaVideoRecorder/CMakeLists.txt | 5 + .../acsdkAlexaVideoRecorder/CMakeLists.txt | 7 + .../AlexaVideoRecorderFactory.h | 75 + .../AlexaVideoRecorderCapabilityAgent.h | 206 + .../src/AlexaVideoRecorderCapabilityAgent.cpp | 742 ++ .../src/AlexaVideoRecorderFactory.cpp | 44 + .../src/CMakeLists.txt | 16 + .../AlexaVideoRecorderCapabilityAgentTest.cpp | 553 ++ .../test/CMakeLists.txt | 19 + .../CMakeLists.txt | 13 + .../VideoRecorderInterface.h | 138 + .../VideoRecorderTypes.h | 135 + .../include/acsdkAssetManager/AssetManager.h | 8 +- .../acsdkAssetManager/src/StorageManager.cpp | 8 +- .../src/UrlAllowListWrapper.cpp | 3 - .../test/AssetManagerUpdateTest.cpp | 6 +- .../acsdkAssetManagerClient/ArtifactWrapper.h | 8 +- .../include/acsdkAudioPlayer/AudioItem.h | 1 + .../include/acsdkAudioPlayer/AudioPlayer.h | 37 +- .../acsdkAudioPlayer/AudioPlayerComponent.h | 7 +- .../include/acsdkAudioPlayer/Util.h | 8 +- .../acsdkAudioPlayer/src/AudioPlayer.cpp | 203 +- .../src/AudioPlayerComponent.cpp | 6 +- .../acsdkAudioPlayer/src/CMakeLists.txt | 7 +- .../acsdkAudioPlayer/src/ProgressTimer.cpp | 2 +- .../AudioPlayer/acsdkAudioPlayer/src/Util.cpp | 65 +- .../acsdkAudioPlayer/test/AudioPlayerTest.cpp | 116 +- .../acsdkAudioPlayer/test/CMakeLists.txt | 2 +- .../acsdkAudioPlayer/test/UtilsTest.cpp | 99 + .../include/acsdkBluetooth/Bluetooth.h | 9 + .../acsdkBluetooth/BluetoothNotifier.h | 4 +- .../acsdkBluetooth/src/Bluetooth.cpp | 73 +- .../BluetoothLocalInterface.h | 33 +- .../BluetoothNotifierInterface.h | 4 +- .../acsdkAssetsCommon/src/CurlWrapper.cpp | 2 +- .../acsdkAssetsInterfaces/ResultCode.h | 3 + .../acsdkDeviceSetup/DeviceSetupFactory.h | 39 + .../acsdkDeviceSetup/src/CMakeLists.txt | 1 + .../src/DeviceSetupFactory.cpp | 28 + .../ExternalMediaPlayer/CMakeLists.txt | 1 + .../ExternalMediaAdapterHandler.h | 12 - .../StaticExternalMediaPlayerAdapterHandler.h | 4 - .../src/ExternalMediaAdapterHandler.cpp | 8 - .../src/ExternalMediaPlayer.cpp | 139 +- ...taticExternalMediaPlayerAdapterHandler.cpp | 12 - .../test/CMakeLists.txt | 6 +- .../test/ExternalMediaAdapterHandlerTest.cpp | 112 +- .../test/ExternalMediaPlayerTest.cpp | 276 +- .../MockExternalMediaAdapterHandler.h | 72 + .../CMakeLists.txt | 1 + .../AdapterUtils.h | 37 - .../ExternalMediaAdapterConstants.h | 4 - .../ExternalMediaAdapterHandlerInterface.h | 20 - .../ExternalMediaAdapterInterface.h | 42 +- .../src/AdapterUtils.cpp | 26 - .../test/CMakeLists.txt | 23 + .../MockExternalMediaPlayer.h | 58 + .../MockExternalMediaPlayerAdapter.h | 123 + .../MockExternalMediaPlayerObserver.h | 56 + ...ckRenderPlayerInfoCardsProviderRegistrar.h | 58 + .../AlexaInputController/CMakeLists.txt | 7 + .../InputControllerFactory.h | 83 + .../private/InputControllerCapabilityAgent.h | 236 + .../AlexaInputController/src/CMakeLists.txt | 20 + .../src/InputControllerCapabilityAgent.cpp | 502 ++ .../src/InputControllerFactory.cpp | 41 + .../AlexaInputController/test/CMakeLists.txt | 14 + .../InputControllerCapabilityAgentTest.cpp | 455 + .../CMakeLists.txt | 4 + .../InputControllerInterface.h | 139 + .../InputControllerObserverInterface.h | 48 + .../InputType.h | 178 + .../src/CMakeLists.txt | 12 + .../src/InputType.cpp | 278 + capabilities/InputController/CMakeLists.txt | 7 +- .../acsdkInputController/CMakeLists.txt | 7 - .../InputControllerFactory.h | 53 - .../acsdkInputController/src/CMakeLists.txt | 19 - .../src/InputControllerCapabilityAgent.cpp | 296 - .../src/InputControllerCapabilityAgent.h | 124 - .../src/InputControllerFactory.cpp | 38 - .../acsdkInputController/test/CMakeLists.txt | 10 - .../InputControllerCapabilityAgentTest.cpp | 272 - .../CMakeLists.txt | 12 - .../InputControllerHandlerInterface.h | 74 - capabilities/LiveViewController/.clang-format | 101 + .../AlexaLiveViewController/CMakeLists.txt | 7 + .../AlexaLiveViewControllerFactory.h | 84 + .../AlexaLiveViewControllerCapabilityAgent.h | 361 + ...AlexaLiveViewControllerCapabilityAgent.cpp | 1431 +++ .../src/AlexaLiveViewControllerFactory.cpp | 46 + .../src/CMakeLists.txt | 21 + ...aLiveViewControllerCapabilityAgentTest.cpp | 595 ++ .../test/CMakeLists.txt | 14 + .../CMakeLists.txt | 12 + .../LiveViewControllerConfiguration.h | 59 + .../LiveViewControllerInterface.h | 169 + .../LiveViewControllerObserverInterface.h | 47 + .../LiveViewControllerTypes.h | 575 ++ .../LiveViewController/CMakeLists.txt | 7 + .../NotificationsCapabilityAgent.h | 28 + .../NotificationsNotifier.h | 5 +- .../src/NotificationRenderer.cpp | 2 +- .../src/NotificationsCapabilityAgent.cpp | 55 +- .../test/NotificationsCapabilityAgentTest.cpp | 12 + .../NotificationsNotifierInterface.h | 4 +- capabilities/TemplateRuntime/.clang-format | 102 + capabilities/TemplateRuntime/CMakeLists.txt | 4 + .../TemplateRuntime/CMakeLists.txt | 5 + ...rPlayerInfoCardsProviderRegistrarFactory.h | 37 + .../TemplateRuntime/TemplateRuntimeFactory.h | 73 + .../RenderPlayerInfoCardsProviderRegistrar.h | 21 +- .../private}/TemplateRuntime.h | 247 +- .../TemplateRuntime/src/CMakeLists.txt | 18 + ...RenderPlayerInfoCardsProviderRegistrar.cpp | 15 +- ...layerInfoCardsProviderRegistrarFactory.cpp | 31 + .../TemplateRuntime/src/TemplateRuntime.cpp | 435 + .../src/TemplateRuntimeFactory.cpp | 46 + .../TemplateRuntime/test/CMakeLists.txt | 10 + ...erPlayerInfoCardsProviderRegistrarTest.cpp | 21 +- .../test/TemplateRuntimeTest.cpp | 397 +- .../TemplateRuntimeInterfaces/CMakeLists.txt | 9 + .../TemplateRuntimeInterface.h | 69 + .../TemplateRuntimeObserverInterface.h | 47 +- .../VisualCharacteristics/.clang-format | 102 + .../VisualCharacteristics}/CMakeLists.txt | 4 +- .../VisualCharacteristics/CMakeLists.txt | 5 + .../VisualCharacteristicsFactory.h | 70 + .../VisualCharacteristicsSerializerFactory.h | 38 + .../private/VCConfigParser.h | 133 + .../private/VisualCharacteristics.h | 201 + .../private/VisualCharacteristicsSerializer.h | 64 + .../VisualCharacteristics/src/CMakeLists.txt | 21 + .../src/VCConfigParser.cpp | 514 ++ .../src/VisualCharacteristics.cpp | 465 + .../src/VisualCharacteristicsFactory.cpp | 37 + .../src/VisualCharacteristicsSerializer.cpp | 69 + ...VisualCharacteristicsSerializerFactory.cpp | 27 + .../VisualCharacteristics/test/CMakeLists.txt | 9 + .../test/VCConfigParserTest.cpp | 419 + .../test/VisualCharacteristicsTest.cpp | 481 + .../CMakeLists.txt | 9 + .../VisualCharacteristicsInterface.h | 452 + ...VisualCharacteristicsSerializerInterface.h | 71 + cmakeBuild/BuildDefaults.cmake | 11 +- cmakeBuild/cmake/CodeCoverage/postCTest.sh | 14 + cmakeBuild/cmake/CodeCoverage/preCTest.sh | 14 + cmakeBuild/cmake/Comms.cmake | 6 + cmakeBuild/cmake/DefaultLibNames.cmake | 2 - cmakeBuild/cmake/EndpointControllers.cmake | 12 +- .../cmake/EndpointVideoControllers.cmake | 109 + cmakeBuild/cmake/InputController.cmake | 12 - cmakeBuild/cmake/KeywordDetector.cmake | 30 +- cmakeBuild/cmake/Logger.cmake | 10 + cmakeBuild/cmake/Opus.cmake | 28 + cmakeBuild/cmake/PKCS11.cmake | 2 +- cmakeBuild/cmake/Platforms.cmake | 5 + cmakeBuild/cmake/PrepareInstall.cmake | 7 + cmakeBuild/cmake/SampleApplications.cmake | 9 + cmakeBuild/cmake/SpeechEncoder.cmake | 14 - .../AudioEncoder/AudioEncoder}/CMakeLists.txt | 2 +- .../acsdk/AudioEncoder/AudioEncoderFactory.h | 64 + .../acsdk/AudioEncoder/AudioEncoderParams.h | 109 + .../acsdk/AudioEncoder/private/AudioEncoder.h | 265 + .../AudioEncoder/private/AudioEncoderState.h | 46 + .../AudioEncoder/src/AudioEncoder.cpp | 416 + .../AudioEncoder/src/AudioEncoderFactory.cpp | 56 + .../AudioEncoder/src/AudioEncoderParams.cpp | 50 + .../AudioEncoder/src/AudioEncoderState.cpp | 46 + .../AudioEncoder/src/CMakeLists.txt | 11 + .../AudioEncoder/test/AudioEncoderTest.cpp | 141 +- .../AudioEncoder/test/CMakeLists.txt | 1 + .../AudioEncoderComponent/CMakeLists.txt | 9 + .../AudioEncoderComponent/ComponentFactory.h | 23 +- .../AudioEncoderInterfaces/CMakeLists.txt | 9 + .../AudioEncoderInterface.h | 123 + .../BlockAudioEncoderInterface.h | 202 + core/AudioEncoder/CMakeLists.txt | 8 + .../OpusAudioEncoder/CMakeLists.txt | 4 + .../OpusAudioEncoder/AudioEncoderFactory.h | 39 + .../private/OpusAudioEncoder.h | 87 + .../src/AudioEncoderFactory.cpp | 29 + .../OpusAudioEncoder/src/CMakeLists.txt | 19 + .../OpusAudioEncoder/src/OpusAudioEncoder.cpp | 98 +- .../LWA/LWAAuthorizationAdapter.h | 6 + .../LWA/LWAAuthorizationStorage.h | 21 +- .../private/LWA/LWAStorageConstants.h | 2 +- .../private/LWA/LWAStorageDataMigration.h | 8 +- .../src/AuthorizationManager.cpp | 5 +- .../src/AuthorizationManagerStorage.cpp | 2 +- .../src/LWA/LWAAuthorizationAdapter.cpp | 15 +- .../src/LWA/LWAAuthorizationConfiguration.cpp | 2 +- .../src/LWA/LWAAuthorizationStorage.cpp | 16 +- .../src/LWA/LWAStorageDataMigration.cpp | 8 +- .../test/AuthorizationManagerTest.cpp | 16 +- .../test/LWAAuthStorageMigrationTest.cpp | 8 +- .../test/LWAAuthorizationStorageTest.cpp | 4 +- core/CMakeLists.txt | 3 +- .../CMakeLists.txt | 0 .../doc/CodecUtils.dox | 10 +- .../include/acsdk/CodecUtils}/Base64.h | 12 +- .../include/acsdk/CodecUtils}/Hex.h | 12 +- .../include/acsdk/CodecUtils}/Types.h | 10 +- .../acsdk/CodecUtils}/private/Base64Common.h | 12 +- .../acsdk/CodecUtils}/private/CodecsCommon.h | 10 +- .../src/Base64Common.cpp | 8 +- .../src/Base64Internal.cpp | 6 +- .../src/Base64OpenSsl.cpp | 10 +- .../src/CMakeLists.txt | 0 .../src/CodecsCommon.cpp | 6 +- .../src/Hex.cpp | 10 +- .../test/Base64CodecTest.cpp | 6 +- .../test/Base64InternalCodecTest.cpp | 4 +- .../test/CMakeLists.txt | 0 .../test/HexCodecTest.cpp | 6 +- core/Crypto/CMakeLists.txt | 8 +- .../{acsdkCrypto => Crypto}/CMakeLists.txt | 0 .../CryptoIMPL.dox => Crypto/doc/Crypto.dox} | 26 +- .../include/acsdk/Crypto}/CryptoFactory.h | 16 +- .../acsdk/Crypto}/private/Logging.h | 12 +- .../Crypto}/private/OpenSslCryptoCodec.h | 22 +- .../Crypto}/private/OpenSslCryptoFactory.h | 32 +- .../acsdk/Crypto}/private/OpenSslDigest.h | 20 +- .../Crypto}/private/OpenSslErrorCleanup.h | 12 +- .../acsdk/Crypto}/private/OpenSslKeyFactory.h | 18 +- .../acsdk/Crypto}/private/OpenSslTypeMapper.h | 22 +- .../acsdk/Crypto}/private/OpenSslTypes.h | 22 +- .../src/CMakeLists.txt | 0 .../src/CryptoFactory.cpp | 12 +- .../src/OpenSslCryptoCodec.cpp | 32 +- .../src/OpenSslCryptoFactory.cpp | 18 +- .../src/OpenSslDigest.cpp | 18 +- .../src/OpenSslErrorCleanup.cpp | 6 +- .../src/OpenSslKeyFactory.cpp | 16 +- .../src/OpenSslTypeMapper.cpp | 15 +- .../src/OpenSslTypes.cpp | 8 +- .../test/CMakeLists.txt | 0 .../test/OpenSslCryptoCodecAEADTest.cpp | 16 +- .../test/OpenSslCryptoCodecTest.cpp | 16 +- .../test/OpenSslCryptoFactoryTest.cpp | 14 +- core/Crypto/Crypto/test/OpenSslDigestTest.cpp | 250 + .../test/OpenSslKeyFactoryTest.cpp | 8 +- .../test/OpenSslTypeMapperTest.cpp | 8 +- .../CMakeLists.txt | 0 .../doc/CryptoInterfaces.dox} | 20 +- .../doc/Namespaces.dox | 4 +- .../acsdk/CryptoInterfaces}/AlgorithmType.h | 14 +- .../CryptoInterfaces}/CryptoCodecInterface.h | 12 +- .../CryptoFactoryInterface.h | 14 +- .../acsdk/CryptoInterfaces}/DigestInterface.h | 12 +- .../acsdk/CryptoInterfaces}/DigestType.h | 21 +- .../CryptoInterfaces}/KeyFactoryInterface.h | 12 +- .../CryptoInterfaces}/KeyStoreInterface.h | 14 +- .../src/AlgorithmType.cpp | 6 +- .../src/CMakeLists.txt | 0 .../src/DigestType.cpp | 8 +- .../test/CMakeLists.txt | 0 .../CryptoInterfaces}/test/doc/Namespaces.dox | 7 +- .../CryptoInterfaces/test/MockCryptoCodec.h | 47 + .../CryptoInterfaces/test/MockCryptoFactory.h | 50 + .../acsdk/CryptoInterfaces/test/MockDigest.h | 47 + .../CryptoInterfaces/test/MockKeyFactory.h | 39 + .../CryptoInterfaces/test/MockKeyStore.h | 78 + .../{acsdkPkcs11 => Pkcs11}/CMakeLists.txt | 0 .../doc/CryptoPKCS11.dox | 28 +- .../include/acsdk/Pkcs11}/KeyStoreFactory.h | 18 +- .../acsdk/Pkcs11}/private/ErrorCleanupGuard.h | 10 +- .../acsdk/Pkcs11}/private/Logging.h | 12 +- .../acsdk/Pkcs11}/private/PKCS11API.h | 8 +- .../acsdk/Pkcs11}/private/PKCS11Config.h | 12 +- .../acsdk/Pkcs11}/private/PKCS11Functions.h | 14 +- .../acsdk/Pkcs11}/private/PKCS11Key.h | 18 +- .../Pkcs11}/private/PKCS11KeyDescriptor.h | 30 +- .../acsdk/Pkcs11}/private/PKCS11KeyStore.h | 30 +- .../acsdk/Pkcs11}/private/PKCS11Session.h | 16 +- .../acsdk/Pkcs11}/private/PKCS11Slot.h | 14 +- .../src/CMakeLists.txt | 0 .../src/KeyStoreFactory.cpp | 10 +- .../src/PKCS11Config.cpp | 10 +- .../src/PKCS11Functions.cpp | 12 +- .../src/PKCS11FunctionsPosix.cpp | 12 +- .../src/PKCS11FunctionsUwp.cpp | 12 +- .../{acsdkPkcs11 => Pkcs11}/src/PKCS11Key.cpp | 18 +- .../src/PKCS11KeyDescriptor.cpp | 22 +- .../src/PKCS11KeyStore.cpp | 16 +- .../src/PKCS11Session.cpp | 16 +- .../src/PKCS11Slot.cpp | 14 +- .../test/CMakeLists.txt | 0 .../test/ErrorCleanupGuardTest.cpp | 6 +- .../test/PKCS11ConfigTest.cpp | 6 +- .../test/PKCS11FunctionsTest.cpp | 10 +- .../test/PKCS11KeyStoreTest.cpp | 12 +- .../test/PKCS11KeyTest.cpp | 16 +- .../test/PKCS11SessionTest.cpp | 14 +- .../test/PKCS11SlotTest.cpp | 10 +- .../testStubs/CMakeLists.txt | 0 .../testStubs/doc/CryptoPKCS11Stubs.dox | 12 +- .../testStubs/src/Pkcs11Stubs.cpp | 20 +- .../acsdkCrypto/test/OpenSslDigestTest.cpp | 119 - .../test/MockCryptoCodec.h | 84 - .../test/MockCryptoFactory.h | 71 - .../acsdkCryptoInterfaces/test/MockDigest.h | 98 - .../test/MockKeyFactory.h | 50 - .../acsdkCryptoInterfaces/test/MockKeyStore.h | 160 - core/Properties/CMakeLists.txt | 4 +- .../CMakeLists.txt | 0 .../doc/Namespaces.dox | 9 +- .../doc/acsdkProperties.dox} | 20 +- .../EncryptedPropertiesFactories.h | 28 +- .../Properties}/ErrorCallbackInterface.h | 16 +- .../acsdk/Properties}/ErrorCallbackSetter.h | 14 +- .../acsdk/Properties}/MiscStorageAdapter.h | 26 +- .../acsdk/Properties}/private/Asn1Helper.h | 20 +- .../acsdk/Properties}/private/Asn1Types.h | 12 +- .../Properties}/private/DataPropertyCodec.h | 26 +- .../private/DataPropertyCodecState.h | 26 +- .../Properties}/private/EncryptedProperties.h | 54 +- .../private/EncryptedPropertiesFactory.h | 42 +- .../private/EncryptionKeyPropertyCodec.h | 44 +- .../private/EncryptionKeyPropertyCodecState.h | 30 +- .../acsdk/Properties}/private/Logging.h | 22 +- .../private/MiscStorageProperties.h | 20 +- .../private/MiscStoragePropertiesFactory.h | 20 +- .../acsdk/Properties}/private/RetryExecutor.h | 16 +- .../src/Asn1Helper.cpp | 18 +- .../src/Asn1Types.cpp | 6 +- .../src/CMakeLists.txt | 0 .../src/DataPropertyCodec.cpp | 14 +- .../src/DataPropertyCodecState.cpp | 16 +- .../src/EncryptedProperties.cpp | 22 +- .../src/EncryptedPropertiesFactories.cpp | 20 +- .../src/EncryptedPropertiesFactory.cpp | 16 +- .../src/EncryptionKeyPropertyCodec.cpp | 28 +- .../src/EncryptionKeyPropertyCodecState.cpp | 14 +- .../src/ErrorCallbackSetter.cpp | 8 +- .../src/Logging.cpp | 10 +- .../src/MiscStorageAdapter.cpp | 12 +- .../src/MiscStorageProperties.cpp | 16 +- .../src/MiscStoragePropertiesFactory.cpp | 14 +- .../src/RetryExecutor.cpp | 12 +- .../src/SimpleMiscStorageUriMapper.cpp | 10 +- .../test/CMakeLists.txt | 0 .../test/MiscStoragePropertiesFactoryTest.cpp | 6 +- .../test/MiscStoragePropertiesTest.cpp | 8 +- .../testCrypto/CMakeLists.txt | 0 .../testCrypto/DataPropertyCodecTest.cpp | 30 +- .../EncryptedPropertiesFactoryTest.cpp | 30 +- .../testCrypto/EncryptedPropertiesTest.cpp | 40 +- .../EncryptionKeyPropertyCodecTest.cpp | 32 +- .../CMakeLists.txt | 0 .../doc/Namespaces.dox | 4 +- .../doc/acsdkPropertiesInterfaces.dox} | 14 +- .../PropertiesFactoryInterface.h | 18 +- .../PropertiesInterface.h | 12 +- .../test/CMakeLists.txt | 0 .../test/doc/Namespaces.dox | 20 + .../test/MockProperties.h | 49 + .../test/MockPropertiesFactory.h | 44 + .../test/StubProperties.h | 18 +- .../test/StubPropertiesFactory.h | 16 +- .../test/src/CMakeLists.txt | 4 +- .../test/src/StubProperties.cpp | 6 +- .../test/src/StubPropertiesFactory.cpp | 8 +- .../test/MockProperties.h | 88 - .../test/MockPropertiesFactory.h | 54 - .../AlexaEventProcessedNotifierInterface.h | 4 +- .../include/acsdkCore/CoreComponent.h | 3 - core/acsdkCore/src/CoreComponent.cpp | 2 +- .../PostConnectOperationProviderRegistrar.cpp | 2 +- .../RegistrationManagerFactory.h | 59 + .../RegistrationNotifier.h | 4 +- .../src/CMakeLists.txt | 1 + .../src/CustomerDataHandler.cpp | 2 +- .../src/CustomerDataManager.cpp | 2 +- .../src/RegistrationManager.cpp | 2 +- .../src/RegistrationManagerFactory.cpp | 41 + .../RegistrationNotifierInterface.h | 4 +- .../SystemClockNotifier.h | 4 +- .../src/SystemClockMonitor.cpp | 2 +- .../SystemClockNotifierInterface.h | 4 +- doc/CMakeLists.txt | 23 +- doc/doxygen.cfg.in | 8 +- shared/CMakeLists.txt | 23 +- .../AbstractKeywordDetector.h | 18 +- .../KeywordDetectorStateNotifier.h | 4 +- .../acsdkKWDImplementations/KeywordNotifier.h | 4 +- .../src/AbstractKeywordDetector.cpp | 18 +- .../test/AbstractKeywordDetectorTest.cpp | 6 +- .../test/CMakeLists.txt | 2 +- .../KeywordDetectorStateNotifierInterface.h | 4 +- .../KeywordNotifierInterface.h | 4 +- .../src/KeywordDetectorProvider.cpp | 2 +- shared/Notifier/CMakeLists.txt | 16 + shared/Notifier/doc/Namespaces.dox | 31 + shared/Notifier/doc/Notifier.dox | 22 + .../include/acsdk/Notifier/Notifier.h | 104 + .../acsdk/Notifier/internal/DataInterface.h | 101 + .../acsdk/Notifier/internal/NotifierTraits.h | 130 + .../acsdk/Notifier/private/NotifierData.h | 105 + .../acsdk/Notifier/private/ObserverWrapper.h | 115 + .../acsdk/Notifier/private/ReferenceType.h | 40 + shared/Notifier/src/NotifierData.cpp | 178 + shared/Notifier/src/ObserverWrapper.cpp | 189 + shared/Notifier/test/CMakeLists.txt | 3 + .../Notifier/test}/Namespaces.dox | 6 +- shared/Notifier/test/NotifierDataTest.cpp | 247 + .../test/NotifierTest.cpp | 96 +- shared/Notifier/test/NotifierTests.dox | 22 + shared/Notifier/test/NotifierTraitsTest.cpp | 69 + shared/Notifier/test/ObserverWrapperTest.cpp | 112 + .../CMakeLists.txt | 0 shared/NotifierInterfaces/doc/Namespaces.dox | 23 + .../doc/NotifierInterfaces.dox | 23 + .../NotifierInterfaces}/NotifierInterface.h | 60 +- shared/NotifierInterfaces/test/CMakeLists.txt | 11 + .../test/MockNotifierTest.cpp | 46 + shared/NotifierInterfaces/test/Namespaces.dox | 20 + .../test/NotifierInterfacesTests.dox | 22 + .../NotifierInterfaces/test}/MockNotifier.h | 34 +- shared/PresentationOrchestrator/.clang-format | 102 + .../PresentationOrchestrator/CMakeLists.txt | 6 + .../CMakeLists.txt | 5 + .../PresentationOrchestratorClientFactory.h | 61 + .../private/MultiWindowManagerInterface.h | 58 + .../private/Presentation.h | 177 + .../PresentationLifespanToTimeoutMapper.h | 68 + .../private/PresentationOrchestratorClient.h | 193 + .../private/ReorderableUniqueStack.h | 214 + .../private/WindowManager.h | 279 + .../src/CMakeLists.txt | 20 + .../src/Presentation.cpp | 274 + .../PresentationLifespanToTimeoutMapper.cpp | 121 + .../src/PresentationOrchestratorClient.cpp | 286 + .../PresentationOrchestratorClientFactory.cpp | 40 + .../src/WindowManager.cpp | 494 ++ .../test/CMakeLists.txt | 9 + .../PresentationOrchestratorClientTest.cpp | 1657 ++++ .../CMakeLists.txt | 13 + .../PresentationInterface.h | 106 + .../PresentationObserverInterface.h | 62 + .../PresentationOrchestratorClientInterface.h | 48 + .../PresentationOrchestratorInterface.h | 40 + ...tationOrchestratorStateObserverInterface.h | 44 + ...ntationOrchestratorStateTrackerInterface.h | 151 + .../PresentationOrchestratorTypes.h | 66 + ...ationOrchestratorWindowObserverInterface.h | 56 + .../PresentationTypes.h | 150 + .../VisualTimeoutManagerInterface.h | 56 + .../test/CMakeLists.txt | 9 + .../MockPresentationObserver.h | 52 + ...MockPresentationOrchestratorStateTracker.h | 101 + .../MockVisualTimeoutManager.h | 50 + .../CMakeLists.txt | 5 + ...sentationOrchestratorStateTrackerFactory.h | 57 + .../PresentationOrchestratorStateTracker.h | 223 + .../src/CMakeLists.txt | 17 + .../PresentationOrchestratorStateTracker.cpp | 439 + ...ntationOrchestratorStateTrackerFactory.cpp | 34 + .../test/CMakeLists.txt | 9 + ...esentationOrchestratorStateTrackerTest.cpp | 411 + .../VisualTimeoutManager/CMakeLists.txt | 5 + .../VisualTimeoutManagerFactory.h | 56 + .../private/VisualTimeoutManager.h | 242 + .../VisualTimeoutManager/src/CMakeLists.txt | 17 + .../src/VisualTimeoutManager.cpp | 218 + .../src/VisualTimeoutManagerFactory.cpp | 33 + .../VisualTimeoutManager/test/CMakeLists.txt | 9 + .../test/MockTimerFactory.h | 130 + .../test/VisualTimeoutManagerTest.cpp | 246 + shared/SDKClient/CMakeLists.txt | 5 + .../include/acsdk/SDKClient/Annotated.h | 188 + .../SDKClient/FeatureClientBuilderInterface.h | 81 + .../acsdk/SDKClient/FeatureClientInterface.h | 61 + .../acsdk/SDKClient/SDKClientBuilder.h | 88 + .../acsdk/SDKClient/SDKClientRegistry.h | 161 + .../FeatureClientBuilderInterface_impl.h | 29 + .../internal/SDKClientBuilder_impl.h | 42 + .../internal/SDKClientRegistry_impl.h | 76 + .../acsdk/SDKClient/internal/TypeRegistry.h | 124 + .../include/acsdk/SDKClient/internal/Utils.h | 80 + shared/SDKClient/src/CMakeLists.txt | 16 + shared/SDKClient/src/SDKClientBuilder.cpp | 113 + shared/SDKClient/src/SDKClientRegistry.cpp | 129 + shared/SDKClient/src/TypeRegistry.cpp | 74 + shared/SDKClient/test/CMakeLists.txt | 5 + .../SDKClient/test/SDKClientRegistryTest.cpp | 381 + .../InMemoryCommunicationPropertiesHandler.h | 4 +- .../CommunicationProperty.h | 34 +- .../internal/AbstractRecipe.h | 12 +- .../acsdkManufactory/internal/CookBook.h | 15 +- .../acsdkManufactory/internal/CookBook_imp.h | 86 +- .../internal/RuntimeManufactory.h | 5 +- .../internal/RuntimeManufactory_imp.h | 7 +- shared/acsdkManufactory/src/CookBook.cpp | 16 +- shared/acsdkNotifier/CMakeLists.txt | 16 - .../include/acsdkNotifier/internal/Notifier.h | 314 - shared/acsdkNotifier/test/CMakeLists.txt | 3 - .../test/CMakeLists.txt | 9 - .../acsdkShutdownManager/ShutdownNotifier.h | 4 +- .../src/ShutdownManager.cpp | 2 +- .../ShutdownNotifierInterface.h | 4 +- .../acsdkStartupManager/StartupNotifier.h | 4 +- .../src/StartupManager.cpp | 2 +- .../CMakeLists.txt | 2 + .../StartupNotifierInterface.h | 4 +- .../test/CMakeLists.txt | 10 + .../MockStartupNotifier.h | 57 + tools/Install/android.sh | 21 +- tools/Install/genConfig.sh | 33 +- tools/Install/mingw.sh | 12 +- tools/Install/pi.sh | 11 +- tools/Install/setup.sh | 8 +- tools/Testing.cmake | 7 +- tools/Testing/android_test.py | 4 +- 1842 files changed, 247185 insertions(+), 25230 deletions(-) create mode 100644 AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/GUIActivityEvent.h create mode 100644 AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/GUIActivityEventObserverInterface.h create mode 100644 AVSCommon/Utils/include/AVSCommon/Utils/Threading/ExecutorFactory.h create mode 100644 AVSCommon/Utils/include/AVSCommon/Utils/Threading/ExecutorInterface.h rename {shared/acsdkManufactory/include/acsdkManufactory/internal => AVSCommon/Utils/include/AVSCommon/Utils}/TypeIndex.h (82%) create mode 100644 AVSCommon/Utils/privateInclude/AVSCommon/Utils/Threading/private/SharedExecutor.h delete mode 100644 AVSCommon/Utils/src/Executor.cpp create mode 100644 AVSCommon/Utils/src/Threading/Executor.cpp create mode 100644 AVSCommon/Utils/src/Threading/ExecutorFactory.cpp create mode 100644 AVSCommon/Utils/src/Threading/SharedExecutor.cpp create mode 100644 AVSCommon/Utils/test/GmockExtensionTest.cpp create mode 100644 AVSCommon/Utils/test/LibCurlHTTP2ConnectionTest.cpp create mode 100644 AVSCommon/Utils/test/acsdk/Test/GmockExtensions.h delete mode 100644 ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/PlatformSpecificValues.h create mode 100644 ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientBuilder.h create mode 100644 ApplicationUtilities/DefaultClient/src/DefaultClientBuilder.cpp create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/CMakeLists.txt create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/doc/Namespaces.dox create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/doc/SpeakerManager.dox create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/Factories.h create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerConfigInterface.h rename CapabilityAgents/SpeakerManager/{include => SpeakerManager/include/acsdk}/SpeakerManager/SpeakerManagerStorageInterface.h (65%) rename CapabilityAgents/SpeakerManager/{include => SpeakerManager/include/acsdk}/SpeakerManager/SpeakerManagerStorageState.h (61%) rename CapabilityAgents/SpeakerManager/{include/SpeakerManager => SpeakerManager/privateInclude/acsdk/SpeakerManager/private}/ChannelVolumeManager.h (87%) rename CapabilityAgents/SpeakerManager/{include/SpeakerManager => SpeakerManager/privateInclude/acsdk/SpeakerManager/private}/DefaultChannelVolumeFactory.h (66%) rename CapabilityAgents/SpeakerManager/{include/SpeakerManager => SpeakerManager/privateInclude/acsdk/SpeakerManager/private}/SpeakerManager.h (86%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConfig.h rename CapabilityAgents/SpeakerManager/{include/SpeakerManager => SpeakerManager/privateInclude/acsdk/SpeakerManager/private}/SpeakerManagerConfigHelper.h (60%) rename CapabilityAgents/SpeakerManager/{include/SpeakerManager => SpeakerManager/privateInclude/acsdk/SpeakerManager/private}/SpeakerManagerConstants.h (76%) rename CapabilityAgents/SpeakerManager/{include/SpeakerManager => SpeakerManager/privateInclude/acsdk/SpeakerManager/private}/SpeakerManagerMiscStorage.h (65%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/src/CMakeLists.txt create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/src/ChannelVolumeFactory.cpp rename CapabilityAgents/SpeakerManager/{ => SpeakerManager}/src/ChannelVolumeManager.cpp (89%) rename CapabilityAgents/SpeakerManager/{ => SpeakerManager}/src/DefaultChannelVolumeFactory.cpp (70%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/src/Factories.cpp rename CapabilityAgents/SpeakerManager/{ => SpeakerManager}/src/SpeakerManager.cpp (80%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerConfig.cpp create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerConfigHelper.cpp rename CapabilityAgents/SpeakerManager/{ => SpeakerManager}/src/SpeakerManagerMiscStorage.cpp (77%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/test/CMakeLists.txt rename CapabilityAgents/SpeakerManager/{ => SpeakerManager}/test/ChannelVolumeManagerTest.cpp (95%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/test/Namespaces.dox rename CapabilityAgents/SpeakerManager/{ => SpeakerManager}/test/SpeakerManagerConfigHelperTest.cpp (56%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerConfigTest.cpp rename CapabilityAgents/SpeakerManager/{ => SpeakerManager}/test/SpeakerManagerMiscStorageTest.cpp (95%) rename CapabilityAgents/SpeakerManager/{ => SpeakerManager}/test/SpeakerManagerTest.cpp (55%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerTest.dox create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerConfig.h create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerObserver.h create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerStorage.h create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManagerComponent/CMakeLists.txt create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManagerComponent/doc/Namespaces.dox create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManagerComponent/doc/SpeakerManagerComponent.dox create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManagerComponent/include/acsdk/SpeakerManager/ChannelVolumeFactoryComponent.h rename CapabilityAgents/SpeakerManager/{include => SpeakerManagerComponent/include/acsdk}/SpeakerManager/SpeakerManagerComponent.h (73%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/CMakeLists.txt rename CapabilityAgents/SpeakerManager/{src/SpeakerManagerComponent.cpp => SpeakerManagerComponent/src/ChannelVolumeFactoryComponent.cpp} (63%) create mode 100644 CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/SpeakerManagerComponent.cpp delete mode 100644 CapabilityAgents/SpeakerManager/src/CMakeLists.txt delete mode 100644 CapabilityAgents/SpeakerManager/src/SpeakerManagerConfigHelper.cpp delete mode 100644 CapabilityAgents/SpeakerManager/test/CMakeLists.txt delete mode 100644 CapabilityAgents/TemplateRuntime/src/CMakeLists.txt delete mode 100644 CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp delete mode 100644 CapabilityAgents/TemplateRuntime/test/CMakeLists.txt delete mode 100644 Integration/include/Integration/ACLTestContext.h delete mode 100644 Integration/include/Integration/AipStateObserver.h delete mode 100644 Integration/include/Integration/AuthDelegateTestContext.h delete mode 100644 Integration/include/Integration/AuthObserver.h delete mode 100644 Integration/include/Integration/ClientMessageHandler.h delete mode 100644 Integration/include/Integration/ConnectionStatusObserver.h delete mode 100644 Integration/include/Integration/JsonHeader.h delete mode 100644 Integration/include/Integration/ObservableMessageRequest.h delete mode 100644 Integration/include/Integration/SDKTestContext.h delete mode 100644 Integration/include/Integration/TestAlertObserver.h delete mode 100644 Integration/include/Integration/TestDirectiveHandler.h delete mode 100644 Integration/include/Integration/TestExceptionEncounteredSender.h delete mode 100644 Integration/include/Integration/TestMediaPlayer.h delete mode 100644 Integration/include/Integration/TestMessageSender.h delete mode 100644 Integration/include/Integration/TestSpeechSynthesizerObserver.h delete mode 100755 Integration/inputs/alexa_recognize_joke_test.wav delete mode 100755 Integration/inputs/alexa_recognize_silence_test.wav delete mode 100755 Integration/inputs/alexa_recognize_wiki_test.wav delete mode 100755 Integration/inputs/recognize_cancel_timer_test.wav delete mode 100755 Integration/inputs/recognize_flashbriefing_test.wav delete mode 100755 Integration/inputs/recognize_joke_test.wav delete mode 100755 Integration/inputs/recognize_lions_test.wav delete mode 100755 Integration/inputs/recognize_long_timer_test.wav delete mode 100755 Integration/inputs/recognize_silence_test.wav delete mode 100755 Integration/inputs/recognize_sing_song_test.wav delete mode 100755 Integration/inputs/recognize_stop_test.wav delete mode 100755 Integration/inputs/recognize_stop_timer_test.wav delete mode 100755 Integration/inputs/recognize_test.wav delete mode 100755 Integration/inputs/recognize_timer_test.wav delete mode 100755 Integration/inputs/recognize_very_long_timer_test.wav delete mode 100755 Integration/inputs/recognize_volume_up_test.wav delete mode 100755 Integration/inputs/recognize_weather_test.wav delete mode 100755 Integration/inputs/recognize_whats_up_test.wav delete mode 100755 Integration/inputs/recognize_wiki_test.wav delete mode 100755 Integration/inputs/silence_test.wav delete mode 100755 Integration/inputs/utterance_time_success.opus delete mode 100644 Integration/src/ACLTestContext.cpp delete mode 100644 Integration/src/AipStateObserver.cpp delete mode 100644 Integration/src/AuthDelegateTestContext.cpp delete mode 100644 Integration/src/AuthObserver.cpp delete mode 100644 Integration/src/CMakeLists.txt delete mode 100644 Integration/src/ClientMessageHandler.cpp delete mode 100644 Integration/src/ConnectionStatusObserver.cpp delete mode 100644 Integration/src/ObservableMessageRequest.cpp delete mode 100644 Integration/src/SDKTestContext.cpp delete mode 100644 Integration/src/TestAlertObserver.cpp delete mode 100644 Integration/src/TestDirectiveHandler.cpp delete mode 100644 Integration/src/TestExceptionEncounteredSender.cpp delete mode 100644 Integration/src/TestMediaPlayer.cpp delete mode 100644 Integration/src/TestMessageSender.cpp delete mode 100644 Integration/src/TestSpeechSynthesizerObserver.cpp delete mode 100644 Integration/test/AlertsIntegrationTest.cpp delete mode 100644 Integration/test/AlexaAuthorizationDelegateTest.cpp delete mode 100644 Integration/test/AlexaCommunicationsLibraryTest.cpp delete mode 100644 Integration/test/AlexaDirectiveSequencerLibraryTest.cpp delete mode 100644 Integration/test/AudioInputProcessorIntegrationTest.cpp delete mode 100644 Integration/test/AudioPlayerIntegrationTest.cpp delete mode 100644 Integration/test/CMakeLists.txt delete mode 100644 Integration/test/NetworkIntegrationTest.cpp delete mode 100644 Integration/test/ServerDisconnectIntegrationTest.cpp delete mode 100644 Integration/test/SpeechSynthesizerIntegrationTest.cpp delete mode 100644 SampleApp/Authorization/CBLAuthDelegate/include/CBLAuthDelegate/CBLAuthDelegateStorageInterface.h delete mode 100644 SampleApp/Authorization/CBLAuthDelegate/include/CBLAuthDelegate/CBLAuthRequesterInterface.h delete mode 100644 SampleApp/Authorization/CBLAuthDelegate/src/CMakeLists.txt delete mode 100644 SampleApp/CMakeLists.txt delete mode 100644 SampleApp/include/SampleApp/InputControllerHandler.h delete mode 100644 SampleApp/include/SampleApp/LocaleAssetsManager.h delete mode 100644 SampleApp/include/SampleApp/PlatformSpecificValues.h delete mode 100644 SampleApp/src/CMakeLists.txt delete mode 100644 SampleApp/src/InputControllerHandler.cpp create mode 100644 SampleApplications/CMakeLists.txt rename {SampleApp => SampleApplications/Common}/Authorization/CBLAuthDelegate/include/CBLAuthDelegate/CBLAuthDelegate.h (97%) rename {SampleApp => SampleApplications/Common}/Authorization/CBLAuthDelegate/include/CBLAuthDelegate/SQLiteCBLAuthDelegateStorage.h (80%) rename {SampleApp => SampleApplications/Common}/Authorization/CBLAuthDelegate/src/CBLAuthDelegate.cpp (99%) rename {SampleApp => SampleApplications/Common}/Authorization/CBLAuthDelegate/src/SQLiteCBLAuthDelegateStorage.cpp (93%) create mode 100644 SampleApplications/Common/CMakeLists.txt rename {applications/acsdkCBLAuthorizationDelegate => SampleApplications/Common/Console}/CMakeLists.txt (57%) rename {SampleApp/include/SampleApp => SampleApplications/Common/Console/include/acsdk/Sample/Console}/ConsolePrinter.h (90%) create mode 100644 SampleApplications/Common/Console/src/CMakeLists.txt rename {SampleApp => SampleApplications/Common/Console}/src/ConsolePrinter.cpp (89%) create mode 100644 SampleApplications/Common/Endpoint/CMakeLists.txt rename {SampleApp/include/SampleApp => SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint}/DefaultEndpoint/DefaultEndpointModeControllerHandler.h (90%) rename {SampleApp/include/SampleApp => SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint}/DefaultEndpoint/DefaultEndpointRangeControllerHandler.h (89%) rename {SampleApp/include/SampleApp => SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint}/DefaultEndpoint/DefaultEndpointToggleControllerHandler.h (87%) create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointAlexaChannelControllerHandler.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointAlexaKeypadControllerHandler.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointAlexaLauncherHandler.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointAlexaPlaybackControllerHandler.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointAlexaRecordControllerHandler.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointAlexaRemoteVideoPlayerHandler.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointAlexaSeekControllerHandler.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointAlexaVideoRecorderHandler.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointCapabilitiesBuilder.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointFocusAdapter.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/EndpointInputControllerHandler.h create mode 100644 SampleApplications/Common/Endpoint/include/acsdk/Sample/Endpoint/InputControllerEndpointCapabilitiesBuilder.h create mode 100644 SampleApplications/Common/Endpoint/src/CMakeLists.txt rename {SampleApp => SampleApplications/Common/Endpoint}/src/DefaultEndpoint/DefaultEndpointModeControllerHandler.cpp (95%) rename {SampleApp => SampleApplications/Common/Endpoint}/src/DefaultEndpoint/DefaultEndpointRangeControllerHandler.cpp (95%) rename {SampleApp => SampleApplications/Common/Endpoint}/src/DefaultEndpoint/DefaultEndpointToggleControllerHandler.cpp (92%) create mode 100644 SampleApplications/Common/Endpoint/src/EndpointAlexaChannelControllerHandler.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointAlexaKeypadControllerHandler.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointAlexaLauncherHandler.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointAlexaPlaybackControllerHandler.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointAlexaRecordControllerHandler.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointAlexaRemoteVideoPlayerHandler.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointAlexaSeekControllerHandler.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointAlexaVideoRecorderHandler.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointCapabilitiesBuilder.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointFocusAdapter.cpp create mode 100644 SampleApplications/Common/Endpoint/src/EndpointInputControllerHandler.cpp create mode 100644 SampleApplications/Common/Endpoint/src/InputControllerEndpointCapabilitiesBuilder.cpp create mode 100644 SampleApplications/Common/InteractionManager/CMakeLists.txt rename {SampleApp/include/SampleApp => SampleApplications/Common/InteractionManager/include/acsdk/Sample/InteractionManager}/InteractionManager.h (85%) create mode 100644 SampleApplications/Common/InteractionManager/include/acsdk/Sample/InteractionManager/KeywordObserver.h rename {SampleApp/include/SampleApp => SampleApplications/Common/InteractionManager/include/acsdk/Sample/InteractionManager}/UIManager.h (91%) create mode 100644 SampleApplications/Common/InteractionManager/src/CMakeLists.txt rename {SampleApp => SampleApplications/Common/InteractionManager}/src/InteractionManager.cpp (76%) rename {SampleApp => SampleApplications/Common/InteractionManager}/src/KeywordObserver.cpp (96%) rename {SampleApp => SampleApplications/Common/InteractionManager}/src/UIManager.cpp (89%) create mode 100644 SampleApplications/Common/LibSampleApp/CMakeLists.txt rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/CaptionPresenter.h (89%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/ConsoleReader.h (92%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/ExternalCapabilitiesBuilder.h (87%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/GuiRenderer.h (68%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/KeywordObserver.h (92%) create mode 100644 SampleApplications/Common/LibSampleApp/include/SampleApp/LocaleAssetsManager.h rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/RevokeAuthorizationObserver.h (85%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/SDKDiagnostics.h (93%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/SampleApplication.h (92%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/SampleApplicationComponent.h (74%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/SampleApplicationReturnCodes.h (78%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/SampleEqualizerModeController.h (87%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/include/SampleApp/UserInputManager.h (88%) create mode 100644 SampleApplications/Common/LibSampleApp/src/CMakeLists.txt rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/CaptionPresenter.cpp (94%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/ConsoleReader.cpp (96%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/ExternalCapabilitiesBuilder.cpp (86%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/GuiRenderer.cpp (94%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/LocaleAssetsManager.cpp (59%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/RevokeAuthorizationObserver.cpp (100%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/SDKDiagnostics.cpp (98%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/SampleApplication.cpp (88%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/SampleApplicationComponent.cpp (86%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/SampleEqualizerModeController.cpp (92%) rename {SampleApp => SampleApplications/Common/LibSampleApp}/src/UserInputManager.cpp (94%) create mode 100644 SampleApplications/Common/PeripheralEndpoint/CMakeLists.txt rename {SampleApp/include/SampleApp => SampleApplications/Common/PeripheralEndpoint/include/acsdk/Sample}/PeripheralEndpoint/PeripheralEndpointModeControllerHandler.h (90%) rename {SampleApp/include/SampleApp => SampleApplications/Common/PeripheralEndpoint/include/acsdk/Sample}/PeripheralEndpoint/PeripheralEndpointPowerControllerHandler.h (88%) rename {SampleApp/include/SampleApp => SampleApplications/Common/PeripheralEndpoint/include/acsdk/Sample}/PeripheralEndpoint/PeripheralEndpointRangeControllerHandler.h (90%) rename {SampleApp/include/SampleApp => SampleApplications/Common/PeripheralEndpoint/include/acsdk/Sample}/PeripheralEndpoint/PeripheralEndpointToggleControllerHandler.h (89%) create mode 100644 SampleApplications/Common/PeripheralEndpoint/src/CMakeLists.txt create mode 100644 SampleApplications/Common/PeripheralEndpoint/src/NullPeripheralEndpoint.cpp rename {SampleApp/src/PeripheralEndpoint => SampleApplications/Common/PeripheralEndpoint/src}/PeripheralEndpointModeControllerHandler.cpp (95%) rename {SampleApp/src/PeripheralEndpoint => SampleApplications/Common/PeripheralEndpoint/src}/PeripheralEndpointPowerControllerHandler.cpp (94%) rename {SampleApp/src/PeripheralEndpoint => SampleApplications/Common/PeripheralEndpoint/src}/PeripheralEndpointRangeControllerHandler.cpp (95%) rename {SampleApp/src/PeripheralEndpoint => SampleApplications/Common/PeripheralEndpoint/src}/PeripheralEndpointToggleControllerHandler.cpp (93%) create mode 100644 SampleApplications/Common/PortAudioMicrophoneAdapter/CMakeLists.txt create mode 100644 SampleApplications/Common/PortAudioMicrophoneAdapter/include/acsdk/Sample/Microphone/PortAudioMicrophoneAdapter.h rename {SampleApp/include/SampleApp => SampleApplications/Common/PortAudioMicrophoneAdapter/privateInclude/acsdk/Sample/Microphone}/PortAudioMicrophoneWrapper.h (93%) create mode 100644 SampleApplications/Common/PortAudioMicrophoneAdapter/src/PortAudioMicrophoneAdapter.cpp create mode 100644 SampleApplications/Common/PortAudioMicrophoneAdapter/src/PortAudioMicrophoneWrapper.cpp create mode 100644 SampleApplications/Common/TemplateRuntimePresentationAdaptor/CMakeLists.txt create mode 100644 SampleApplications/Common/TemplateRuntimePresentationAdaptor/include/acsdk/Sample/TemplateRuntime/TemplateRuntimePresentationAdapter.h create mode 100644 SampleApplications/Common/TemplateRuntimePresentationAdaptor/include/acsdk/Sample/TemplateRuntime/TemplateRuntimePresentationAdapterObserverInterface.h create mode 100644 SampleApplications/Common/TemplateRuntimePresentationAdaptor/src/CMakeLists.txt create mode 100644 SampleApplications/Common/TemplateRuntimePresentationAdaptor/src/TemplateRuntimePresentationAdapter.cpp create mode 100644 SampleApplications/ConsoleSampleApplication/CMakeLists.txt create mode 100644 SampleApplications/ConsoleSampleApplication/src/CMakeLists.txt rename {SampleApp => SampleApplications/ConsoleSampleApplication}/src/main.cpp (98%) create mode 100644 SampleApplications/IPCServerSampleApplication/.clang-format create mode 100644 SampleApplications/IPCServerSampleApplication/.gitignore create mode 100644 SampleApplications/IPCServerSampleApplication/CMakeLists.txt create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/CMakeLists.txt create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/include/Communication/MessageListenerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/include/Communication/MessagingInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/include/Communication/MessagingServerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/include/Communication/MessagingServerObserverInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/include/Communication/WebSocketConfig.h create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/include/Communication/WebSocketSDKLogger.h create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/include/Communication/WebSocketServer.h create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/src/CMakeLists.txt create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/src/WebSocketSDKLogger.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/Communication/src/WebSocketServer.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/CMakeLists.txt create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/APLDocumentSession.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/APLDocumentSessionManager.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/APLDocumentSessionManagerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/APLPayloadParser.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/APLRuntimeInterfaceImpl.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/APLRuntimePresentationAdapter.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/APLViewhostInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/APLViewhostObserverInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/AplClientBridge.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/AlexaPresentation/IPCAPLAgent.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/CachingDownloadManager.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/ConfigValidator.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/ConnectionObserver.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/ConsolePrinter.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/DownloadMonitor.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/Extensions/LiveView/AplLiveViewExtension.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/Extensions/LiveView/AplLiveViewExtensionObserverInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/ExternalCapabilitiesBuilder.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUI/CaptureState.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUI/GUIActivityEventNotifier.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUI/GUIActivityEventNotifierInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUI/GUIClient.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUI/GUIClientInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUI/GUIManager.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUI/GUIServerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUI/NavigationEvent.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUI/TemplateRuntimePresentationAdapterBridge.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/GUILogBridge.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/APLClientHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/AlexaCaptionsHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/AudioFocusManagerHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/DoNotDisturbHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/InteractionManagerHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/LiveViewCameraHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/LoggerHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/SessionSetupHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/SystemHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/TemplateRuntimeHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Components/WindowManagerHandler.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/APLClientHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/AudioFocusManagerHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/DoNotDisturbHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/InteractionManagerHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/LiveViewCameraHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/LoggerHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/SessionSetupHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/SystemHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/TemplateRuntimeHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/HandlerInterfaces/WindowManagerHandlerInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/IPCDispatcherInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/IPCHandlerBase.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/IPCHandlerRegistrationInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/IPCRouter.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/IPCVersionManager.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/APLClientNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/AlexaCaptionsNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/AudioFocusManagerNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/CommunicationsNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/DoNotDisturbNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/InteractionManagerNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/LiveViewCameraNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/LoggerNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/SessionSetupNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/SystemNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/TemplateRuntimeNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/IPC/Namespaces/WindowManagerNamespace.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/KeywordObserver.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/LiveViewController/LiveViewControllerPresentationAdapter.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/LocaleAssetsManager.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/Messages/Message.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/Messages/MessageInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/PortAudioMicrophoneWrapper.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/RenderCaptionsInterface.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/RevokeAuthorizationObserver.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/SampleApplication.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/SampleApplicationComponent.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/SampleApplicationReturnCodes.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/SampleEqualizerModeController.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/SmartScreenCaptionPresenter.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/SmartScreenCaptionStateManager.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/TelemetrySink.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/include/IPCServerSampleApp/TimezoneHelper.h create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/AlexaPresentation/APLDocumentSession.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/AlexaPresentation/APLDocumentSessionManager.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/AlexaPresentation/APLPayloadParser.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/AlexaPresentation/APLRuntimeInterfaceImpl.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/AlexaPresentation/APLRuntimePresentationAdapter.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/AlexaPresentation/AplClientBridge.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/AlexaPresentation/IPCAPLAgent.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/CMakeLists.txt create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/CachingDownloadManager.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/ConfigValidator.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/ConnectionObserver.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/ConsolePrinter.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/DownloadMonitor.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/Extensions/LiveView/AplLiveViewExtension.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/ExternalCapabilitiesBuilder.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/GUI/GUIActivityEventNotifier.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/GUI/GUIClient.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/GUI/GUIManager.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/GUI/TemplateRuntimePresentationAdapterBridge.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/GUILogBridge.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/APLClientHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/AlexaCaptionsHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/AudioFocusManagerHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/DoNotDisturbHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/InteractionManagerHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/LiveViewCameraHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/LoggerHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/SessionSetupHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/SystemHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/TemplateRuntimeHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/Components/WindowManagerHandler.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/IPCHandlerBase.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/IPCRouter.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/IPC/IPCVersionManager.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/KeywordObserver.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/LiveViewController/LiveViewControllerPresentationAdapter.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/LocaleAssetsManager.cpp rename {SampleApp => SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp}/src/PortAudioMicrophoneWrapper.cpp (94%) create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/RevokeAuthorizationObserver.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/SampleApplication.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/SampleApplicationComponent.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/SampleEqualizerModeController.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/SmartScreenCaptionPresenter.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/SmartScreenCaptionStateManager.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/TelemetrySink.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/src/TimezoneHelper.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/test/CMakeLists.txt create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/test/IPCVersionManagerTest.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/test/SmartScreenCaptionPresenterTest.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/LibIPCServerSampleApp/test/SmartScreenCaptionStateManagerTest.cpp create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/.clang-format create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/.gitignore create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/.travis.yml create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/CMakeLists.txt create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/README.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/appveyor.yml create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/CHANGES create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/CMakeLists.txt create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/CONTRIBUTORS create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/LICENSE create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/Makefile.am create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/README.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/build-aux/.keep create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/configure.ac create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/CheatSheet.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/CookBook.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/DesignDoc.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/DevGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/Documentation.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/ForDummies.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/FrequentlyAskedQuestions.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/KnownIssues.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_5/CheatSheet.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_5/CookBook.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_5/Documentation.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_5/ForDummies.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_5/FrequentlyAskedQuestions.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_6/CheatSheet.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_6/CookBook.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_6/Documentation.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_6/ForDummies.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_6/FrequentlyAskedQuestions.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_7/CheatSheet.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_7/CookBook.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_7/Documentation.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_7/ForDummies.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/docs/v1_7/FrequentlyAskedQuestions.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-actions.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-cardinalities.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-actions.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-actions.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-function-mockers.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-function-mockers.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-matchers.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-matchers.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-nice-strict.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-nice-strict.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-matchers.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-more-actions.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-more-matchers.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-spec-builders.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/internal/custom/gmock-generated-actions.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/internal/custom/gmock-generated-actions.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/internal/custom/gmock-matchers.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/internal/custom/gmock-port.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/internal/gmock-generated-internal-utils.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/internal/gmock-generated-internal-utils.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/internal/gmock-internal-utils.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/internal/gmock-port.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/make/Makefile create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2005/gmock.sln create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2005/gmock.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2005/gmock_config.vsprops create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2005/gmock_main.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2005/gmock_test.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2010/gmock.sln create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2010/gmock.vcxproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2010/gmock_config.props create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2010/gmock_main.vcxproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2010/gmock_test.vcxproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2015/gmock.sln create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2015/gmock.vcxproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2015/gmock_config.props create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2015/gmock_main.vcxproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/msvc/2015/gmock_test.vcxproj create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/fuse_gmock_files.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/LICENSE create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/README create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/README.cppclean create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/cpp/__init__.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/cpp/ast.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/cpp/gmock_class.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/cpp/gmock_class_test.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/cpp/keywords.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/cpp/tokenize.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/cpp/utils.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/generator/gmock_gen.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/gmock-config.in create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/gmock_doctor.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/upload.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/scripts/upload_gmock.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/src/gmock-all.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/src/gmock-cardinalities.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/src/gmock-internal-utils.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/src/gmock-matchers.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/src/gmock-spec-builders.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/src/gmock.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/src/gmock_main.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-actions_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-cardinalities_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-generated-actions_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-generated-function-mockers_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-generated-internal-utils_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-generated-matchers_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-internal-utils_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-matchers_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-more-actions_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-nice-strict_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-port_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock-spec-builders_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_all_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_ex_test.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_leak_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_leak_test_.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_link2_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_link_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_link_test.h create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_output_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_output_test_.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_output_test_golden.txt create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_stress_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_test.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/test/gmock_test_utils.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/.gitignore create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/CHANGES create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/CMakeLists.txt create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/CONTRIBUTORS create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/LICENSE create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/Makefile.am create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/README.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/build-aux/.keep create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/cmake/internal_utils.cmake create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/codegear/gtest.cbproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/codegear/gtest.groupproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/codegear/gtest_all.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/codegear/gtest_link.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/codegear/gtest_main.cbproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/codegear/gtest_unittest.cbproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/configure.ac create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/AdvancedGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/DevGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/Documentation.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/FAQ.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/Primer.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/PumpManual.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/Samples.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_5_AdvancedGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_5_Documentation.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_5_FAQ.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_5_Primer.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_5_PumpManual.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_5_XcodeGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_6_AdvancedGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_6_Documentation.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_6_FAQ.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_6_Primer.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_6_PumpManual.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_6_Samples.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_6_XcodeGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_7_AdvancedGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_7_Documentation.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_7_FAQ.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_7_Primer.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_7_PumpManual.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_7_Samples.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/V1_7_XcodeGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/docs/XcodeGuide.md create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest-death-test.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest-message.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest-param-test.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest-param-test.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest-printers.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest-spi.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest-test-part.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest-typed-test.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest_pred_impl.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/gtest_prod.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/custom/gtest-port.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/custom/gtest-printers.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/custom/gtest.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-death-test-internal.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-filepath.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-internal.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-linked_ptr.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-param-util-generated.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-param-util-generated.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-param-util.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-port-arch.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-port.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-string.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-tuple.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-tuple.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-type-util.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/include/gtest/internal/gtest-type-util.h.pump create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/m4/acx_pthread.m4 create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/m4/gtest.m4 create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/make/Makefile create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest-md.sln create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest-md.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest.sln create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest_main-md.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest_main.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest_prod_test-md.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest_prod_test.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest_unittest-md.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/msvc/gtest_unittest.vcproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/prime_tables.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample1.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample1.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample10_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample1_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample2.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample2.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample2_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample3-inl.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample3_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample4.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample4.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample4_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample5_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample6_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample7_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample8_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/samples/sample9_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/scripts/common.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/scripts/fuse_gtest_files.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/scripts/gen_gtest_pred_impl.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/scripts/gtest-config.in create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/scripts/pump.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/scripts/release_docs.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/scripts/test/Makefile create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/scripts/upload.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/scripts/upload_gtest.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest-all.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest-death-test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest-filepath.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest-internal-inl.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest-port.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest-printers.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest-test-part.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest-typed-test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/src/gtest_main.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-death-test_ex_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-death-test_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-filepath_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-linked_ptr_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-listener_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-message_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-options_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-param-test2_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-param-test_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-param-test_test.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-port_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-printers_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-test-part_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-tuple_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-typed-test2_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-typed-test_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-typed-test_test.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest-unittest-api_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_all_test.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_break_on_failure_unittest.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_break_on_failure_unittest_.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_catch_exceptions_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_catch_exceptions_test_.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_color_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_color_test_.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_env_var_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_env_var_test_.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_environment_test.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_filter_unittest.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_filter_unittest_.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_help_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_help_test_.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_list_tests_unittest.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_list_tests_unittest_.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_main_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_no_test_unittest.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_output_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_output_test_.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_output_test_golden_lin.txt create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_pred_impl_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_premature_exit_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_prod_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_repeat_test.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_shuffle_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_shuffle_test_.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_sole_header_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_stress_test.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_test_utils.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_throw_on_failure_ex_test.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_throw_on_failure_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_throw_on_failure_test_.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_uninitialized_test.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_uninitialized_test_.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_unittest.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_xml_outfile1_test_.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_xml_outfile2_test_.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_xml_outfiles_test.py create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_xml_output_unittest.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_xml_output_unittest_.cc create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/gtest_xml_test_utils.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/production.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/test/production.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Config/DebugProject.xcconfig create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Config/FrameworkTarget.xcconfig create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Config/General.xcconfig create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Config/ReleaseProject.xcconfig create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Config/StaticLibraryTarget.xcconfig create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Config/TestTarget.xcconfig create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Resources/Info.plist create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Samples/FrameworkSample/Info.plist create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Samples/FrameworkSample/WidgetFramework.xcodeproj/project.pbxproj create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Samples/FrameworkSample/runtests.sh create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Samples/FrameworkSample/widget.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Samples/FrameworkSample/widget.h create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Samples/FrameworkSample/widget_test.cc create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Scripts/runtests.sh create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/Scripts/versiongenerate.py create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googletest/xcode/gtest.xcodeproj/project.pbxproj create mode 100755 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/travis.sh create mode 100644 SampleApplications/IPCServerSampleApplication/ThirdParty/googletest.cmake create mode 100644 SampleApplications/IPCServerSampleApplication/config/IPCServerSampleAppConfig.json create mode 100644 SampleApplications/IPCServerSampleApplication/config/certificates/ca_settings.cnf create mode 100644 SampleApplications/IPCServerSampleApplication/config/certificates/cert_settings.cnf create mode 100755 SampleApplications/IPCServerSampleApplication/config/certificates/generate.sh create mode 100644 SampleApplications/IPCServerSampleApplication/config/samples/SampleConfig_HubLargeLandscape.json create mode 100644 SampleApplications/IPCServerSampleApplication/config/samples/SampleConfig_HubOrientable.json create mode 100644 SampleApplications/IPCServerSampleApplication/config/samples/SampleConfig_HubRound.json create mode 100644 SampleApplications/IPCServerSampleApplication/config/samples/SampleConfig_ResizableDesktopApp.json create mode 100644 SampleApplications/IPCServerSampleApplication/config/samples/SampleConfig_TvFullscreen.json create mode 100644 SampleApplications/IPCServerSampleApplication/config/samples/SampleConfig_TvFullscreenAndOverlay.json create mode 100644 SampleApplications/IPCServerSampleApplication/config/schema/IPCServerSampleAppConfigSchema.json create mode 100644 SampleApplications/IPCServerSampleApplication/src/CMakeLists.txt rename applications/acsdkPreviewAlexaClient/src/previewMain.cpp => SampleApplications/IPCServerSampleApplication/src/main.cpp (74%) create mode 100644 SampleApplications/IPCServerSampleApplication/tools/Testing.cmake create mode 100644 SampleApplications/IPCServerSampleApplication/tools/Testing/android_test.py delete mode 100644 SpeechEncoder/CMakeLists.txt delete mode 100644 SpeechEncoder/OpusEncoderContext/include/SpeechEncoder/OpusEncoderContext.h delete mode 100644 SpeechEncoder/OpusEncoderContext/src/CMakeLists.txt delete mode 100644 SpeechEncoder/include/SpeechEncoder/EncoderContext.h delete mode 100644 SpeechEncoder/include/SpeechEncoder/SpeechEncoder.h delete mode 100644 SpeechEncoder/src/CMakeLists.txt delete mode 100644 SpeechEncoder/src/SpeechEncoder.cpp delete mode 100644 SpeechEncoder/test/CMakeLists.txt create mode 100644 applications/FeatureClients/AlexaPresentationFeatureClient/CMakeLists.txt create mode 100644 applications/FeatureClients/AlexaPresentationFeatureClient/include/acsdk/AlexaPresentationFeatureClient/AlexaPresentationFeatureClient.h create mode 100644 applications/FeatureClients/AlexaPresentationFeatureClient/include/acsdk/AlexaPresentationFeatureClient/AlexaPresentationFeatureClientBuilder.h create mode 100644 applications/FeatureClients/AlexaPresentationFeatureClient/src/AlexaPresentationFeatureClient.cpp create mode 100644 applications/FeatureClients/AlexaPresentationFeatureClient/src/AlexaPresentationFeatureClientBuilder.cpp create mode 100644 applications/FeatureClients/AlexaPresentationFeatureClient/src/CMakeLists.txt create mode 100644 applications/FeatureClients/CMakeLists.txt create mode 100644 applications/FeatureClients/LiveViewControllerFeatureClient/CMakeLists.txt create mode 100644 applications/FeatureClients/LiveViewControllerFeatureClient/include/acsdk/LiveViewControllerFeatureClient/LiveViewControllerFeatureClient.h create mode 100644 applications/FeatureClients/LiveViewControllerFeatureClient/include/acsdk/LiveViewControllerFeatureClient/LiveViewControllerFeatureClientBuilder.h create mode 100644 applications/FeatureClients/LiveViewControllerFeatureClient/src/CMakeLists.txt create mode 100644 applications/FeatureClients/LiveViewControllerFeatureClient/src/LiveViewControllerFeatureClient.cpp create mode 100644 applications/FeatureClients/LiveViewControllerFeatureClient/src/LiveViewControllerFeatureClientBuilder.cpp create mode 100644 applications/FeatureClients/PresentationOrchestratorFeatureClient/CMakeLists.txt create mode 100644 applications/FeatureClients/PresentationOrchestratorFeatureClient/include/acsdk/PresentationOrchestratorFeatureClient/PresentationOrchestratorFeatureClient.h create mode 100644 applications/FeatureClients/PresentationOrchestratorFeatureClient/include/acsdk/PresentationOrchestratorFeatureClient/PresentationOrchestratorFeatureClientBuilder.h create mode 100644 applications/FeatureClients/PresentationOrchestratorFeatureClient/src/CMakeLists.txt create mode 100644 applications/FeatureClients/PresentationOrchestratorFeatureClient/src/PresentationOrchestratorFeatureClient.cpp create mode 100644 applications/FeatureClients/PresentationOrchestratorFeatureClient/src/PresentationOrchestratorFeatureClientBuilder.cpp create mode 100644 applications/FeatureClients/VisualCharacteristicsFeatureClient/CMakeLists.txt create mode 100644 applications/FeatureClients/VisualCharacteristicsFeatureClient/include/acsdk/VisualCharacteristicsFeatureClient/VisualCharacteristicsFeatureClient.h create mode 100644 applications/FeatureClients/VisualCharacteristicsFeatureClient/include/acsdk/VisualCharacteristicsFeatureClient/VisualCharacteristicsFeatureClientBuilder.h create mode 100644 applications/FeatureClients/VisualCharacteristicsFeatureClient/src/CMakeLists.txt create mode 100644 applications/FeatureClients/VisualCharacteristicsFeatureClient/src/VisualCharacteristicsFeatureClient.cpp create mode 100644 applications/FeatureClients/VisualCharacteristicsFeatureClient/src/VisualCharacteristicsFeatureClientBuilder.cpp create mode 100644 applications/FeatureClients/VisualStateTrackerFeatureClient/CMakeLists.txt create mode 100644 applications/FeatureClients/VisualStateTrackerFeatureClient/include/acsdk/VisualStateTrackerFeatureClient/VisualStateTrackerFeatureClient.h create mode 100644 applications/FeatureClients/VisualStateTrackerFeatureClient/include/acsdk/VisualStateTrackerFeatureClient/VisualStateTrackerFeatureClientBuilder.h create mode 100644 applications/FeatureClients/VisualStateTrackerFeatureClient/src/CMakeLists.txt create mode 100644 applications/FeatureClients/VisualStateTrackerFeatureClient/src/VisualStateTrackerFeatureClient.cpp create mode 100644 applications/FeatureClients/VisualStateTrackerFeatureClient/src/VisualStateTrackerFeatureClientBuilder.cpp delete mode 100644 applications/acsdkCBLAuthorizationDelegate/include/acsdkAuthorizationDelegate/AuthorizationDelegateComponent.h delete mode 100644 applications/acsdkCBLAuthorizationDelegate/src/AuthorizationDelegateComponent.cpp delete mode 100644 applications/acsdkCBLAuthorizationDelegate/src/CMakeLists.txt delete mode 100644 applications/acsdkNullSpeechEncoder/include/acsdkSpeechEncoder/SpeechEncoderComponent.h delete mode 100644 applications/acsdkPreviewAlexaClient/CMakeLists.txt delete mode 100644 applications/acsdkPreviewAlexaClient/README.md delete mode 100644 applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClient.h delete mode 100644 applications/acsdkPreviewAlexaClient/include/acsdkPreviewAlexaClient/PreviewAlexaClientComponent.h delete mode 100644 applications/acsdkPreviewAlexaClient/src/CMakeLists.txt delete mode 100644 applications/acsdkPreviewAlexaClient/src/PreviewAlexaClient.cpp delete mode 100644 applications/acsdkPreviewAlexaClient/src/PreviewAlexaClientComponent.cpp create mode 100644 applications/acsdkSampleApplicationInterfaces/include/acsdkSampleApplicationInterfaces/UIAuthNotifierInterface.h create mode 100644 applications/acsdkSampleApplicationInterfaces/include/acsdkSampleApplicationInterfaces/UIStateAggregatorInterface.h delete mode 100644 applications/acsdkSensoryAdapter/CMakeLists.txt delete mode 100644 applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp delete mode 100644 applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp delete mode 100644 applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h delete mode 100644 applications/acsdkSensoryAdapter/src/CMakeLists.txt delete mode 100644 applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp delete mode 100644 applications/acsdkSensoryAdapter/test/CMakeLists.txt delete mode 100644 applications/acsdkSensoryAdapter/test/SensoryKeywordDetectorTest.cpp create mode 100644 capabilities/APLCapabilityCommon/.clang-format create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommon/CMakeLists.txt create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommon/include/acsdk/APLCapabilityCommon/APLPayloadParser.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommon/include/acsdk/APLCapabilityCommon/BaseAPLCapabilityAgent.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommon/src/APLPayloadParser.cpp create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommon/src/BaseAPLCapabilityAgent.cpp create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommon/src/CMakeLists.txt create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommon/test/BaseAPLCapabilityAgentTest.cpp create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommon/test/CMakeLists.txt create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/CMakeLists.txt create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLCapabilityAgentInterface.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLCapabilityAgentNotifierInterface.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLCapabilityAgentObserverInterface.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLCommandExecutionEvent.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLDocumentObserverInterface.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLDocumentSessionInterface.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLEventPayload.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLRuntimeInterface.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLTimeoutType.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/APLVideoConfiguration.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/PresentationOptions.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/PresentationSession.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/PresentationToken.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/include/acsdk/APLCapabilityCommonInterfaces/VisualStateProviderInterface.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/test/CMakeLists.txt create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/test/include/acsdk/APLCapabilityCommonInterfaces/MockAPLCapabilityAgent.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/test/include/acsdk/APLCapabilityCommonInterfaces/MockAPLCapabilityAgentObserver.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/test/include/acsdk/APLCapabilityCommonInterfaces/MockAPLDocumentObserver.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/test/include/acsdk/APLCapabilityCommonInterfaces/MockAPLDocumentSession.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/test/include/acsdk/APLCapabilityCommonInterfaces/MockAPLRuntime.h create mode 100644 capabilities/APLCapabilityCommon/APLCapabilityCommonInterfaces/test/include/acsdk/APLCapabilityCommonInterfaces/MockVisualStateProvider.h create mode 100644 capabilities/APLCapabilityCommon/CMakeLists.txt create mode 100644 capabilities/AlexaChannelController/.clang-format create mode 100644 capabilities/AlexaChannelController/AlexaChannelController/CMakeLists.txt create mode 100644 capabilities/AlexaChannelController/AlexaChannelController/include/acsdk/AlexaChannelController/AlexaChannelControllerFactory.h create mode 100644 capabilities/AlexaChannelController/AlexaChannelController/privateInclude/acsdk/AlexaChannelController/private/AlexaChannelControllerCapabilityAgent.h create mode 100644 capabilities/AlexaChannelController/AlexaChannelController/src/AlexaChannelControllerCapabilityAgent.cpp create mode 100644 capabilities/AlexaChannelController/AlexaChannelController/src/AlexaChannelControllerFactory.cpp create mode 100644 capabilities/AlexaChannelController/AlexaChannelController/src/CMakeLists.txt create mode 100644 capabilities/AlexaChannelController/AlexaChannelController/test/AlexaChannelControllerCapabilityAgentTest.cpp create mode 100644 capabilities/AlexaChannelController/AlexaChannelController/test/CMakeLists.txt create mode 100644 capabilities/AlexaChannelController/AlexaChannelControllerInterfaces/CMakeLists.txt create mode 100644 capabilities/AlexaChannelController/AlexaChannelControllerInterfaces/include/acsdk/AlexaChannelControllerInterfaces/ChannelControllerInterface.h create mode 100644 capabilities/AlexaChannelController/AlexaChannelControllerInterfaces/include/acsdk/AlexaChannelControllerInterfaces/ChannelControllerObserverInterface.h create mode 100644 capabilities/AlexaChannelController/AlexaChannelControllerInterfaces/include/acsdk/AlexaChannelControllerInterfaces/ChannelType.h create mode 100644 capabilities/AlexaChannelController/CMakeLists.txt create mode 100644 capabilities/AlexaKeypadController/.clang-format create mode 100644 capabilities/AlexaKeypadController/CMakeLists.txt create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadController/CMakeLists.txt create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadController/include/acsdkAlexaKeypadController/AlexaKeypadControllerFactory.h create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadController/privateInclude/acsdkAlexaKeypadController/AlexaKeypadControllerCapabilityAgent.h create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadController/src/AlexaKeypadControllerCapabilityAgent.cpp create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadController/src/AlexaKeypadControllerFactory.cpp create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadController/src/CMakeLists.txt create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadController/test/AlexaKeypadControllerCapabilityAgentTest.cpp create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadController/test/CMakeLists.txt create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadControllerInterfaces/CMakeLists.txt create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadControllerInterfaces/include/acsdkAlexaKeypadControllerInterfaces/AlexaKeypadControllerInterface.h create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadControllerInterfaces/include/acsdkAlexaKeypadControllerInterfaces/Keystroke.h create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadControllerInterfaces/src/CMakeLists.txt create mode 100644 capabilities/AlexaKeypadController/acsdkAlexaKeypadControllerInterfaces/src/Keystroke.cpp create mode 100644 capabilities/AlexaLauncher/.clang-format create mode 100644 capabilities/AlexaLauncher/CMakeLists.txt create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncher/CMakeLists.txt create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncher/include/acsdkAlexaLauncher/AlexaLauncherFactory.h create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncher/privateInclude/acsdkAlexaLauncher/AlexaLauncherCapabilityAgent.h create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncher/src/AlexaLauncherCapabilityAgent.cpp create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncher/src/AlexaLauncherFactory.cpp create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncher/src/CMakeLists.txt create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncher/test/AlexaLauncherCapabilityAgentTest.cpp create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncher/test/CMakeLists.txt create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncherInterfaces/CMakeLists.txt create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncherInterfaces/include/acsdkAlexaLauncherInterfaces/AlexaLauncherInterface.h create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncherInterfaces/include/acsdkAlexaLauncherInterfaces/AlexaLauncherObserverInterface.h create mode 100644 capabilities/AlexaLauncher/acsdkAlexaLauncherInterfaces/include/acsdkAlexaLauncherInterfaces/AlexaLauncherTargetState.h create mode 100644 capabilities/AlexaPlaybackController/.clang-format create mode 100644 capabilities/AlexaPlaybackController/CMakeLists.txt create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackController/CMakeLists.txt create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackController/include/acsdkAlexaPlaybackController/AlexaPlaybackControllerFactory.h create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackController/privateInclude/acsdkAlexaPlaybackController/AlexaPlaybackControllerCapabilityAgent.h create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackController/src/AlexaPlaybackControllerCapabilityAgent.cpp create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackController/src/AlexaPlaybackControllerFactory.cpp create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackController/src/CMakeLists.txt create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackController/test/AlexaPlaybackControllerCapabilityAgentTest.cpp create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackController/test/CMakeLists.txt create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackControllerInterfaces/CMakeLists.txt create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackControllerInterfaces/include/acsdkAlexaPlaybackControllerInterfaces/AlexaPlaybackControllerInterface.h create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackControllerInterfaces/include/acsdkAlexaPlaybackControllerInterfaces/AlexaPlaybackControllerObserverInterface.h create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackControllerInterfaces/include/acsdkAlexaPlaybackControllerInterfaces/PlaybackOperation.h create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackControllerInterfaces/include/acsdkAlexaPlaybackControllerInterfaces/PlaybackState.h create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackControllerInterfaces/src/CMakeLists.txt create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackControllerInterfaces/src/PlaybackOperation.cpp create mode 100644 capabilities/AlexaPlaybackController/acsdkAlexaPlaybackControllerInterfaces/src/PlaybackState.cpp create mode 100644 capabilities/AlexaPresentation/.clang-format create mode 100644 capabilities/AlexaPresentation/AlexaPresentation/CMakeLists.txt create mode 100644 capabilities/AlexaPresentation/AlexaPresentation/include/acsdk/AlexaPresentation/AlexaPresentationFactory.h create mode 100644 capabilities/AlexaPresentation/AlexaPresentation/privateInclude/acsdk/AlexaPresentation/private/AlexaPresentation.h create mode 100644 capabilities/AlexaPresentation/AlexaPresentation/src/AlexaPresentation.cpp create mode 100644 capabilities/AlexaPresentation/AlexaPresentation/src/AlexaPresentationFactory.cpp create mode 100644 capabilities/AlexaPresentation/AlexaPresentation/src/CMakeLists.txt create mode 100644 capabilities/AlexaPresentation/AlexaPresentation/test/AlexaPresentationTest.cpp create mode 100644 capabilities/AlexaPresentation/AlexaPresentation/test/CMakeLists.txt create mode 100644 capabilities/AlexaPresentation/AlexaPresentationInterfaces/CMakeLists.txt create mode 100644 capabilities/AlexaPresentation/AlexaPresentationInterfaces/include/acsdk/AlexaPresentationInterfaces/AlexaPresentationCapabilityAgentInterface.h create mode 100644 capabilities/AlexaPresentation/AlexaPresentationInterfaces/include/acsdkAlexaPresentationInterfaces/APLDocumentObserverInterface.h create mode 100644 capabilities/AlexaPresentation/AlexaPresentationInterfaces/include/acsdkAlexaPresentationInterfaces/APLDocumentSessionInterface.h create mode 100644 capabilities/AlexaPresentation/AlexaPresentationInterfaces/include/acsdkAlexaPresentationInterfaces/APLRuntimeInterface.h create mode 100644 capabilities/AlexaPresentation/AlexaPresentationInterfaces/include/acsdkAlexaPresentationInterfaces/PresentationOptions.h create mode 100644 capabilities/AlexaPresentation/AlexaPresentationInterfaces/include/acsdkAlexaPresentationInterfaces/PresentationSession.h create mode 100644 capabilities/AlexaPresentation/AlexaPresentationInterfaces/test/CMakeLists.txt create mode 100644 capabilities/AlexaPresentation/AlexaPresentationInterfaces/test/include/acsdk/AlexaPresentationInterfaces/MockAlexaPresentationCapabilityAgent.h create mode 100644 capabilities/AlexaPresentation/CMakeLists.txt create mode 100644 capabilities/AlexaPresentationAPL/.clang-format create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/CMakeLists.txt create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/include/acsdk/AlexaPresentationAPL/AlexaPresentationAPLFactory.h create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/privateInclude/acsdk/AlexaPresentationAPL/private/AlexaPresentationAPL.h create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/privateInclude/acsdk/AlexaPresentationAPL/private/AlexaPresentationAPLVideoConfigParser.h create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/src/AlexaPresentationAPL.cpp create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/src/AlexaPresentationAPLFactory.cpp create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/src/AlexaPresentationAPLVideoConfigParser.cpp create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/src/CMakeLists.txt create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/test/AlexaPresentationAPLVideoConfigParserTest.cpp create mode 100644 capabilities/AlexaPresentationAPL/AlexaPresentationAPL/test/CMakeLists.txt create mode 100644 capabilities/AlexaPresentationAPL/CMakeLists.txt create mode 100644 capabilities/AlexaRecordController/.clang-format rename {SampleApp/Authorization/CBLAuthDelegate => capabilities/AlexaRecordController/AlexaRecordController}/CMakeLists.txt (62%) create mode 100644 capabilities/AlexaRecordController/AlexaRecordController/include/acsdk/AlexaRecordController/AlexaRecordControllerFactory.h create mode 100644 capabilities/AlexaRecordController/AlexaRecordController/privateInclude/acsdk/AlexaRecordController/private/AlexaRecordControllerCapabilityAgent.h create mode 100644 capabilities/AlexaRecordController/AlexaRecordController/src/AlexaRecordControllerCapabilityAgent.cpp create mode 100644 capabilities/AlexaRecordController/AlexaRecordController/src/AlexaRecordControllerFactory.cpp create mode 100644 capabilities/AlexaRecordController/AlexaRecordController/src/CMakeLists.txt create mode 100644 capabilities/AlexaRecordController/AlexaRecordController/test/AlexaRecordControllerCapabilityAgentTest.cpp create mode 100644 capabilities/AlexaRecordController/AlexaRecordController/test/CMakeLists.txt create mode 100644 capabilities/AlexaRecordController/AlexaRecordControllerInterfaces/CMakeLists.txt create mode 100644 capabilities/AlexaRecordController/AlexaRecordControllerInterfaces/include/acsdk/AlexaRecordControllerInterfaces/RecordControllerInterface.h create mode 100644 capabilities/AlexaRecordController/CMakeLists.txt create mode 100644 capabilities/AlexaRemoteVideoPlayer/.clang-format rename {SpeechEncoder/OpusEncoderContext => capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayer}/CMakeLists.txt (61%) create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayer/include/acsdk/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayerFactory.h create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayer/privateInclude/acsdk/AlexaRemoteVideoPlayer/private/AlexaRemoteVideoPlayerCapabilityAgent.h create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayer/src/AlexaRemoteVideoPlayerCapabilityAgent.cpp create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayer/src/AlexaRemoteVideoPlayerFactory.cpp create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayer/src/CMakeLists.txt create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayer/test/AlexaRemoteVideoPlayerCapabilityAgentTest.cpp create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayer/test/CMakeLists.txt create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayerInterfaces/CMakeLists.txt create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayerInterfaces/include/acsdk/AlexaRemoteVideoPlayerInterfaces/RemoteVideoPlayerConfiguration.h create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayerInterfaces/include/acsdk/AlexaRemoteVideoPlayerInterfaces/RemoteVideoPlayerInterface.h create mode 100644 capabilities/AlexaRemoteVideoPlayer/AlexaRemoteVideoPlayerInterfaces/include/acsdk/AlexaRemoteVideoPlayerInterfaces/RemoteVideoPlayerTypes.h create mode 100644 capabilities/AlexaRemoteVideoPlayer/CMakeLists.txt create mode 100644 capabilities/AlexaSeekController/.clang-format create mode 100644 capabilities/AlexaSeekController/CMakeLists.txt create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekController/CMakeLists.txt create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekController/include/acsdkAlexaSeekController/AlexaSeekControllerFactory.h create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekController/privateInclude/acsdkAlexaSeekController/AlexaSeekControllerCapabilityAgent.h create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekController/src/AlexaSeekControllerCapabilityAgent.cpp create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekController/src/AlexaSeekControllerFactory.cpp create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekController/src/CMakeLists.txt create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekController/test/AlexaSeekControllerCapabilityAgentTest.cpp create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekController/test/CMakeLists.txt create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekControllerInterfaces/CMakeLists.txt create mode 100644 capabilities/AlexaSeekController/acsdkAlexaSeekControllerInterfaces/include/acsdkAlexaSeekControllerInterfaces/AlexaSeekControllerInterface.h create mode 100644 capabilities/AlexaVideoCommon/.clang-format create mode 100644 capabilities/AlexaVideoCommon/CMakeLists.txt rename {Integration => capabilities/AlexaVideoCommon/VideoContent}/CMakeLists.txt (78%) create mode 100644 capabilities/AlexaVideoCommon/VideoContent/include/acsdk/VideoContent/VideoEntityTypes.h create mode 100644 capabilities/AlexaVideoCommon/VideoContent/src/CMakeLists.txt create mode 100644 capabilities/AlexaVideoCommon/VideoContent/src/VideoEntityTypes.cpp create mode 100644 capabilities/AlexaVideoCommon/VideoContent/test/CMakeLists.txt create mode 100644 capabilities/AlexaVideoCommon/VideoContent/test/VideoEntityTypesTest.cpp create mode 100644 capabilities/AlexaVideoRecorder/.clang-format create mode 100644 capabilities/AlexaVideoRecorder/CMakeLists.txt create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorder/CMakeLists.txt create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorder/include/acsdkAlexaVideoRecorder/AlexaVideoRecorderFactory.h create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorder/privateInclude/acsdkAlexaVideoRecorder/AlexaVideoRecorderCapabilityAgent.h create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorder/src/AlexaVideoRecorderCapabilityAgent.cpp create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorder/src/AlexaVideoRecorderFactory.cpp create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorder/src/CMakeLists.txt create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorder/test/AlexaVideoRecorderCapabilityAgentTest.cpp create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorder/test/CMakeLists.txt create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorderInterfaces/CMakeLists.txt create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorderInterfaces/include/acsdkAlexaVideoRecorderInterfaces/VideoRecorderInterface.h create mode 100644 capabilities/AlexaVideoRecorder/acsdkAlexaVideoRecorderInterfaces/include/acsdkAlexaVideoRecorderInterfaces/VideoRecorderTypes.h create mode 100644 capabilities/AudioPlayer/acsdkAudioPlayer/test/UtilsTest.cpp create mode 100644 capabilities/DeviceSetup/acsdkDeviceSetup/include/acsdkDeviceSetup/DeviceSetupFactory.h create mode 100644 capabilities/DeviceSetup/acsdkDeviceSetup/src/DeviceSetupFactory.cpp create mode 100644 capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayer/test/acsdkExternalMediaPlayer/MockExternalMediaAdapterHandler.h create mode 100644 capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/test/CMakeLists.txt create mode 100644 capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/test/acsdkExternalMediaPlayerInterfaces/MockExternalMediaPlayer.h create mode 100644 capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/test/acsdkExternalMediaPlayerInterfaces/MockExternalMediaPlayerAdapter.h create mode 100644 capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/test/acsdkExternalMediaPlayerInterfaces/MockExternalMediaPlayerObserver.h create mode 100644 capabilities/ExternalMediaPlayer/acsdkExternalMediaPlayerInterfaces/test/acsdkExternalMediaPlayerInterfaces/MockRenderPlayerInfoCardsProviderRegistrar.h create mode 100644 capabilities/InputController/AlexaInputController/CMakeLists.txt create mode 100644 capabilities/InputController/AlexaInputController/include/acsdk/AlexaInputController/InputControllerFactory.h create mode 100644 capabilities/InputController/AlexaInputController/privateInclude/acsdk/AlexaInputController/private/InputControllerCapabilityAgent.h create mode 100644 capabilities/InputController/AlexaInputController/src/CMakeLists.txt create mode 100644 capabilities/InputController/AlexaInputController/src/InputControllerCapabilityAgent.cpp create mode 100644 capabilities/InputController/AlexaInputController/src/InputControllerFactory.cpp create mode 100644 capabilities/InputController/AlexaInputController/test/CMakeLists.txt create mode 100644 capabilities/InputController/AlexaInputController/test/InputControllerCapabilityAgentTest.cpp create mode 100644 capabilities/InputController/AlexaInputControllerInterfaces/CMakeLists.txt create mode 100644 capabilities/InputController/AlexaInputControllerInterfaces/include/acsdk/AlexaInputControllerInterfaces/InputControllerInterface.h create mode 100644 capabilities/InputController/AlexaInputControllerInterfaces/include/acsdk/AlexaInputControllerInterfaces/InputControllerObserverInterface.h create mode 100644 capabilities/InputController/AlexaInputControllerInterfaces/include/acsdk/AlexaInputControllerInterfaces/InputType.h create mode 100644 capabilities/InputController/AlexaInputControllerInterfaces/src/CMakeLists.txt create mode 100644 capabilities/InputController/AlexaInputControllerInterfaces/src/InputType.cpp delete mode 100644 capabilities/InputController/acsdkInputController/CMakeLists.txt delete mode 100644 capabilities/InputController/acsdkInputController/include/acsdkInputController/InputControllerFactory.h delete mode 100644 capabilities/InputController/acsdkInputController/src/CMakeLists.txt delete mode 100644 capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.cpp delete mode 100644 capabilities/InputController/acsdkInputController/src/InputControllerCapabilityAgent.h delete mode 100644 capabilities/InputController/acsdkInputController/src/InputControllerFactory.cpp delete mode 100644 capabilities/InputController/acsdkInputController/test/CMakeLists.txt delete mode 100644 capabilities/InputController/acsdkInputController/test/InputControllerCapabilityAgentTest.cpp delete mode 100644 capabilities/InputController/acsdkInputControllerInterfaces/CMakeLists.txt delete mode 100644 capabilities/InputController/acsdkInputControllerInterfaces/include/acsdkInputControllerInterfaces/InputControllerHandlerInterface.h create mode 100644 capabilities/LiveViewController/.clang-format create mode 100644 capabilities/LiveViewController/AlexaLiveViewController/CMakeLists.txt create mode 100644 capabilities/LiveViewController/AlexaLiveViewController/include/acsdk/AlexaLiveViewController/AlexaLiveViewControllerFactory.h create mode 100644 capabilities/LiveViewController/AlexaLiveViewController/privateInclude/acsdk/AlexaLiveViewController/private/AlexaLiveViewControllerCapabilityAgent.h create mode 100644 capabilities/LiveViewController/AlexaLiveViewController/src/AlexaLiveViewControllerCapabilityAgent.cpp create mode 100644 capabilities/LiveViewController/AlexaLiveViewController/src/AlexaLiveViewControllerFactory.cpp create mode 100644 capabilities/LiveViewController/AlexaLiveViewController/src/CMakeLists.txt create mode 100644 capabilities/LiveViewController/AlexaLiveViewController/test/AlexaLiveViewControllerCapabilityAgentTest.cpp create mode 100644 capabilities/LiveViewController/AlexaLiveViewController/test/CMakeLists.txt create mode 100644 capabilities/LiveViewController/AlexaLiveViewControllerInterfaces/CMakeLists.txt create mode 100644 capabilities/LiveViewController/AlexaLiveViewControllerInterfaces/include/acsdk/AlexaLiveViewControllerInterfaces/LiveViewControllerConfiguration.h create mode 100644 capabilities/LiveViewController/AlexaLiveViewControllerInterfaces/include/acsdk/AlexaLiveViewControllerInterfaces/LiveViewControllerInterface.h create mode 100644 capabilities/LiveViewController/AlexaLiveViewControllerInterfaces/include/acsdk/AlexaLiveViewControllerInterfaces/LiveViewControllerObserverInterface.h create mode 100644 capabilities/LiveViewController/AlexaLiveViewControllerInterfaces/include/acsdk/AlexaLiveViewControllerInterfaces/LiveViewControllerTypes.h create mode 100644 capabilities/LiveViewController/CMakeLists.txt create mode 100644 capabilities/TemplateRuntime/.clang-format create mode 100644 capabilities/TemplateRuntime/CMakeLists.txt create mode 100644 capabilities/TemplateRuntime/TemplateRuntime/CMakeLists.txt create mode 100644 capabilities/TemplateRuntime/TemplateRuntime/include/acsdk/TemplateRuntime/RenderPlayerInfoCardsProviderRegistrarFactory.h create mode 100644 capabilities/TemplateRuntime/TemplateRuntime/include/acsdk/TemplateRuntime/TemplateRuntimeFactory.h rename {CapabilityAgents/TemplateRuntime/include/TemplateRuntime => capabilities/TemplateRuntime/TemplateRuntime/privateInclude/acsdk/TemplateRuntime/private}/RenderPlayerInfoCardsProviderRegistrar.h (68%) rename {CapabilityAgents/TemplateRuntime/include/TemplateRuntime => capabilities/TemplateRuntime/TemplateRuntime/privateInclude/acsdk/TemplateRuntime/private}/TemplateRuntime.h (56%) create mode 100644 capabilities/TemplateRuntime/TemplateRuntime/src/CMakeLists.txt rename {CapabilityAgents => capabilities/TemplateRuntime}/TemplateRuntime/src/RenderPlayerInfoCardsProviderRegistrar.cpp (75%) create mode 100644 capabilities/TemplateRuntime/TemplateRuntime/src/RenderPlayerInfoCardsProviderRegistrarFactory.cpp create mode 100644 capabilities/TemplateRuntime/TemplateRuntime/src/TemplateRuntime.cpp create mode 100644 capabilities/TemplateRuntime/TemplateRuntime/src/TemplateRuntimeFactory.cpp create mode 100644 capabilities/TemplateRuntime/TemplateRuntime/test/CMakeLists.txt rename {CapabilityAgents => capabilities/TemplateRuntime}/TemplateRuntime/test/RenderPlayerInfoCardsProviderRegistrarTest.cpp (69%) rename {CapabilityAgents => capabilities/TemplateRuntime}/TemplateRuntime/test/TemplateRuntimeTest.cpp (67%) create mode 100644 capabilities/TemplateRuntime/TemplateRuntimeInterfaces/CMakeLists.txt create mode 100644 capabilities/TemplateRuntime/TemplateRuntimeInterfaces/include/acsdk/TemplateRuntimeInterfaces/TemplateRuntimeInterface.h rename {AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces => capabilities/TemplateRuntime/TemplateRuntimeInterfaces/include/acsdk/TemplateRuntimeInterfaces}/TemplateRuntimeObserverInterface.h (61%) create mode 100644 capabilities/VisualCharacteristics/.clang-format rename {SampleApp/Authorization => capabilities/VisualCharacteristics}/CMakeLists.txt (51%) create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/CMakeLists.txt create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/include/acsdk/VisualCharacteristics/VisualCharacteristicsFactory.h create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/include/acsdk/VisualCharacteristics/VisualCharacteristicsSerializerFactory.h create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/privateInclude/acsdk/VisualCharacteristics/private/VCConfigParser.h create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/privateInclude/acsdk/VisualCharacteristics/private/VisualCharacteristics.h create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/privateInclude/acsdk/VisualCharacteristics/private/VisualCharacteristicsSerializer.h create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/src/CMakeLists.txt create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/src/VCConfigParser.cpp create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/src/VisualCharacteristics.cpp create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/src/VisualCharacteristicsFactory.cpp create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/src/VisualCharacteristicsSerializer.cpp create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/src/VisualCharacteristicsSerializerFactory.cpp create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/test/CMakeLists.txt create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/test/VCConfigParserTest.cpp create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristics/test/VisualCharacteristicsTest.cpp create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristicsInterfaces/CMakeLists.txt create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristicsInterfaces/include/acsdk/VisualCharacteristicsInterfaces/VisualCharacteristicsInterface.h create mode 100644 capabilities/VisualCharacteristics/VisualCharacteristicsInterfaces/include/acsdk/VisualCharacteristicsInterfaces/VisualCharacteristicsSerializerInterface.h create mode 100644 cmakeBuild/cmake/EndpointVideoControllers.cmake delete mode 100644 cmakeBuild/cmake/InputController.cmake create mode 100644 cmakeBuild/cmake/Opus.cmake create mode 100644 cmakeBuild/cmake/SampleApplications.cmake delete mode 100644 cmakeBuild/cmake/SpeechEncoder.cmake rename {CapabilityAgents/TemplateRuntime => core/AudioEncoder/AudioEncoder}/CMakeLists.txt (70%) create mode 100644 core/AudioEncoder/AudioEncoder/include/acsdk/AudioEncoder/AudioEncoderFactory.h create mode 100644 core/AudioEncoder/AudioEncoder/include/acsdk/AudioEncoder/AudioEncoderParams.h create mode 100644 core/AudioEncoder/AudioEncoder/privateInclude/acsdk/AudioEncoder/private/AudioEncoder.h create mode 100644 core/AudioEncoder/AudioEncoder/privateInclude/acsdk/AudioEncoder/private/AudioEncoderState.h create mode 100644 core/AudioEncoder/AudioEncoder/src/AudioEncoder.cpp create mode 100644 core/AudioEncoder/AudioEncoder/src/AudioEncoderFactory.cpp create mode 100644 core/AudioEncoder/AudioEncoder/src/AudioEncoderParams.cpp create mode 100644 core/AudioEncoder/AudioEncoder/src/AudioEncoderState.cpp create mode 100644 core/AudioEncoder/AudioEncoder/src/CMakeLists.txt rename SpeechEncoder/test/SpeechEncoderTest.cpp => core/AudioEncoder/AudioEncoder/test/AudioEncoderTest.cpp (64%) create mode 100644 core/AudioEncoder/AudioEncoder/test/CMakeLists.txt create mode 100644 core/AudioEncoder/AudioEncoderComponent/CMakeLists.txt rename applications/acsdkOpusSpeechEncoder/include/acsdkSpeechEncoder/SpeechEncoderComponent.h => core/AudioEncoder/AudioEncoderComponent/include/acsdk/AudioEncoderComponent/ComponentFactory.h (54%) create mode 100644 core/AudioEncoder/AudioEncoderInterfaces/CMakeLists.txt create mode 100644 core/AudioEncoder/AudioEncoderInterfaces/include/acsdk/AudioEncoderInterfaces/AudioEncoderInterface.h create mode 100644 core/AudioEncoder/AudioEncoderInterfaces/include/acsdk/AudioEncoderInterfaces/BlockAudioEncoderInterface.h create mode 100644 core/AudioEncoder/CMakeLists.txt create mode 100644 core/AudioEncoder/OpusAudioEncoder/CMakeLists.txt create mode 100644 core/AudioEncoder/OpusAudioEncoder/include/acsdk/OpusAudioEncoder/AudioEncoderFactory.h create mode 100644 core/AudioEncoder/OpusAudioEncoder/privateInclude/acsdk/OpusAudioEncoder/private/OpusAudioEncoder.h create mode 100644 core/AudioEncoder/OpusAudioEncoder/src/AudioEncoderFactory.cpp create mode 100644 core/AudioEncoder/OpusAudioEncoder/src/CMakeLists.txt rename SpeechEncoder/OpusEncoderContext/src/OpusEncoderContext.cpp => core/AudioEncoder/OpusAudioEncoder/src/OpusAudioEncoder.cpp (62%) rename core/{acsdkCodecUtils => CodecUtils}/CMakeLists.txt (100%) rename core/{acsdkCodecUtils => CodecUtils}/doc/CodecUtils.dox (78%) rename core/{acsdkCodecUtils/include/acsdkCodecUtils => CodecUtils/include/acsdk/CodecUtils}/Base64.h (91%) rename core/{acsdkCodecUtils/include/acsdkCodecUtils => CodecUtils/include/acsdk/CodecUtils}/Hex.h (91%) rename core/{acsdkCodecUtils/include/acsdkCodecUtils => CodecUtils/include/acsdk/CodecUtils}/Types.h (83%) rename core/{acsdkCodecUtils/privateInclude/acsdkCodecUtils => CodecUtils/privateInclude/acsdk/CodecUtils}/private/Base64Common.h (83%) rename core/{acsdkCodecUtils/privateInclude/acsdkCodecUtils => CodecUtils/privateInclude/acsdk/CodecUtils}/private/CodecsCommon.h (83%) rename core/{acsdkCodecUtils => CodecUtils}/src/Base64Common.cpp (93%) rename core/{acsdkCodecUtils => CodecUtils}/src/Base64Internal.cpp (97%) rename core/{acsdkCodecUtils => CodecUtils}/src/Base64OpenSsl.cpp (89%) rename core/{acsdkCodecUtils => CodecUtils}/src/CMakeLists.txt (100%) rename core/{acsdkCodecUtils => CodecUtils}/src/CodecsCommon.cpp (89%) rename core/{acsdkCodecUtils => CodecUtils}/src/Hex.cpp (93%) rename core/{acsdkCodecUtils => CodecUtils}/test/Base64CodecTest.cpp (97%) rename core/{acsdkCodecUtils => CodecUtils}/test/Base64InternalCodecTest.cpp (98%) rename core/{acsdkCodecUtils => CodecUtils}/test/CMakeLists.txt (100%) rename core/{acsdkCodecUtils => CodecUtils}/test/HexCodecTest.cpp (98%) rename core/Crypto/{acsdkCrypto => Crypto}/CMakeLists.txt (100%) rename core/Crypto/{acsdkCrypto/doc/CryptoIMPL.dox => Crypto/doc/Crypto.dox} (58%) rename core/Crypto/{acsdkCrypto/include/acsdkCrypto => Crypto/include/acsdk/Crypto}/CryptoFactory.h (68%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Crypto/privateInclude/acsdk/Crypto}/private/Logging.h (85%) rename core/Crypto/{acsdkCrypto/privateInclude/acsdkCrypto => Crypto/privateInclude/acsdk/Crypto}/private/OpenSslCryptoCodec.h (87%) rename core/Crypto/{acsdkCrypto/privateInclude/acsdkCrypto => Crypto/privateInclude/acsdk/Crypto}/private/OpenSslCryptoFactory.h (53%) rename core/Crypto/{acsdkCrypto/privateInclude/acsdkCrypto => Crypto/privateInclude/acsdk/Crypto}/private/OpenSslDigest.h (83%) rename core/Crypto/{acsdkCrypto/privateInclude/acsdkCrypto => Crypto/privateInclude/acsdk/Crypto}/private/OpenSslErrorCleanup.h (88%) rename core/Crypto/{acsdkCrypto/privateInclude/acsdkCrypto => Crypto/privateInclude/acsdk/Crypto}/private/OpenSslKeyFactory.h (79%) rename core/Crypto/{acsdkCrypto/privateInclude/acsdkCrypto => Crypto/privateInclude/acsdk/Crypto}/private/OpenSslTypeMapper.h (82%) rename core/Crypto/{acsdkCrypto/privateInclude/acsdkCrypto => Crypto/privateInclude/acsdk/Crypto}/private/OpenSslTypes.h (79%) rename core/Crypto/{acsdkCrypto => Crypto}/src/CMakeLists.txt (100%) rename core/Crypto/{acsdkCrypto => Crypto}/src/CryptoFactory.cpp (69%) rename core/Crypto/{acsdkCrypto => Crypto}/src/OpenSslCryptoCodec.cpp (89%) rename core/Crypto/{acsdkCrypto => Crypto}/src/OpenSslCryptoFactory.cpp (81%) rename core/Crypto/{acsdkCrypto => Crypto}/src/OpenSslDigest.cpp (92%) rename core/Crypto/{acsdkCrypto => Crypto}/src/OpenSslErrorCleanup.cpp (92%) rename core/Crypto/{acsdkCrypto => Crypto}/src/OpenSslKeyFactory.cpp (85%) rename core/Crypto/{acsdkCrypto => Crypto}/src/OpenSslTypeMapper.cpp (91%) rename core/Crypto/{acsdkCrypto => Crypto}/src/OpenSslTypes.cpp (76%) rename core/Crypto/{acsdkCrypto => Crypto}/test/CMakeLists.txt (100%) rename core/Crypto/{acsdkCrypto => Crypto}/test/OpenSslCryptoCodecAEADTest.cpp (97%) rename core/Crypto/{acsdkCrypto => Crypto}/test/OpenSslCryptoCodecTest.cpp (97%) rename core/Crypto/{acsdkCrypto => Crypto}/test/OpenSslCryptoFactoryTest.cpp (93%) create mode 100644 core/Crypto/Crypto/test/OpenSslDigestTest.cpp rename core/Crypto/{acsdkCrypto => Crypto}/test/OpenSslKeyFactoryTest.cpp (95%) rename core/Crypto/{acsdkCrypto => Crypto}/test/OpenSslTypeMapperTest.cpp (93%) rename core/Crypto/{acsdkCryptoInterfaces => CryptoInterfaces}/CMakeLists.txt (100%) rename core/Crypto/{acsdkCryptoInterfaces/doc/CryptoAPI.dox => CryptoInterfaces/doc/CryptoInterfaces.dox} (61%) rename core/Crypto/{acsdkCryptoInterfaces => CryptoInterfaces}/doc/Namespaces.dox (87%) rename core/Crypto/{acsdkCryptoInterfaces/include/acsdkCryptoInterfaces => CryptoInterfaces/include/acsdk/CryptoInterfaces}/AlgorithmType.h (88%) rename core/Crypto/{acsdkCryptoInterfaces/include/acsdkCryptoInterfaces => CryptoInterfaces/include/acsdk/CryptoInterfaces}/CryptoCodecInterface.h (98%) rename core/Crypto/{acsdkCryptoInterfaces/include/acsdkCryptoInterfaces => CryptoInterfaces/include/acsdk/CryptoInterfaces}/CryptoFactoryInterface.h (89%) rename core/Crypto/{acsdkCryptoInterfaces/include/acsdkCryptoInterfaces => CryptoInterfaces/include/acsdk/CryptoInterfaces}/DigestInterface.h (96%) rename core/Crypto/{acsdkCryptoInterfaces/include/acsdkCryptoInterfaces => CryptoInterfaces/include/acsdk/CryptoInterfaces}/DigestType.h (74%) rename core/Crypto/{acsdkCryptoInterfaces/include/acsdkCryptoInterfaces => CryptoInterfaces/include/acsdk/CryptoInterfaces}/KeyFactoryInterface.h (89%) rename core/Crypto/{acsdkCryptoInterfaces/include/acsdkCryptoInterfaces => CryptoInterfaces/include/acsdk/CryptoInterfaces}/KeyStoreInterface.h (97%) rename core/Crypto/{acsdkCryptoInterfaces => CryptoInterfaces}/src/AlgorithmType.cpp (92%) rename core/Crypto/{acsdkCryptoInterfaces => CryptoInterfaces}/src/CMakeLists.txt (100%) rename core/Crypto/{acsdkCryptoInterfaces => CryptoInterfaces}/src/DigestType.cpp (83%) rename core/Crypto/{acsdkCryptoInterfaces => CryptoInterfaces}/test/CMakeLists.txt (100%) rename core/{Properties/acsdkPropertiesInterfaces => Crypto/CryptoInterfaces}/test/doc/Namespaces.dox (78%) create mode 100644 core/Crypto/CryptoInterfaces/test/include/acsdk/CryptoInterfaces/test/MockCryptoCodec.h create mode 100644 core/Crypto/CryptoInterfaces/test/include/acsdk/CryptoInterfaces/test/MockCryptoFactory.h create mode 100644 core/Crypto/CryptoInterfaces/test/include/acsdk/CryptoInterfaces/test/MockDigest.h create mode 100644 core/Crypto/CryptoInterfaces/test/include/acsdk/CryptoInterfaces/test/MockKeyFactory.h create mode 100644 core/Crypto/CryptoInterfaces/test/include/acsdk/CryptoInterfaces/test/MockKeyStore.h rename core/Crypto/{acsdkPkcs11 => Pkcs11}/CMakeLists.txt (100%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/doc/CryptoPKCS11.dox (81%) rename core/Crypto/{acsdkPkcs11/include/acsdkPkcs11 => Pkcs11/include/acsdk/Pkcs11}/KeyStoreFactory.h (76%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Pkcs11/privateInclude/acsdk/Pkcs11}/private/ErrorCleanupGuard.h (91%) rename core/Crypto/{acsdkCrypto/privateInclude/acsdkCrypto => Pkcs11/privateInclude/acsdk/Pkcs11}/private/Logging.h (85%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Pkcs11/privateInclude/acsdk/Pkcs11}/private/PKCS11API.h (88%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Pkcs11/privateInclude/acsdk/Pkcs11}/private/PKCS11Config.h (92%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Pkcs11/privateInclude/acsdk/Pkcs11}/private/PKCS11Functions.h (92%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Pkcs11/privateInclude/acsdk/Pkcs11}/private/PKCS11Key.h (92%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Pkcs11/privateInclude/acsdk/Pkcs11}/private/PKCS11KeyDescriptor.h (77%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Pkcs11/privateInclude/acsdk/Pkcs11}/private/PKCS11KeyStore.h (86%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Pkcs11/privateInclude/acsdk/Pkcs11}/private/PKCS11Session.h (87%) rename core/Crypto/{acsdkPkcs11/privateInclude/acsdkPkcs11 => Pkcs11/privateInclude/acsdk/Pkcs11}/private/PKCS11Slot.h (86%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/CMakeLists.txt (100%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/KeyStoreFactory.cpp (78%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/PKCS11Config.cpp (92%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/PKCS11Functions.cpp (94%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/PKCS11FunctionsPosix.cpp (90%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/PKCS11FunctionsUwp.cpp (92%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/PKCS11Key.cpp (97%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/PKCS11KeyDescriptor.cpp (79%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/PKCS11KeyStore.cpp (96%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/PKCS11Session.cpp (92%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/src/PKCS11Slot.cpp (88%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/test/CMakeLists.txt (100%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/test/ErrorCleanupGuardTest.cpp (92%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/test/PKCS11ConfigTest.cpp (94%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/test/PKCS11FunctionsTest.cpp (92%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/test/PKCS11KeyStoreTest.cpp (94%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/test/PKCS11KeyTest.cpp (94%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/test/PKCS11SessionTest.cpp (86%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/test/PKCS11SlotTest.cpp (89%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/testStubs/CMakeLists.txt (100%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/testStubs/doc/CryptoPKCS11Stubs.dox (82%) rename core/Crypto/{acsdkPkcs11 => Pkcs11}/testStubs/src/Pkcs11Stubs.cpp (98%) delete mode 100644 core/Crypto/acsdkCrypto/test/OpenSslDigestTest.cpp delete mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoCodec.h delete mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockCryptoFactory.h delete mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockDigest.h delete mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyFactory.h delete mode 100644 core/Crypto/acsdkCryptoInterfaces/test/include/acsdkCryptoInterfaces/test/MockKeyStore.h rename core/Properties/{acsdkProperties => Properties}/CMakeLists.txt (100%) rename core/Properties/{acsdkProperties => Properties}/doc/Namespaces.dox (75%) rename core/Properties/{acsdkProperties/doc/PropertiesIMPL.dox => Properties/doc/acsdkProperties.dox} (68%) rename core/Properties/{acsdkProperties/include/acsdkProperties => Properties/include/acsdk/Properties}/EncryptedPropertiesFactories.h (82%) rename core/Properties/{acsdkProperties/include/acsdkProperties => Properties/include/acsdk/Properties}/ErrorCallbackInterface.h (94%) rename core/Properties/{acsdkProperties/include/acsdkProperties => Properties/include/acsdk/Properties}/ErrorCallbackSetter.h (90%) rename core/Properties/{acsdkProperties/include/acsdkProperties => Properties/include/acsdk/Properties}/MiscStorageAdapter.h (87%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/Asn1Helper.h (93%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/Asn1Types.h (94%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/DataPropertyCodec.h (77%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/DataPropertyCodecState.h (89%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/EncryptedProperties.h (73%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/EncryptedPropertiesFactory.h (55%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/EncryptionKeyPropertyCodec.h (69%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/EncryptionKeyPropertyCodecState.h (89%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/Logging.h (82%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/MiscStorageProperties.h (87%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/MiscStoragePropertiesFactory.h (80%) rename core/Properties/{acsdkProperties/privateInclude/acsdkProperties => Properties/privateInclude/acsdk/Properties}/private/RetryExecutor.h (96%) rename core/Properties/{acsdkProperties => Properties}/src/Asn1Helper.cpp (94%) rename core/Properties/{acsdkProperties => Properties}/src/Asn1Types.cpp (95%) rename core/Properties/{acsdkProperties => Properties}/src/CMakeLists.txt (100%) rename core/Properties/{acsdkProperties => Properties}/src/DataPropertyCodec.cpp (94%) rename core/Properties/{acsdkProperties => Properties}/src/DataPropertyCodecState.cpp (92%) rename core/Properties/{acsdkProperties => Properties}/src/EncryptedProperties.cpp (98%) rename core/Properties/{acsdkProperties => Properties}/src/EncryptedPropertiesFactories.cpp (72%) rename core/Properties/{acsdkProperties => Properties}/src/EncryptedPropertiesFactory.cpp (84%) rename core/Properties/{acsdkProperties => Properties}/src/EncryptionKeyPropertyCodec.cpp (89%) rename core/Properties/{acsdkProperties => Properties}/src/EncryptionKeyPropertyCodecState.cpp (94%) rename core/Properties/{acsdkProperties => Properties}/src/ErrorCallbackSetter.cpp (84%) rename core/Properties/{acsdkProperties => Properties}/src/Logging.cpp (85%) rename core/Properties/{acsdkProperties => Properties}/src/MiscStorageAdapter.cpp (81%) rename core/Properties/{acsdkProperties => Properties}/src/MiscStorageProperties.cpp (97%) rename core/Properties/{acsdkProperties => Properties}/src/MiscStoragePropertiesFactory.cpp (90%) rename core/Properties/{acsdkProperties => Properties}/src/RetryExecutor.cpp (96%) rename core/Properties/{acsdkProperties => Properties}/src/SimpleMiscStorageUriMapper.cpp (88%) rename core/Properties/{acsdkProperties => Properties}/test/CMakeLists.txt (100%) rename core/Properties/{acsdkProperties => Properties}/test/MiscStoragePropertiesFactoryTest.cpp (97%) rename core/Properties/{acsdkProperties => Properties}/test/MiscStoragePropertiesTest.cpp (98%) rename core/Properties/{acsdkProperties => Properties}/testCrypto/CMakeLists.txt (100%) rename core/Properties/{acsdkProperties => Properties}/testCrypto/DataPropertyCodecTest.cpp (78%) rename core/Properties/{acsdkProperties => Properties}/testCrypto/EncryptedPropertiesFactoryTest.cpp (82%) rename core/Properties/{acsdkProperties => Properties}/testCrypto/EncryptedPropertiesTest.cpp (88%) rename core/Properties/{acsdkProperties => Properties}/testCrypto/EncryptionKeyPropertyCodecTest.cpp (81%) rename core/Properties/{acsdkPropertiesInterfaces => PropertiesInterfaces}/CMakeLists.txt (100%) rename core/Properties/{acsdkPropertiesInterfaces => PropertiesInterfaces}/doc/Namespaces.dox (87%) rename core/Properties/{acsdkPropertiesInterfaces/doc/PropertiesAPI.dox => PropertiesInterfaces/doc/acsdkPropertiesInterfaces.dox} (70%) rename core/Properties/{acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces => PropertiesInterfaces/include/acsdk/PropertiesInterfaces}/PropertiesFactoryInterface.h (83%) rename core/Properties/{acsdkPropertiesInterfaces/include/acsdkPropertiesInterfaces => PropertiesInterfaces/include/acsdk/PropertiesInterfaces}/PropertiesInterface.h (95%) rename core/Properties/{acsdkPropertiesInterfaces => PropertiesInterfaces}/test/CMakeLists.txt (100%) create mode 100644 core/Properties/PropertiesInterfaces/test/doc/Namespaces.dox create mode 100644 core/Properties/PropertiesInterfaces/test/include/acsdk/PropertiesInterfaces/test/MockProperties.h create mode 100644 core/Properties/PropertiesInterfaces/test/include/acsdk/PropertiesInterfaces/test/MockPropertiesFactory.h rename core/Properties/{acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces => PropertiesInterfaces/test/include/acsdk/PropertiesInterfaces}/test/StubProperties.h (82%) rename core/Properties/{acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces => PropertiesInterfaces/test/include/acsdk/PropertiesInterfaces}/test/StubPropertiesFactory.h (81%) rename core/Properties/{acsdkPropertiesInterfaces => PropertiesInterfaces}/test/src/CMakeLists.txt (69%) rename core/Properties/{acsdkPropertiesInterfaces => PropertiesInterfaces}/test/src/StubProperties.cpp (96%) rename core/Properties/{acsdkPropertiesInterfaces => PropertiesInterfaces}/test/src/StubPropertiesFactory.cpp (84%) delete mode 100644 core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockProperties.h delete mode 100644 core/Properties/acsdkPropertiesInterfaces/test/include/acsdkPropertiesInterfaces/test/MockPropertiesFactory.h create mode 100644 core/acsdkRegistrationManager/include/RegistrationManager/RegistrationManagerFactory.h create mode 100644 core/acsdkRegistrationManager/src/RegistrationManagerFactory.cpp create mode 100644 shared/Notifier/CMakeLists.txt create mode 100644 shared/Notifier/doc/Namespaces.dox create mode 100644 shared/Notifier/doc/Notifier.dox create mode 100644 shared/Notifier/include/acsdk/Notifier/Notifier.h create mode 100644 shared/Notifier/include/acsdk/Notifier/internal/DataInterface.h create mode 100644 shared/Notifier/include/acsdk/Notifier/internal/NotifierTraits.h create mode 100644 shared/Notifier/privateInclude/acsdk/Notifier/private/NotifierData.h create mode 100644 shared/Notifier/privateInclude/acsdk/Notifier/private/ObserverWrapper.h create mode 100644 shared/Notifier/privateInclude/acsdk/Notifier/private/ReferenceType.h create mode 100644 shared/Notifier/src/NotifierData.cpp create mode 100644 shared/Notifier/src/ObserverWrapper.cpp create mode 100644 shared/Notifier/test/CMakeLists.txt rename {core/Crypto/acsdkCryptoInterfaces/test/doc => shared/Notifier/test}/Namespaces.dox (81%) create mode 100644 shared/Notifier/test/NotifierDataTest.cpp rename shared/{acsdkNotifier => Notifier}/test/NotifierTest.cpp (82%) create mode 100644 shared/Notifier/test/NotifierTests.dox create mode 100644 shared/Notifier/test/NotifierTraitsTest.cpp create mode 100644 shared/Notifier/test/ObserverWrapperTest.cpp rename shared/{acsdkNotifierInterfaces => NotifierInterfaces}/CMakeLists.txt (100%) create mode 100644 shared/NotifierInterfaces/doc/Namespaces.dox create mode 100644 shared/NotifierInterfaces/doc/NotifierInterfaces.dox rename shared/{acsdkNotifierInterfaces/include/acsdkNotifierInterfaces/internal => NotifierInterfaces/include/acsdk/NotifierInterfaces}/NotifierInterface.h (54%) create mode 100644 shared/NotifierInterfaces/test/CMakeLists.txt create mode 100644 shared/NotifierInterfaces/test/MockNotifierTest.cpp create mode 100644 shared/NotifierInterfaces/test/Namespaces.dox create mode 100644 shared/NotifierInterfaces/test/NotifierInterfacesTests.dox rename shared/{acsdkNotifierInterfaces/test/include/acsdkNotifierInterfaces/internal => NotifierInterfaces/test/include/acsdk/NotifierInterfaces/test}/MockNotifier.h (70%) create mode 100644 shared/PresentationOrchestrator/.clang-format create mode 100644 shared/PresentationOrchestrator/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/include/acsdk/PresentationOrchestratorClient/PresentationOrchestratorClientFactory.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/privateInclude/acsdk/PresentationOrchestratorClient/private/MultiWindowManagerInterface.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/privateInclude/acsdk/PresentationOrchestratorClient/private/Presentation.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/privateInclude/acsdk/PresentationOrchestratorClient/private/PresentationLifespanToTimeoutMapper.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/privateInclude/acsdk/PresentationOrchestratorClient/private/PresentationOrchestratorClient.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/privateInclude/acsdk/PresentationOrchestratorClient/private/ReorderableUniqueStack.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/privateInclude/acsdk/PresentationOrchestratorClient/private/WindowManager.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/src/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/src/Presentation.cpp create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/src/PresentationLifespanToTimeoutMapper.cpp create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/src/PresentationOrchestratorClient.cpp create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/src/PresentationOrchestratorClientFactory.cpp create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/src/WindowManager.cpp create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/test/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorClient/test/PresentationOrchestratorClientTest.cpp create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/PresentationInterface.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/PresentationObserverInterface.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/PresentationOrchestratorClientInterface.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/PresentationOrchestratorInterface.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/PresentationOrchestratorStateObserverInterface.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/PresentationOrchestratorStateTrackerInterface.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/PresentationOrchestratorTypes.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/PresentationOrchestratorWindowObserverInterface.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/PresentationTypes.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/include/acsdk/PresentationOrchestratorInterfaces/VisualTimeoutManagerInterface.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/test/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/test/acsdk/PresentationOrchestratorInterfaces/MockPresentationObserver.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/test/acsdk/PresentationOrchestratorInterfaces/MockPresentationOrchestratorStateTracker.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorInterfaces/test/acsdk/PresentationOrchestratorInterfaces/MockVisualTimeoutManager.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorStateTracker/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorStateTracker/include/acsdk/PresentationOrchestratorStateTracker/PresentationOrchestratorStateTrackerFactory.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorStateTracker/privateInclude/acsdk/PresentationOrchestratorStateTracker/private/PresentationOrchestratorStateTracker.h create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorStateTracker/src/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorStateTracker/src/PresentationOrchestratorStateTracker.cpp create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorStateTracker/src/PresentationOrchestratorStateTrackerFactory.cpp create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorStateTracker/test/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/PresentationOrchestratorStateTracker/test/PresentationOrchestratorStateTrackerTest.cpp create mode 100644 shared/PresentationOrchestrator/VisualTimeoutManager/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/VisualTimeoutManager/include/acsdk/VisualTimeoutManager/VisualTimeoutManagerFactory.h create mode 100644 shared/PresentationOrchestrator/VisualTimeoutManager/privateInclude/acsdk/VisualTimeoutManager/private/VisualTimeoutManager.h create mode 100644 shared/PresentationOrchestrator/VisualTimeoutManager/src/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/VisualTimeoutManager/src/VisualTimeoutManager.cpp create mode 100644 shared/PresentationOrchestrator/VisualTimeoutManager/src/VisualTimeoutManagerFactory.cpp create mode 100644 shared/PresentationOrchestrator/VisualTimeoutManager/test/CMakeLists.txt create mode 100644 shared/PresentationOrchestrator/VisualTimeoutManager/test/MockTimerFactory.h create mode 100644 shared/PresentationOrchestrator/VisualTimeoutManager/test/VisualTimeoutManagerTest.cpp create mode 100644 shared/SDKClient/CMakeLists.txt create mode 100644 shared/SDKClient/include/acsdk/SDKClient/Annotated.h create mode 100644 shared/SDKClient/include/acsdk/SDKClient/FeatureClientBuilderInterface.h create mode 100644 shared/SDKClient/include/acsdk/SDKClient/FeatureClientInterface.h create mode 100644 shared/SDKClient/include/acsdk/SDKClient/SDKClientBuilder.h create mode 100644 shared/SDKClient/include/acsdk/SDKClient/SDKClientRegistry.h create mode 100644 shared/SDKClient/include/acsdk/SDKClient/internal/FeatureClientBuilderInterface_impl.h create mode 100644 shared/SDKClient/include/acsdk/SDKClient/internal/SDKClientBuilder_impl.h create mode 100644 shared/SDKClient/include/acsdk/SDKClient/internal/SDKClientRegistry_impl.h create mode 100644 shared/SDKClient/include/acsdk/SDKClient/internal/TypeRegistry.h create mode 100644 shared/SDKClient/include/acsdk/SDKClient/internal/Utils.h create mode 100644 shared/SDKClient/src/CMakeLists.txt create mode 100644 shared/SDKClient/src/SDKClientBuilder.cpp create mode 100644 shared/SDKClient/src/SDKClientRegistry.cpp create mode 100644 shared/SDKClient/src/TypeRegistry.cpp create mode 100644 shared/SDKClient/test/CMakeLists.txt create mode 100644 shared/SDKClient/test/SDKClientRegistryTest.cpp delete mode 100644 shared/acsdkNotifier/CMakeLists.txt delete mode 100644 shared/acsdkNotifier/include/acsdkNotifier/internal/Notifier.h delete mode 100644 shared/acsdkNotifier/test/CMakeLists.txt delete mode 100644 shared/acsdkNotifierInterfaces/test/CMakeLists.txt create mode 100644 shared/acsdkStartupManagerInterfaces/test/CMakeLists.txt create mode 100644 shared/acsdkStartupManagerInterfaces/test/acsdkStartupManagerInterfaces/MockStartupNotifier.h diff --git a/ACL/include/ACL/Transport/MessageRequestHandler.h b/ACL/include/ACL/Transport/MessageRequestHandler.h index c2a90e2fb6..f011ad9847 100644 --- a/ACL/include/ACL/Transport/MessageRequestHandler.h +++ b/ACL/include/ACL/Transport/MessageRequestHandler.h @@ -115,7 +115,7 @@ class MessageRequestHandler * Record a stream metric once when a specific threshold of bytes have been read from the stream. * The stream metric name and threshold will be specified in the MessageRequest. */ - void recordStreamMetric(int bytes); + void recordStreamMetric(std::size_t bytes); /** * Record the metric that specifics the start of sending the Message Event to the cloud. @@ -159,7 +159,7 @@ class MessageRequestHandler avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status m_resultStatus; /// The number of bytes that have been read from the stream. - unsigned int m_streamBytesRead; + std::size_t m_streamBytesRead; /// If the stream metric has already been recorded. bool m_recordedStreamMetric; diff --git a/ACL/include/ACL/Transport/MessageRouterFactory.h b/ACL/include/ACL/Transport/MessageRouterFactory.h index 093c46b9ba..dcdfc61404 100644 --- a/ACL/include/ACL/Transport/MessageRouterFactory.h +++ b/ACL/include/ACL/Transport/MessageRouterFactory.h @@ -22,7 +22,6 @@ namespace alexaClientSDK { namespace acl { -using namespace alexaClientSDK::acl; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::avs::attachment; diff --git a/ACL/include/ACL/Transport/MessageRouterFactoryInterface.h b/ACL/include/ACL/Transport/MessageRouterFactoryInterface.h index 7a46a0ede6..a2b3242b6d 100644 --- a/ACL/include/ACL/Transport/MessageRouterFactoryInterface.h +++ b/ACL/include/ACL/Transport/MessageRouterFactoryInterface.h @@ -22,7 +22,6 @@ namespace alexaClientSDK { namespace acl { -using namespace alexaClientSDK::acl; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::avs::attachment; diff --git a/ACL/src/AVSConnectionManager.cpp b/ACL/src/AVSConnectionManager.cpp index c5983a6c8e..75d41af7ec 100644 --- a/ACL/src/AVSConnectionManager.cpp +++ b/ACL/src/AVSConnectionManager.cpp @@ -26,7 +26,7 @@ namespace acl { using namespace alexaClientSDK::avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("AVSConnectionManager"); +#define TAG "AVSConnectionManager" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ACL/src/Transport/DownchannelHandler.cpp b/ACL/src/Transport/DownchannelHandler.cpp index f68908c0ba..8699df90d0 100644 --- a/ACL/src/Transport/DownchannelHandler.cpp +++ b/ACL/src/Transport/DownchannelHandler.cpp @@ -43,7 +43,7 @@ static const std::string DOWNCHANNEL_ID_PREFIX = "AVSDownChannel-"; 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"); +#define TAG "DownchannelHandler" /// String to identify the metric source prefix for @c DownChannelHandler. static const std::string METRIC_SOURCE_PREFIX = "DOWNCHANNEL_HANDLER-"; diff --git a/ACL/src/Transport/ExchangeHandler.cpp b/ACL/src/Transport/ExchangeHandler.cpp index e47ab179d6..222d71e31a 100644 --- a/ACL/src/Transport/ExchangeHandler.cpp +++ b/ACL/src/Transport/ExchangeHandler.cpp @@ -27,7 +27,7 @@ namespace acl { static const std::string AUTHORIZATION_HEADER = "Authorization: Bearer "; /// String to identify log entries originating from this file. -static const std::string TAG("ExchangeHandler"); +#define TAG "ExchangeHandler" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ACL/src/Transport/HTTP2Transport.cpp b/ACL/src/Transport/HTTP2Transport.cpp index 5d50ad78e3..bcb0be467f 100644 --- a/ACL/src/Transport/HTTP2Transport.cpp +++ b/ACL/src/Transport/HTTP2Transport.cpp @@ -43,7 +43,7 @@ using namespace avsCommon::utils::metrics; using namespace avsCommon::utils::power; /// String to identify log entries originating from this file. -static const std::string TAG("HTTP2Transport"); +#define TAG "HTTP2Transport" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -288,10 +288,10 @@ HTTP2Transport::HTTP2Transport( m_disconnectReason{ConnectionStatusObserverInterface::ChangedReason::NONE} { m_observers.insert(transportObserver); - m_mainLoopPowerResource = PowerMonitor::getInstance()->createLocalPowerResource(TAG + "_mainLoop"); + m_mainLoopPowerResource = PowerMonitor::getInstance()->createLocalPowerResource(TAG "_mainLoop"); m_requestActivityPowerResource = - PowerMonitor::getInstance()->createLocalPowerResource(TAG + "_requestActivityResource"); + PowerMonitor::getInstance()->createLocalPowerResource(TAG "_requestActivityResource"); if (m_mainLoopPowerResource) { m_mainLoopPowerResource->acquire(); @@ -772,6 +772,8 @@ HTTP2Transport::State HTTP2Transport::handlePostConnecting() { } } + ACSDK_INFO(LX_P("handlePostConnecting").m("completing")); + return sendMessagesAndPings(State::POST_CONNECTING, m_requestQueue); } diff --git a/ACL/src/Transport/HTTP2TransportFactory.cpp b/ACL/src/Transport/HTTP2TransportFactory.cpp index 805536fbdb..e9354cfc39 100644 --- a/ACL/src/Transport/HTTP2TransportFactory.cpp +++ b/ACL/src/Transport/HTTP2TransportFactory.cpp @@ -27,7 +27,7 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::avs::attachment; /// String to identify log entries originating from this file. -static const std::string TAG("HTTP2TransportFactory"); +#define TAG "HTTP2TransportFactory" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ACL/src/Transport/MessageRequestHandler.cpp b/ACL/src/Transport/MessageRequestHandler.cpp index 4a036005e4..7f0ec41b39 100644 --- a/ACL/src/Transport/MessageRequestHandler.cpp +++ b/ACL/src/Transport/MessageRequestHandler.cpp @@ -32,6 +32,7 @@ namespace alexaClientSDK { namespace acl { +using namespace avsCommon::avs; using namespace avsCommon::avs::attachment; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::http; @@ -66,7 +67,7 @@ static const std::string ATTACHMENT_CONTENT_TYPE = "Content-Type: application/oc static const std::string MESSAGEREQUEST_ID_PREFIX = "AVSEvent-"; /// String to identify log entries originating from this file. -static const std::string TAG("MessageRequestHandler"); +#define TAG "MessageRequestHandler" /// Prefix used to identify metrics published by this module. static const std::string ACL_METRIC_SOURCE_PREFIX = "ACL-"; @@ -92,9 +93,18 @@ static const std::string SEND_COMPLETED = "SEND_COMPLETED"; /// Metric identifier for message send error. static const std::string MESSAGE_SEND_ERROR = "ERROR.MESSAGE_SEND_FAILED"; -// Key value separator for HTTP headers +/// Key value separator for HTTP headers static const std::string HTTP_KEY_VALUE_SEPARATOR = ": "; +/// Event header key for the namespace field. +static const std::string EVENT_HEADER_NAMESPACE = "namespace"; + +/// Event header key for the name field. +static const std::string EVENT_HEADER_NAME = "name"; + +/// Event header missing. +static const std::string EVENT_HEADER_MISSING = "EVENT_HEADER_MISSING"; + /** * Create a LogEntry using this file's TAG and the specified event string. * @@ -131,10 +141,12 @@ static void collectSendDataResultMetric( * * @param metricRecorder The metric recorder object. * @param status The @c MessageRequestObserverInterface::Status of the message. + * @param messageRequest The @c MessageRequest object. */ static void submitMessageSendErrorMetric( const std::shared_ptr& metricRecorder, - MessageRequestObserverInterface::Status status) { + MessageRequestObserverInterface::Status status, + const std::shared_ptr& messageRequest) { if (!metricRecorder) { return; } @@ -155,11 +167,27 @@ static void submitMessageSendErrorMetric( return; } - auto metricEvent = MetricEventBuilder{} - .setActivityName(ACL_METRIC_SOURCE_PREFIX + MESSAGE_SEND_ERROR) - .addDataPoint(DataPointCounterBuilder{}.setName(ss.str()).increment(1).build()) - .build(); - + auto metricEventBuilder = MetricEventBuilder{} + .setActivityName(ACL_METRIC_SOURCE_PREFIX + MESSAGE_SEND_ERROR) + .addDataPoint(DataPointCounterBuilder{}.setName(ss.str()).increment(1).build()); + if (messageRequest) { + auto eventHeaders = messageRequest->retrieveEventHeaders(); + if (!eventHeaders.eventNamespace.empty()) { + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName(EVENT_HEADER_NAMESPACE).setValue(eventHeaders.eventNamespace).build()); + } else { + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName(EVENT_HEADER_NAMESPACE).setValue(EVENT_HEADER_MISSING).build()); + } + if (!eventHeaders.eventName.empty()) { + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName(EVENT_HEADER_NAME).setValue(eventHeaders.eventName).build()); + } else { + metricEventBuilder.addDataPoint( + DataPointStringBuilder{}.setName(EVENT_HEADER_NAME).setValue(EVENT_HEADER_MISSING).build()); + } + } + auto metricEvent = metricEventBuilder.build(); if (!metricEvent) { ACSDK_ERROR(LX("submitErrorMetricFailed").d("reason", "invalid metric event")); return; @@ -168,7 +196,7 @@ static void submitMessageSendErrorMetric( recordMetric(metricRecorder, metricEvent); } -void MessageRequestHandler::recordStreamMetric(int bytes) { +void MessageRequestHandler::recordStreamMetric(std::size_t bytes) { if (m_messageRequest == nullptr) { return; } @@ -506,7 +534,7 @@ void MessageRequestHandler::onResponseFinished(HTTP2ResponseFinishedStatus statu m_messageRequest->sendCompleted(m_resultStatus); - submitMessageSendErrorMetric(m_metricRecorder, m_resultStatus); + submitMessageSendErrorMetric(m_metricRecorder, m_resultStatus, m_messageRequest); } } // namespace acl diff --git a/ACL/src/Transport/MessageRequestQueue.cpp b/ACL/src/Transport/MessageRequestQueue.cpp index 64bc623a9a..ac5adb2b30 100644 --- a/ACL/src/Transport/MessageRequestQueue.cpp +++ b/ACL/src/Transport/MessageRequestQueue.cpp @@ -24,7 +24,7 @@ namespace acl { using namespace avsCommon::avs; /// String to identify log entries originating from this file. -static const std::string TAG("MessageRequestQueue"); +#define TAG "MessageRequestQueue" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ACL/src/Transport/MessageRouter.cpp b/ACL/src/Transport/MessageRouter.cpp index 752ad20af9..bb8e16ce96 100644 --- a/ACL/src/Transport/MessageRouter.cpp +++ b/ACL/src/Transport/MessageRouter.cpp @@ -31,7 +31,7 @@ using namespace avsCommon::avs::attachment; using namespace avsCommon::avs; /// String to identify log entries originating from this file. -static const std::string TAG("MessageRouter"); +#define TAG "MessageRouter" /// String for logging purpose as the key for the size of m_transports. static constexpr const char* KEY_SIZEOF_TRANSPORTS = "sizeOf m_transports"; @@ -316,7 +316,7 @@ void MessageRouter::notifyObserverOnConnectionStatusChanged( m_serverSideDisconnectNotificationPending = true; m_serverSideDisconnectTimer.start(m_serverSideReconnectGracePeriod, [this]() { ACSDK_DEBUG0(LX("serverSideDisconectTimerPredicate")); - m_executor.submit([this]() { + m_executor.execute([this]() { ACSDK_DEBUG0( LX("serverSideDisconectTimerHandler") .d("m_serverSideDisconnectNotificationPending", m_serverSideDisconnectNotificationPending)); @@ -335,7 +335,7 @@ void MessageRouter::notifyObserverOnConnectionStatusChanged( handleNotifyObserverOnConnectionStatusChanged(status, reason); } }; - m_executor.submit(task); + m_executor.execute(task); } void MessageRouter::handleNotifyObserverOnConnectionStatusChanged( @@ -360,7 +360,7 @@ void MessageRouter::notifyObserverOnReceive(const std::string& contextId, const temp->receive(contextId, message); } }; - m_executor.submit(task); + m_executor.execute(task); } void MessageRouter::createActiveTransportLocked() { @@ -428,7 +428,7 @@ void MessageRouter::safelyResetActiveTransportLocked() { void MessageRouter::safelyReleaseTransport(std::shared_ptr transport) { if (transport) { auto task = [transport]() { transport->shutdown(); }; - m_executor.submit(task); + m_executor.execute(task); } } diff --git a/ACL/src/Transport/MessageRouterFactory.cpp b/ACL/src/Transport/MessageRouterFactory.cpp index b70d903e41..85506d09d9 100644 --- a/ACL/src/Transport/MessageRouterFactory.cpp +++ b/ACL/src/Transport/MessageRouterFactory.cpp @@ -20,7 +20,6 @@ namespace alexaClientSDK { namespace acl { -using namespace alexaClientSDK::acl; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::avs::attachment; diff --git a/ACL/src/Transport/MimeResponseSink.cpp b/ACL/src/Transport/MimeResponseSink.cpp index a9fac90ff7..2bffc4e964 100644 --- a/ACL/src/Transport/MimeResponseSink.cpp +++ b/ACL/src/Transport/MimeResponseSink.cpp @@ -47,7 +47,7 @@ static const std::string MIME_OCTET_STREAM_CONTENT_TYPE = "application/octet-str 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"); +#define TAG "MimeResponseSink" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ACL/src/Transport/PingHandler.cpp b/ACL/src/Transport/PingHandler.cpp index f6bf8260a4..7589e50647 100644 --- a/ACL/src/Transport/PingHandler.cpp +++ b/ACL/src/Transport/PingHandler.cpp @@ -40,7 +40,7 @@ static const uint8_t PING_PRIORITY = 200; static const std::string PING_ID_PREFIX = "AVSPing-"; /// String to identify log entries originating from this file. -static const std::string TAG("PingHandler"); +#define TAG "PingHandler" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ACL/src/Transport/PostConnectSequencer.cpp b/ACL/src/Transport/PostConnectSequencer.cpp index aed17e2262..39c0dd813c 100644 --- a/ACL/src/Transport/PostConnectSequencer.cpp +++ b/ACL/src/Transport/PostConnectSequencer.cpp @@ -27,7 +27,7 @@ using namespace avsCommon::utils::error; using namespace avsCommon::utils::power; /// String to identify log entries originating form this file. -static const std::string TAG("PostConnectSequencer"); +#define TAG "PostConnectSequencer" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -50,9 +50,9 @@ std::shared_ptr PostConnectSequencer::create( PostConnectSequencer::PostConnectSequencer(const PostConnectOperationsSet& postConnectOperations) : m_isStopping{false}, m_postConnectOperations{postConnectOperations} { - ACSDK_DEBUG5(LX("init")); + ACSDK_INFO(LX("init")); - m_mainLoopPowerResource = PowerMonitor::getInstance()->createLocalPowerResource(TAG + "_mainLoop"); + m_mainLoopPowerResource = PowerMonitor::getInstance()->createLocalPowerResource(TAG "_mainLoop"); if (m_mainLoopPowerResource) { m_mainLoopPowerResource->acquire(); @@ -60,14 +60,14 @@ PostConnectSequencer::PostConnectSequencer(const PostConnectOperationsSet& postC } PostConnectSequencer::~PostConnectSequencer() { - ACSDK_DEBUG5(LX("destroy")); + ACSDK_INFO(LX("destroy")); stop(); } bool PostConnectSequencer::doPostConnect( std::shared_ptr postConnectSender, std::shared_ptr postConnectObserver) { - ACSDK_DEBUG5(LX("doPostConnect")); + ACSDK_INFO(LX("doPostConnect")); if (!postConnectSender) { ACSDK_ERROR(LX("doPostConnectFailed").d("reason", "nullPostConnectSender")); @@ -94,7 +94,7 @@ bool PostConnectSequencer::doPostConnect( void PostConnectSequencer::mainLoop( std::shared_ptr postConnectSender, std::shared_ptr postConnectObserver) { - ACSDK_DEBUG5(LX("mainLoop")); + ACSDK_INFO(LX("mainLoop")); PowerMonitor::getInstance()->assignThreadPowerResource(m_mainLoopPowerResource); @@ -144,11 +144,11 @@ void PostConnectSequencer::mainLoop( postConnectObserver->onPostConnected(); } - ACSDK_DEBUG5(LX("mainLoopReturning")); + ACSDK_INFO(LX("mainLoopReturning")); } void PostConnectSequencer::onDisconnect() { - ACSDK_DEBUG5(LX("onDisconnect")); + ACSDK_INFO(LX("onDisconnect")); stop(); } @@ -163,7 +163,7 @@ bool PostConnectSequencer::isStopping() { } void PostConnectSequencer::stop() { - ACSDK_DEBUG5(LX("stop")); + ACSDK_INFO(LX("stop")); { std::lock_guard lock{m_mutex}; if (m_isStopping) { diff --git a/ACL/src/Transport/PostConnectSequencerFactory.cpp b/ACL/src/Transport/PostConnectSequencerFactory.cpp index b28d63a251..503619f8a6 100644 --- a/ACL/src/Transport/PostConnectSequencerFactory.cpp +++ b/ACL/src/Transport/PostConnectSequencerFactory.cpp @@ -27,7 +27,7 @@ using namespace avsCommon::sdkInterfaces; using namespace acsdkPostConnectOperationProviderRegistrarInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("PostConnectSequencerFactory"); +#define TAG "PostConnectSequencerFactory" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp b/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp index 701147990a..54860f84c7 100644 --- a/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp +++ b/ACL/src/Transport/SynchronizedMessageRequestQueue.cpp @@ -24,7 +24,7 @@ namespace acl { using namespace avsCommon::avs; /// String to identify log entries originating from this file. -static const std::string TAG("SynchronizedMessageRequestQueue"); +#define TAG "SynchronizedMessageRequestQueue" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ACL/test/Transport/Common/MockHTTP2Connection.cpp b/ACL/test/Transport/Common/MockHTTP2Connection.cpp index 775cb656c8..b163e42fa8 100644 --- a/ACL/test/Transport/Common/MockHTTP2Connection.cpp +++ b/ACL/test/Transport/Common/MockHTTP2Connection.cpp @@ -55,7 +55,7 @@ std::shared_ptr MockHTTP2Connection::createAndSendRequest if (request->getRequestType() == HTTP2RequestType::POST) { // Parse POST HTTP2 Requests. - std::lock_guard lock(m_postRequestMutex); + std::lock_guard innerLock(m_postRequestMutex); m_postRequestQueue.push_back(request); if (m_postResponseCode != HTTPResponseCode::HTTP_RESPONSE_CODE_UNDEFINED) { request->getSink()->onReceiveResponseCode(responseCodeToInt(m_postResponseCode)); @@ -66,12 +66,12 @@ std::shared_ptr MockHTTP2Connection::createAndSendRequest m_requestPostCv.notify_one(); } else if (m_downchannelURL == request->getUrl()) { // Push downchannel requests to its queue. - std::lock_guard lock(m_downchannelRequestMutex); + std::lock_guard innerLock(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); + std::lock_guard innerLock(m_pingRequestMutex); m_pingRequestQueue.push_back(request); m_pingRequestCv.notify_one(); } diff --git a/ACL/test/Transport/Common/MockMimeResponseSink.cpp b/ACL/test/Transport/Common/MockMimeResponseSink.cpp index fa9dec7451..b2b6aa6f19 100644 --- a/ACL/test/Transport/Common/MockMimeResponseSink.cpp +++ b/ACL/test/Transport/Common/MockMimeResponseSink.cpp @@ -64,7 +64,7 @@ std::vector MockMimeResponseSink::getMimePart(unsigned part) { } unsigned MockMimeResponseSink::getCountOfMimeParts() { - return m_mimeContents.size(); + return static_cast(m_mimeContents.size()); } } // namespace test diff --git a/ACL/test/Transport/HTTP2TransportTest.cpp b/ACL/test/Transport/HTTP2TransportTest.cpp index f8be3dcc6c..f9b7032780 100644 --- a/ACL/test/Transport/HTTP2TransportTest.cpp +++ b/ACL/test/Transport/HTTP2TransportTest.cpp @@ -644,7 +644,7 @@ TEST_F(HTTP2TransportTest, test_pauseSendWhenSDSEmpty) { // 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; + unsigned int chunkSize = static_cast((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; @@ -959,7 +959,7 @@ TEST_F(HTTP2TransportTest, test_onSendCompletedNotification) { // Send a message for each test case defined in the messageResponseMap. std::vector> messageObservers; - unsigned messagesCount = messageResponseMap.size(); // number of test messages to send + unsigned messagesCount = static_cast(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(); diff --git a/ACL/test/Transport/MessageRouterTest.cpp b/ACL/test/Transport/MessageRouterTest.cpp index 7cd35bb7d5..8daf3230ac 100644 --- a/ACL/test/Transport/MessageRouterTest.cpp +++ b/ACL/test/Transport/MessageRouterTest.cpp @@ -149,7 +149,7 @@ TEST_F(MessageRouterTest, test_sendMessageDoesNotSendAfterDisconnected) { m_router->sendMessage(messageRequest); } -TEST_F(MessageRouterTest, test_shutdownCalledWithMultipleMessages) { +TEST_F(MessageRouterTest, testTimer_shutdownCalledWithMultipleMessages) { setupStateToConnected(); // wait for the result to propagate by scheduling a task on the client executor diff --git a/ADSL/src/DirectiveProcessor.cpp b/ADSL/src/DirectiveProcessor.cpp index 3ca3dd2c55..80d7e995cc 100644 --- a/ADSL/src/DirectiveProcessor.cpp +++ b/ADSL/src/DirectiveProcessor.cpp @@ -25,7 +25,7 @@ #include "ADSL/DirectiveProcessor.h" /// String to identify log entries originating from this file. -static const std::string TAG("DirectiveProcessor"); +#define TAG "DirectiveProcessor" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -427,7 +427,6 @@ bool DirectiveProcessor::handleQueuedDirectivesLocked(std::unique_lockgetDialogRequestId()); } } - return handleDirectiveCalled; } diff --git a/ADSL/src/DirectiveRouter.cpp b/ADSL/src/DirectiveRouter.cpp index 9cd5f520ff..43cd5a3db0 100644 --- a/ADSL/src/DirectiveRouter.cpp +++ b/ADSL/src/DirectiveRouter.cpp @@ -27,7 +27,7 @@ #include "ADSL/DirectiveRouter.h" /// String to identify log entries originating from this file. -static const std::string TAG("DirectiveRouter"); +#define TAG "DirectiveRouter" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ADSL/src/DirectiveSequencer.cpp b/ADSL/src/DirectiveSequencer.cpp index 6ce7101540..328e61db0b 100644 --- a/ADSL/src/DirectiveSequencer.cpp +++ b/ADSL/src/DirectiveSequencer.cpp @@ -26,7 +26,7 @@ #include "ADSL/DirectiveSequencer.h" /// String to identify log entries originating from this file. -static const std::string TAG("DirectiveSequencer"); +#define TAG "DirectiveSequencer" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ADSL/src/MessageInterpreter.cpp b/ADSL/src/MessageInterpreter.cpp index 0cfcf613b9..33bbd3d4a5 100644 --- a/ADSL/src/MessageInterpreter.cpp +++ b/ADSL/src/MessageInterpreter.cpp @@ -39,7 +39,7 @@ static const std::string PARSE_COMPLETE("PARSE_COMPLETE"); static const std::string PARSE_COMPLETE_ACTIVITY_NAME("MESSAGE_INTERPRETER-" + PARSE_COMPLETE); /// String to identify log entries originating from this file. -static const std::string TAG("MessageInterpreter"); +#define TAG "MessageInterpreter" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AFML/src/AudioActivityTracker.cpp b/AFML/src/AudioActivityTracker.cpp index b0ef2fd6ba..bd29030b6a 100644 --- a/AFML/src/AudioActivityTracker.cpp +++ b/AFML/src/AudioActivityTracker.cpp @@ -43,7 +43,7 @@ static const std::string AUDIOACTIVITYTRACKER_CAPABILITY_INTERFACE_NAME = "Audio 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"); +#define TAG "AudioActivityTracker" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -113,12 +113,12 @@ void AudioActivityTracker::provideState( const avsCommon::avs::NamespaceAndName& stateProviderName, unsigned int stateRequestToken) { ACSDK_DEBUG5(LX("provideState")); - m_executor.submit([this, stateRequestToken]() { executeProvideState(stateRequestToken); }); + m_executor.execute([this, stateRequestToken]() { executeProvideState(stateRequestToken); }); } void AudioActivityTracker::notifyOfActivityUpdates(const std::vector& channelStates) { ACSDK_DEBUG5(LX("notifyOfActivityUpdates")); - m_executor.submit([this, channelStates]() { executeNotifyOfActivityUpdates(channelStates); }); + m_executor.execute([this, channelStates]() { executeNotifyOfActivityUpdates(channelStates); }); } AudioActivityTracker::AudioActivityTracker( diff --git a/AFML/src/Channel.cpp b/AFML/src/Channel.cpp index 3158ae00ce..8fc5a997ca 100644 --- a/AFML/src/Channel.cpp +++ b/AFML/src/Channel.cpp @@ -25,7 +25,7 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("Channel"); +#define TAG "Channel" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -49,7 +49,7 @@ Channel::Channel(const std::string& name, const unsigned int priority, bool isVi m_state{name} { /// Non-refcounted. m_powerResource = power::PowerResource::create( - TAG + ":" + name, + TAG ":" + name, power::PowerMonitor::getInstance()->getPowerResourceManager(), PowerResourceManagerInterface::PowerResourceLevel::STANDBY_MED, false); diff --git a/AFML/src/FocusManagementComponent.cpp b/AFML/src/FocusManagementComponent.cpp index f67235aab0..63fac243a4 100644 --- a/AFML/src/FocusManagementComponent.cpp +++ b/AFML/src/FocusManagementComponent.cpp @@ -27,7 +27,7 @@ using AudioFocusAnnotation = avsCommon::sdkInterfaces::AudioFocusAnnotation; using VisualFocusAnnotation = avsCommon::sdkInterfaces::VisualFocusAnnotation; /// String to identify log entries originating from this file. -static const std::string TAG("FocusManagementComponent"); +#define TAG "FocusManagementComponent" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AFML/src/FocusManager.cpp b/AFML/src/FocusManager.cpp index c7c7b6ec5c..555df4d5b0 100644 --- a/AFML/src/FocusManager.cpp +++ b/AFML/src/FocusManager.cpp @@ -29,7 +29,7 @@ using namespace avsCommon::avs; using namespace interruptModel; /// String to identify log entries originating from this file. -static const std::string TAG("FocusManager"); +#define TAG "FocusManager" /// Key for @c FocusManager configurations in configuration node. static const std::string VIRTUAL_CHANNELS_CONFIG_KEY = "virtualChannels"; @@ -77,7 +77,7 @@ bool FocusManager::acquireChannel( return false; } - m_executor.submit( + m_executor.execute( [this, channelToAcquire, channelActivity]() { acquireChannelHelper(channelToAcquire, channelActivity); }); return true; } @@ -97,7 +97,7 @@ bool FocusManager::acquireChannel( return false; } - m_executor.submit( + m_executor.execute( [this, channelToAcquire, channelActivity]() { acquireChannelHelper(channelToAcquire, channelActivity); }); return true; } @@ -117,7 +117,7 @@ std::future FocusManager::releaseChannel( return returnValue; } - m_executor.submit([this, channelToRelease, channelObserver, releaseChannelSuccess, channelName]() { + m_executor.execute([this, channelToRelease, channelObserver, releaseChannelSuccess, channelName]() { releaseChannelHelper(channelToRelease, channelObserver, releaseChannelSuccess, channelName); }); @@ -379,9 +379,9 @@ void FocusManager::stopAllActivitiesHelper(const ChannelsToInterfaceNamesMap& ch // Only release and set entire channel focus to NONE if there are no active Activity remaining. if (!channel->isActive()) { // Lock here to update internal state which stopForegroundActivity may concurrently access. - std::unique_lock lock(m_mutex); + std::unique_lock activeChannelsLock(m_mutex); m_activeChannels.erase(channel); - lock.unlock(); + activeChannelsLock.unlock(); setChannelFocus(channel, FocusState::NONE, MixingBehavior::MUST_STOP); } } diff --git a/AFML/src/VisualActivityTracker.cpp b/AFML/src/VisualActivityTracker.cpp index bf12c73f0c..ffefea6760 100644 --- a/AFML/src/VisualActivityTracker.cpp +++ b/AFML/src/VisualActivityTracker.cpp @@ -40,7 +40,7 @@ static const std::string VISUALACTIVITYTRACKER_CAPABILITY_INTERFACE_NAME = "Visu 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"); +#define TAG "VisualActivityTracker" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -110,7 +110,7 @@ void VisualActivityTracker::provideState( const avsCommon::avs::NamespaceAndName& stateProviderName, unsigned int stateRequestToken) { ACSDK_DEBUG5(LX("provideState")); - m_executor.submit([this, stateRequestToken]() { executeProvideState(stateRequestToken); }); + m_executor.execute([this, stateRequestToken]() { executeProvideState(stateRequestToken); }); } void VisualActivityTracker::notifyOfActivityUpdates(const std::vector& channels) { @@ -132,7 +132,7 @@ void VisualActivityTracker::notifyOfActivityUpdates(const std::vector +#include #include namespace alexaClientSDK { @@ -27,7 +27,7 @@ namespace avs { * Implementation of CapabilityChangeNotifierInterface */ using CapabilityChangeNotifier = - acsdkNotifier::Notifier; + notifier::Notifier; } // namespace avs } // namespace avsCommon diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifierInterface.h b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifierInterface.h index 3af5c99e69..ac197b669f 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifierInterface.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityChangeNotifierInterface.h @@ -16,7 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CAPABILITYCHANGENOTIFIERINTERFACE_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_CAPABILITYCHANGENOTIFIERINTERFACE_H_ -#include +#include #include namespace alexaClientSDK { @@ -27,7 +27,7 @@ namespace avs { * Interface for registering to observe capability change. */ using CapabilityChangeNotifierInterface = - acsdkNotifierInterfaces::NotifierInterface; + notifierInterfaces::NotifierInterface; } // namespace avs } // namespace avsCommon diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityConfiguration.h b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityConfiguration.h index 9012952693..03163e2fe3 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityConfiguration.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/CapabilityConfiguration.h @@ -28,13 +28,13 @@ namespace avsCommon { namespace avs { /// Key for interface type in the @c CapabilityConfiguration map -static const std::string CAPABILITY_INTERFACE_TYPE_KEY = "type"; +static const auto CAPABILITY_INTERFACE_TYPE_KEY = "type"; /// Key for interface name in the @c CapabilityConfiguration map -static const std::string CAPABILITY_INTERFACE_NAME_KEY = "interface"; +static const auto CAPABILITY_INTERFACE_NAME_KEY = "interface"; /// Key for interface version in the @c CapabilityConfiguration map -static const std::string CAPABILITY_INTERFACE_VERSION_KEY = "version"; +static const auto CAPABILITY_INTERFACE_VERSION_KEY = "version"; /// Key for interface configurations in the @c CapabilityConfiguration map -static const std::string CAPABILITY_INTERFACE_CONFIGURATIONS_KEY = "configurations"; +static const auto CAPABILITY_INTERFACE_CONFIGURATIONS_KEY = "configurations"; /** * Class to encapsulate the capability configuration implemented by a capability agent. diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/MessageRequest.h b/AVSCommon/AVS/include/AVSCommon/AVS/MessageRequest.h index 871dab5d98..6886e5fb5b 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/MessageRequest.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/MessageRequest.h @@ -59,6 +59,28 @@ class MessageRequest { std::shared_ptr reader; }; + /// A struct to hold event namespace and name. + struct EventHeaders { + /** + * Constructor. + * + * @param eventNamespace The namespace of the event. + * @param eventName The name of the event. + */ + EventHeaders(const std::string& eventNamespace, const std::string& eventName) : + eventNamespace{eventNamespace}, + eventName{eventName} { + } + + EventHeaders() = default; + + /// The event namespace. + std::string eventNamespace; + + /// The event name. + std::string eventName; + }; + /** * Function to resolve an editable message request based on the provided resolveKey by updating the MessageRequest. * @param[in,out] req Target editable request message that will be modified in place. @@ -216,6 +238,13 @@ class MessageRequest { */ void removeObserver(std::shared_ptr observer); + /** + * Retrieve MessageRequest event headers (namespace and name). + * + * @return EventHeaders containing the namespace and name. + */ + EventHeaders retrieveEventHeaders() const; + /** * Get additional HTTP headers for this request * diff --git a/AVSCommon/AVS/src/AVSContext.cpp b/AVSCommon/AVS/src/AVSContext.cpp index d3a0a275d8..c80de167e3 100644 --- a/AVSCommon/AVS/src/AVSContext.cpp +++ b/AVSCommon/AVS/src/AVSContext.cpp @@ -39,7 +39,7 @@ static const std::string TIME_OF_SAMPLE_KEY_STRING = "timeOfSample"; static const std::string UNCERTAINTY_KEY_STRING = "uncertaintyInMilliseconds"; /// String to identify log entries originating from this file. -static const std::string TAG("AVSContext"); +#define TAG "AVSContext" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/AVSDirective.cpp b/AVSCommon/AVS/src/AVSDirective.cpp index d9e8c65274..e0559eee83 100644 --- a/AVSCommon/AVS/src/AVSDirective.cpp +++ b/AVSCommon/AVS/src/AVSDirective.cpp @@ -59,7 +59,7 @@ static const std::string JSON_ENDPOINT_ID_KEY = "endpointId"; static const std::string JSON_ENDPOINT_COOKIE_KEY = "cookie"; /// String to identify log entries originating from this file. -static const std::string TAG("AvsDirective"); +#define TAG "AvsDirective" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp b/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp index 4a4ba73e83..617c86455b 100644 --- a/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp +++ b/AVSCommon/AVS/src/AbstractAVSConnectionManager.cpp @@ -23,7 +23,7 @@ namespace avs { using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("AbstractAVSConnectionManager"); +#define TAG "AbstractAVSConnectionManager" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/AlexaClientSDKInit.cpp b/AVSCommon/AVS/src/AlexaClientSDKInit.cpp index 2af6d8c173..f54fb3abfb 100644 --- a/AVSCommon/AVS/src/AlexaClientSDKInit.cpp +++ b/AVSCommon/AVS/src/AlexaClientSDKInit.cpp @@ -30,7 +30,7 @@ namespace avs { namespace initialization { /// String to identify log entries originating from this file. -static const std::string TAG("AlexaClientSdkInit"); +#define TAG "AlexaClientSdkInit" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/Attachment/AttachmentManager.cpp b/AVSCommon/AVS/src/Attachment/AttachmentManager.cpp index 025b13fb51..fca1a7c5dc 100644 --- a/AVSCommon/AVS/src/Attachment/AttachmentManager.cpp +++ b/AVSCommon/AVS/src/Attachment/AttachmentManager.cpp @@ -30,7 +30,7 @@ using namespace alexaClientSDK::avsCommon::utils; using namespace alexaClientSDK::avsCommon::utils::memory; /// String to identify log entries originating from this file. -static const std::string TAG("AttachmentManager"); +#define TAG "AttachmentManager" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp b/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp index 3d837b289f..56f5d5dcc8 100644 --- a/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp +++ b/AVSCommon/AVS/src/Attachment/AttachmentUtils.cpp @@ -28,7 +28,7 @@ namespace avs { namespace attachment { /// String to identify log entries originating from this file. -static const std::string TAG("AttachmentUtils"); +#define TAG "AttachmentUtils" /// Maximum size of the reader is 4KB. static const std::size_t MAX_READER_SIZE = 4 * 1024; diff --git a/AVSCommon/AVS/src/Attachment/InProcessAttachmentWriter.cpp b/AVSCommon/AVS/src/Attachment/InProcessAttachmentWriter.cpp index 58910e700b..c335433c41 100644 --- a/AVSCommon/AVS/src/Attachment/InProcessAttachmentWriter.cpp +++ b/AVSCommon/AVS/src/Attachment/InProcessAttachmentWriter.cpp @@ -24,7 +24,7 @@ namespace attachment { using namespace alexaClientSDK::avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("InProcessAttachmentWriter"); +#define TAG "InProcessAttachmentWriter" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/BlockingPolicy.cpp b/AVSCommon/AVS/src/BlockingPolicy.cpp index 97ed6d9619..5082f4a334 100644 --- a/AVSCommon/AVS/src/BlockingPolicy.cpp +++ b/AVSCommon/AVS/src/BlockingPolicy.cpp @@ -26,17 +26,16 @@ 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; +static const unsigned long MEDIUM_FLAG_AUDIO = 1; /// Flag indicating @c VISUAL medium is used. -static const long MEDIUM_FLAG_VISUAL = 2; +static const unsigned long MEDIUM_FLAG_VISUAL = 2; /// String to identify log entries originating from this file. -static const std::string TAG("BlockingPolicy"); +#define TAG "BlockingPolicy" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/CapabilityAgent.cpp b/AVSCommon/AVS/src/CapabilityAgent.cpp index bb5b329aaa..c44c2ec78d 100644 --- a/AVSCommon/AVS/src/CapabilityAgent.cpp +++ b/AVSCommon/AVS/src/CapabilityAgent.cpp @@ -31,7 +31,7 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("CapabilityAgent"); +#define TAG "CapabilityAgent" /// Maximum number of directives that Can be Queued before we emit warning logs. static const int CAPABILITY_QUEUE_WARN_SIZE = 10; @@ -67,6 +67,10 @@ CapabilityAgent::DirectiveInfo::DirectiveInfo( void CapabilityAgent::preHandleDirective( std::shared_ptr directive, std::unique_ptr result) { + if (!directive) { + ACSDK_ERROR(LX("preHandleDirectiveFailed").d("reason", "nullDirective")); + return; + } std::string messageId = directive->getMessageId(); auto info = getDirectiveInfo(messageId); if (info) { diff --git a/AVSCommon/AVS/src/CapabilityResources.cpp b/AVSCommon/AVS/src/CapabilityResources.cpp index d5247142d2..71bb58fa12 100644 --- a/AVSCommon/AVS/src/CapabilityResources.cpp +++ b/AVSCommon/AVS/src/CapabilityResources.cpp @@ -24,7 +24,7 @@ namespace avsCommon { namespace avs { /// String to identify log entries originating from this file. -static const std::string TAG("CapabilityResources"); +#define TAG "CapabilityResources" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/CapabilitySemantics/ActionsToDirectiveMapping.cpp b/AVSCommon/AVS/src/CapabilitySemantics/ActionsToDirectiveMapping.cpp index 80fcdb5de6..2298a77d4d 100644 --- a/AVSCommon/AVS/src/CapabilitySemantics/ActionsToDirectiveMapping.cpp +++ b/AVSCommon/AVS/src/CapabilitySemantics/ActionsToDirectiveMapping.cpp @@ -46,7 +46,7 @@ static const std::string PAYLOAD_KEY("payload"); static const std::string EMPTY_JSON("{}"); /// String to identify log entries originating from this file. -static const std::string TAG("ActionsToDirectiveMapping"); +#define TAG "ActionsToDirectiveMapping" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/CapabilitySemantics/CapabilitySemantics.cpp b/AVSCommon/AVS/src/CapabilitySemantics/CapabilitySemantics.cpp index 727b7bd5f7..ac3c1efda2 100644 --- a/AVSCommon/AVS/src/CapabilitySemantics/CapabilitySemantics.cpp +++ b/AVSCommon/AVS/src/CapabilitySemantics/CapabilitySemantics.cpp @@ -34,7 +34,7 @@ static const std::string STATE_MAPPINGS_KEY("stateMappings"); static const std::string EMPTY_JSON("{}"); /// String to identify log entries originating from this file. -static const std::string TAG("CapabilitySemantics"); +#define TAG "CapabilitySemantics" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/CapabilitySemantics/StatesToRangeMapping.cpp b/AVSCommon/AVS/src/CapabilitySemantics/StatesToRangeMapping.cpp index 8b43bfe5f2..63ad48014c 100644 --- a/AVSCommon/AVS/src/CapabilitySemantics/StatesToRangeMapping.cpp +++ b/AVSCommon/AVS/src/CapabilitySemantics/StatesToRangeMapping.cpp @@ -50,7 +50,7 @@ static constexpr double UNINITIALIZED_DOUBLE = std::numeric_limits::min( static const std::string EMPTY_JSON("{}"); /// String to identify log entries originating from this file. -static const std::string TAG("StatesToRangeMapping"); +#define TAG "StatesToRangeMapping" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/CapabilitySemantics/StatesToValueMapping.cpp b/AVSCommon/AVS/src/CapabilitySemantics/StatesToValueMapping.cpp index e48cb54111..7a5a207309 100644 --- a/AVSCommon/AVS/src/CapabilitySemantics/StatesToValueMapping.cpp +++ b/AVSCommon/AVS/src/CapabilitySemantics/StatesToValueMapping.cpp @@ -47,7 +47,7 @@ static std::string UNINITIALIZED_STRING = ""; static const std::string EMPTY_JSON("{}"); /// String to identify log entries originating from this file. -static const std::string TAG("StatesToValueMapping"); +#define TAG "StatesToValueMapping" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/ComponentConfiguration.cpp b/AVSCommon/AVS/src/ComponentConfiguration.cpp index 1429ffaf6d..65d22e939b 100644 --- a/AVSCommon/AVS/src/ComponentConfiguration.cpp +++ b/AVSCommon/AVS/src/ComponentConfiguration.cpp @@ -23,7 +23,7 @@ namespace avsCommon { namespace avs { /// String to identify log entries originating from this file. -static const std::string TAG("ComponentConfiguration"); +#define TAG "ComponentConfiguration" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/DialogUXStateAggregator.cpp b/AVSCommon/AVS/src/DialogUXStateAggregator.cpp index f581277b9f..0a97253f80 100644 --- a/AVSCommon/AVS/src/DialogUXStateAggregator.cpp +++ b/AVSCommon/AVS/src/DialogUXStateAggregator.cpp @@ -26,7 +26,7 @@ using namespace avsCommon::utils::metrics; using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("DialogUXStateAggregator"); +#define TAG "DialogUXStateAggregator" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -114,7 +114,7 @@ void DialogUXStateAggregator::addObserver(std::shared_ptronDialogUXStateChanged(m_currentState); }); @@ -131,7 +131,7 @@ void DialogUXStateAggregator::removeObserver(std::shared_ptr& audioAnalyzerState) { ACSDK_DEBUG0(LX("onStateChanged").d("SpeechSynthesizerState", state)); m_speechSynthesizerState = state; - m_executor.submit([this, state]() { + m_executor.execute([this, state]() { ACSDK_DEBUG0(LX("onStateChangedLambda").d("SpeechSynthesizerState", state)); switch (state) { case SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING: @@ -222,7 +222,7 @@ void DialogUXStateAggregator::onConnectionStatusChanged( const DialogUXStateAggregator::Status status, const std::vector& engineStatuses) { ACSDK_DEBUG(LX("onConnectionStatusChanged").d("engineAggregatedStatus", status)); - m_executor.submit([this, engineStatuses]() { + m_executor.execute([this, engineStatuses]() { bool isDisconnected = true; for (const auto& engineStatus : engineStatuses) { ACSDK_DEBUG(LX("onConnectionStatusChangedLambda") @@ -244,7 +244,7 @@ void DialogUXStateAggregator::onConnectionStatusChanged( void DialogUXStateAggregator::onRequestProcessingStarted() { ACSDK_DEBUG0(LX("onRequestProcessingStarted")); - m_executor.submit([this]() { + m_executor.execute([this]() { ACSDK_DEBUG0(LX("onRequestProcessingStartedLambda").d("currentState", m_currentState)); // Stop the listening timer m_listeningTimeoutTimer.stop(); @@ -274,7 +274,7 @@ void DialogUXStateAggregator::onRequestProcessingStarted() { void DialogUXStateAggregator::onRequestProcessingCompleted() { ACSDK_DEBUG(LX("onRequestProcessingCompleted")); - m_executor.submit([this]() { + m_executor.execute([this]() { if (DialogUXStateObserverInterface::DialogUXState::LISTENING == m_currentState) { /// It is possible that the cloud sends RPC without sending RPS. In those situations, if we are in /// LISTENING state, switch back to IDLE. @@ -295,7 +295,7 @@ void DialogUXStateAggregator::notifyObserversOfState() { void DialogUXStateAggregator::transitionFromThinkingTimedOut() { ACSDK_DEBUG5(LX("transitionFromThinkingTimedOut")); - m_executor.submit([this]() { + m_executor.execute([this]() { ACSDK_DEBUG5(LX("transitionFromThinkingTimedOutExecutor").d("m_currentState", m_currentState)); if (DialogUXStateObserverInterface::DialogUXState::THINKING == m_currentState) { ACSDK_DEBUG(LX("transitionFromThinkingTimedOut")); @@ -308,7 +308,7 @@ void DialogUXStateAggregator::transitionFromThinkingTimedOut() { void DialogUXStateAggregator::transitionFromListeningTimedOut() { ACSDK_DEBUG5(LX("transitionFromListeningTimedOut")); - m_executor.submit([this]() { + m_executor.execute([this]() { ACSDK_DEBUG5(LX("transitionFromListeningTimedOutExecutor").d("m_currentState", m_currentState)); if (DialogUXStateObserverInterface::DialogUXState::LISTENING == m_currentState) { ACSDK_DEBUG(LX("transitionFromListeningTimedOut")); @@ -321,7 +321,7 @@ void DialogUXStateAggregator::transitionFromListeningTimedOut() { void DialogUXStateAggregator::tryEnterIdleStateOnTimer() { ACSDK_DEBUG5(LX("tryEnterIdleStateOnTimer")); - m_executor.submit([this]() { + m_executor.execute([this]() { ACSDK_DEBUG5(LX("tryEnterIdleStateOnTimerExecutor") .d("m_currentState", m_currentState) .d("m_audioInputProcessorState", m_audioInputProcessorState) diff --git a/AVSCommon/AVS/src/DirectiveRoutingRule.cpp b/AVSCommon/AVS/src/DirectiveRoutingRule.cpp index 0b104c8ab1..1107417b5e 100644 --- a/AVSCommon/AVS/src/DirectiveRoutingRule.cpp +++ b/AVSCommon/AVS/src/DirectiveRoutingRule.cpp @@ -22,7 +22,7 @@ namespace avs { namespace directiveRoutingRule { /// String to identify log entries originating from this file. -static const std::string TAG("DirectiveRoutingRule"); +#define TAG "DirectiveRoutingRule" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/EditableMessageRequest.cpp b/AVSCommon/AVS/src/EditableMessageRequest.cpp index de1fb00b3f..1065870772 100644 --- a/AVSCommon/AVS/src/EditableMessageRequest.cpp +++ b/AVSCommon/AVS/src/EditableMessageRequest.cpp @@ -23,7 +23,7 @@ namespace avs { using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("EditableMessageRequest"); +#define TAG "EditableMessageRequest" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/EventBuilder.cpp b/AVSCommon/AVS/src/EventBuilder.cpp index dd5aaed250..4524d7c378 100644 --- a/AVSCommon/AVS/src/EventBuilder.cpp +++ b/AVSCommon/AVS/src/EventBuilder.cpp @@ -31,7 +31,7 @@ using namespace utils; using namespace avs::constants; /// String to identify log entries originating from this file. -static const std::string TAG("EventBuilder"); +#define TAG "EventBuilder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/ExceptionEncounteredSender.cpp b/AVSCommon/AVS/src/ExceptionEncounteredSender.cpp index 0806bdafa1..e5930aa582 100644 --- a/AVSCommon/AVS/src/ExceptionEncounteredSender.cpp +++ b/AVSCommon/AVS/src/ExceptionEncounteredSender.cpp @@ -32,7 +32,7 @@ using namespace rapidjson; using namespace alexaClientSDK::avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("ExceptionEncountered"); +#define TAG "ExceptionEncountered" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/src/Initialization/SDKPrimitivesProvider.cpp b/AVSCommon/AVS/src/Initialization/SDKPrimitivesProvider.cpp index bf0bc33db0..ef769e9966 100644 --- a/AVSCommon/AVS/src/Initialization/SDKPrimitivesProvider.cpp +++ b/AVSCommon/AVS/src/Initialization/SDKPrimitivesProvider.cpp @@ -23,7 +23,7 @@ namespace avs { namespace initialization { /// String to identify log entries originating from this file. -static const std::string TAG("SDKPrimitivesProvider"); +#define TAG "SDKPrimitivesProvider" std::shared_ptr SDKPrimitivesProvider::m_provider; std::mutex SDKPrimitivesProvider::m_mutex; diff --git a/AVSCommon/AVS/src/MessageRequest.cpp b/AVSCommon/AVS/src/MessageRequest.cpp index f1e89005eb..1afd2cc9f4 100644 --- a/AVSCommon/AVS/src/MessageRequest.cpp +++ b/AVSCommon/AVS/src/MessageRequest.cpp @@ -15,16 +15,30 @@ #include "AVSCommon/AVS/MessageRequest.h" #include "AVSCommon/AVS/EditableMessageRequest.h" +#include #include "AVSCommon/Utils/Logger/Logger.h" namespace alexaClientSDK { namespace avsCommon { namespace avs { +using namespace avsCommon::utils::json::jsonUtils; using namespace sdkInterfaces; +/// Event key. +static const std::string EVENT{"event"}; + +/// Header key. +static const std::string EVENT_HEADER{"header"}; + +/// Event header key for the namespace field. +static const std::string EVENT_HEADER_NAMESPACE{"namespace"}; + +/// Event header key for the name field. +static const std::string EVENT_HEADER_NAME{"name"}; + /// String to identify log entries originating from this file. -static const std::string TAG("MessageRequest"); +#define TAG "MessageRequest" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -110,7 +124,7 @@ std::string MessageRequest::getUriPathExtension() const { } int MessageRequest::attachmentReadersCount() const { - return m_readers.size(); + return static_cast(m_readers.size()); } std::string MessageRequest::getStreamMetricName() const { return m_streamMetricName; @@ -180,6 +194,48 @@ void MessageRequest::removeObserver( m_observers.erase(observer); } +MessageRequest::EventHeaders MessageRequest::retrieveEventHeaders() const { + // This could be more efficient if parsed in the constructor. However, if this optimization is done in the future + // EditableMessageRequest will need to also refresh the cache, and thready safety for both classes would need to be + // re-evaluated. + rapidjson::Document document; + auto eventHeaders = MessageRequest::EventHeaders(); + + auto eventExists = parseJSON(this->getJsonContent(), &document); + if (!eventExists) { + ACSDK_ERROR(LX("retrieveEventHeadersFailed").d("reason", "Parsing error")); + return eventHeaders; + } + + auto event = document.FindMember(EVENT); + if (event == document.MemberEnd()) { + ACSDK_ERROR(LX("retrieveEventHeadersFailed").d("reason", "No event found in json")); + return eventHeaders; + } + + auto headers = event->value.FindMember(EVENT_HEADER); + if (headers == event->value.MemberEnd()) { + ACSDK_ERROR(LX("retrieveEventHeadersFailed").d("reason", "No event headers found")); + return eventHeaders; + } + + auto field = headers->value.FindMember(EVENT_HEADER_NAMESPACE); + if (field != headers->value.MemberEnd() && field->value.IsString()) { + eventHeaders.eventNamespace = field->value.GetString(); + } else { + ACSDK_ERROR(LX("retrieveEventHeadersFailed").d("reason", "No event namespace found")); + } + + field = headers->value.FindMember(EVENT_HEADER_NAME); + if (field != headers->value.MemberEnd() && field->value.IsString()) { + eventHeaders.eventName = field->value.GetString(); + } else { + ACSDK_ERROR(LX("retrieveEventHeadersFailed").d("reason", "No event name found")); + } + + return eventHeaders; +} + const std::vector>& MessageRequest::getHeaders() const { return m_headers; } diff --git a/AVSCommon/AVS/src/WaitableMessageRequest.cpp b/AVSCommon/AVS/src/WaitableMessageRequest.cpp index 73ab9ab482..1d57488b6b 100644 --- a/AVSCommon/AVS/src/WaitableMessageRequest.cpp +++ b/AVSCommon/AVS/src/WaitableMessageRequest.cpp @@ -24,7 +24,7 @@ namespace avs { using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("WaitableMessageRequest"); +#define TAG "WaitableMessageRequest" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/AVS/test/Attachment/AttachmentReaderTest.cpp b/AVSCommon/AVS/test/Attachment/AttachmentReaderTest.cpp index 59d447e6b4..ecb7d10a72 100644 --- a/AVSCommon/AVS/test/Attachment/AttachmentReaderTest.cpp +++ b/AVSCommon/AVS/test/Attachment/AttachmentReaderTest.cpp @@ -122,7 +122,7 @@ void AttachmentReaderTest::testMultipleReads(bool closeWriterBeforeReading) { std::vector result(TEST_SDS_PARTIAL_READ_AMOUNT_IN_BYTES); auto readStatus = InProcessAttachmentReader::ReadStatus::OK; - int totalBytesRead = 0; + size_t totalBytesRead = 0; bool done = false; int iterations = 0; int iterationsMax = 10; @@ -145,7 +145,7 @@ void AttachmentReaderTest::testMultipleReads(bool closeWriterBeforeReading) { // Not only was all the data read, but the reader remained open. ASSERT_NE(iterations, iterationsMax); ASSERT_EQ(readStatus, terminalStatus); - ASSERT_EQ(totalBytesRead, static_cast(m_testPattern.size())); + ASSERT_EQ(totalBytesRead, m_testPattern.size()); } void AttachmentReaderTest::readAndVerifyResult( diff --git a/AVSCommon/AVS/test/Attachment/AttachmentWriterTest.cpp b/AVSCommon/AVS/test/Attachment/AttachmentWriterTest.cpp index 64655407fa..6c432a6e28 100644 --- a/AVSCommon/AVS/test/Attachment/AttachmentWriterTest.cpp +++ b/AVSCommon/AVS/test/Attachment/AttachmentWriterTest.cpp @@ -89,7 +89,7 @@ void AttachmentWriterTest::testMultipleReads(bool closeWriterBeforeReading) { std::vector result(TEST_SDS_PARTIAL_READ_AMOUNT_IN_BYTES); auto readStatus = InProcessAttachmentReader::ReadStatus::OK; - int totalBytesRead = 0; + size_t totalBytesRead = 0; bool done = false; int iterations = 0; int iterationsMax = 10; @@ -111,7 +111,7 @@ void AttachmentWriterTest::testMultipleReads(bool closeWriterBeforeReading) { ASSERT_NE(iterations, iterationsMax); ASSERT_EQ(readStatus, terminalStatus); - ASSERT_EQ(totalBytesRead, static_cast(m_testPattern.size())); + ASSERT_EQ(totalBytesRead, m_testPattern.size()); } /** diff --git a/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp b/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp index 267c2ba555..08c0fb274c 100644 --- a/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp +++ b/AVSCommon/AVS/test/DialogUXStateAggregatorTest.cpp @@ -25,6 +25,7 @@ namespace test { using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils::mediaPlayer; using namespace std; /// Long time out for observers to wait for the state change callback (we should not reach this). @@ -40,7 +41,7 @@ static const auto SHORT_TIMEOUT = std::chrono::milliseconds(50); static const auto TRANSITION_TIMEOUT = std::chrono::milliseconds(300); /// Dummy value for a media player source id -static const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId TEST_SOURCE_ID = -1; +static const MediaPlayerInterface::SourceId TEST_SOURCE_ID = static_cast(-1); /// A test observer that mocks out the DialogUXStateObserverInterface##onDialogUXStateChanged() call. class TestObserver : public DialogUXStateObserverInterface { diff --git a/AVSCommon/AVS/test/MessageRequestTest.cpp b/AVSCommon/AVS/test/MessageRequestTest.cpp index 1f5ddd8ced..304fe9f796 100644 --- a/AVSCommon/AVS/test/MessageRequestTest.cpp +++ b/AVSCommon/AVS/test/MessageRequestTest.cpp @@ -22,11 +22,70 @@ using namespace ::testing; namespace alexaClientSDK { - namespace avsCommon { namespace avs { namespace test { +/// Valid test event. +// clang-format off +static const std::string VALID_TEST_EVENT = R"({ + "event": { + "header": { + "namespace": "test_namespace", + "name": "test_name", + "messageId": "test_messageId", + "dialogRequestId": "test_dialogRequestId" + }, + "payload": {} + } +})"; +// clang-format on + +/// Partially valid test event. +// clang-format off +static const std::string PARTIALLY_VALID_TEST_EVENT = R"({ + "event": { + "header": { + "namespace": "test_namespace", + "messageId": "test_messageId", + "dialogRequestId": "test_dialogRequestId" + }, + "payload": {} + } +})"; +// clang-format on + +/// Incorrectly formatted test event. +// clang-format off +static const std::string INCORRECTLY_FORMATTED_TEST_EVENT = R"({ + "event": { + "namespace": "test_namespace", + "messageId": "test_messageId", + "dialogRequestId": "test_dialogRequestId", + "payload": {} + } +})"; +// clang-format on + +/// Invalid test event. +// clang-format off +static const std::string INVALID_TEST_EVENT = R"({ + "event": { + "header": { + "messageId": "test_messageId", + "dialogRequestId": "test_dialogRequestId" + }, + "payload": {} + } +})"; +// clang-format on + +/// Test event namespace header value. +static const std::string TEST_NAMESPACE = "test_namespace"; + +/// Test event name header value. +static const std::string TEST_NAME = "test_name"; + class MockAttachmentReader : public attachment::AttachmentReader { public: MOCK_METHOD4( @@ -87,6 +146,42 @@ TEST_F(MessageRequestTest, test_extraHeaders) { EXPECT_EQ(expected, actual); } +TEST_F(MessageRequestTest, test_eventHeaders) { + auto expectedEventHeaders = MessageRequest::EventHeaders(TEST_NAMESPACE, TEST_NAME); + MessageRequest messageRequest(VALID_TEST_EVENT, true, "", {}); + auto actualEventHeaders = messageRequest.retrieveEventHeaders(); + + EXPECT_EQ(expectedEventHeaders.eventNamespace, actualEventHeaders.eventNamespace); + EXPECT_EQ(expectedEventHeaders.eventName, actualEventHeaders.eventName); +} + +TEST_F(MessageRequestTest, test_partialEventHeaders) { + auto expectedEventHeaders = MessageRequest::EventHeaders(TEST_NAMESPACE, ""); + MessageRequest messageRequest(PARTIALLY_VALID_TEST_EVENT, true, "", {}); + auto actualEventHeaders = messageRequest.retrieveEventHeaders(); + + EXPECT_EQ(expectedEventHeaders.eventNamespace, actualEventHeaders.eventNamespace); + EXPECT_EQ(expectedEventHeaders.eventName, actualEventHeaders.eventName); +} + +TEST_F(MessageRequestTest, test_incorrectlyFormattedEventHeaders) { + auto expectedEventHeaders = MessageRequest::EventHeaders(); + MessageRequest messageRequest(INCORRECTLY_FORMATTED_TEST_EVENT, true, "", {}); + auto actualEventHeaders = messageRequest.retrieveEventHeaders(); + + EXPECT_EQ(expectedEventHeaders.eventNamespace, actualEventHeaders.eventNamespace); + EXPECT_EQ(expectedEventHeaders.eventName, actualEventHeaders.eventName); +} + +TEST_F(MessageRequestTest, test_emptyEventHeaders) { + auto expectedEventHeaders = MessageRequest::EventHeaders(); + MessageRequest messageRequest(INVALID_TEST_EVENT, true, "", {}); + auto actualEventHeaders = messageRequest.retrieveEventHeaders(); + + EXPECT_EQ(expectedEventHeaders.eventNamespace, actualEventHeaders.eventNamespace); + EXPECT_EQ(expectedEventHeaders.eventName, actualEventHeaders.eventName); +} + TEST_F(MessageRequestTest, test_isResolved) { MessageRequest resolvedReq("{}", true, ""); EXPECT_TRUE(resolvedReq.isResolved()); diff --git a/AVSCommon/CMakeLists.txt b/AVSCommon/CMakeLists.txt index 0ff9cb433b..9c5d181fbc 100644 --- a/AVSCommon/CMakeLists.txt +++ b/AVSCommon/CMakeLists.txt @@ -44,7 +44,6 @@ add_library(AVSCommon Utils/src/BluetoothEventBus.cpp Utils/src/Configuration/ConfigurationNode.cpp Utils/src/DeviceInfo.cpp - Utils/src/Executor.cpp Utils/src/FileUtils.cpp Utils/src/FormattedAudioStreamAdapter.cpp Utils/src/JSON/JSONGenerator.cpp @@ -106,6 +105,9 @@ add_library(AVSCommon Utils/src/TaskThread.cpp Utils/src/ThreadPool.cpp Utils/src/Threading/ConditionVariableWrapper.cpp + Utils/src/Threading/ExecutorFactory.cpp + Utils/src/Threading/Executor.cpp + Utils/src/Threading/SharedExecutor.cpp Utils/src/TimePoint.cpp Utils/src/TimeUtils.cpp Utils/src/Timer.cpp @@ -127,6 +129,8 @@ target_include_directories(AVSCommon PUBLIC "${MultipartParser_SOURCE_DIR}" ${CURL_INCLUDE_DIRS}) +target_include_directories(AVSCommon PRIVATE "${AVSCommon_SOURCE_DIR}/Utils/privateInclude") + if (MSVC) target_include_directories(AVSCommon PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/exports") @@ -146,6 +150,7 @@ target_link_libraries(AVSCommon acsdkApplicationAudioPipelineFactoryInterfaces acsdkEqualizerInterfaces acsdkInteractionModelInterfaces + acsdkNotifierInterfaces ) # install target diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AlexaInterfaceMessageSenderInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AlexaInterfaceMessageSenderInterface.h index 01191c80ce..519b8e8e8a 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AlexaInterfaceMessageSenderInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AlexaInterfaceMessageSenderInterface.h @@ -30,7 +30,8 @@ namespace sdkInterfaces { */ class AlexaInterfaceMessageSenderInterface { public: - /// The type of error when calling sendErrorResponseEvent(). + /// The type of error when calling sendErrorResponseEvent() with @c Alexa.ErrorResponse event. + /// https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-errorresponse.html enum class ErrorResponseType { /// The operation can't be performed because the endpoint is already in operation. ALREADY_IN_OPERATION, @@ -96,6 +97,41 @@ class AlexaInterfaceMessageSenderInterface { VALUE_OUT_OF_RANGE }; + /// The type of video error when calling sendErrorResponseEvent() with @c Alexa.Video.ErrorResponse event. + /// https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-video-errorresponse.html + enum class AlexaVideoErrorResponseType { + /// Indicates the content does not allow the action requested. For example, if the user tries to delete + /// a recording that is marked as not deletable. + ACTION_NOT_PERMITTED_FOR_CONTENT, + + /// Indicates an additional confirmation must occur before the requested action can be completed. + CONFIRMATION_REQUIRED, + + /// Indicates the record operation failed due to restrictions on the content. + CONTENT_NOT_RECORDABLE, + + /// The user is not subscribed to the content for a channel or other subscription-based content. + NOT_SUBSCRIBED, + + /// Indicates that a recording request failed because the recording already exists. + RECORDING_EXISTS, + + /// Indicates that a recording request failed because the DVR storage is full. + STORAGE_FULL, + + /// Indicates the title specified yielded multiple results, and disambiguation is required to determine + /// the program to record. This value should be used to indicate that the target device will provide a + /// mechanism for disambiguation. For example, this error could indicate that there are multiple airings + /// of a program or that the entity requested for recording has multiple programs associated with it. + TITLE_DISAMBIGUATION_REQUIRED, + + /// Indicates that a recording request failed because of a scheduling conflict with another recording. + TUNER_OCCUPIED, + + /// Indicates an invalid error type + NONE + }; + /** * Destructor. */ @@ -189,6 +225,15 @@ class AlexaInterfaceMessageSenderInterface { const std::string& correlationToken, const int estimatedDeferralInSeconds = 0) = 0; + /** + * Convert @c AlexaVideoErrorResponseType type to its corresponding string. Note that any invalid + * @c AlexaVideoErrorResponseType will return an empty string. + * + * @param responseType the response type to convert. + * @return the corresponding string for video error response type + */ + static std::string alexaVideoErrorResponseToString(AlexaVideoErrorResponseType responseType); + /** * Convert an AlexaResponseType to its corresponding ErrorResponseType. Note that any AlexaResponseType that does * not map to ErrorResponseType will return INTERNAL_ERROR. @@ -199,6 +244,31 @@ class AlexaInterfaceMessageSenderInterface { static ErrorResponseType alexaResponseTypeToErrorType(const avsCommon::avs::AlexaResponseType responseType); }; +inline std::string AlexaInterfaceMessageSenderInterface::alexaVideoErrorResponseToString( + AlexaVideoErrorResponseType responseType) { + switch (responseType) { + case AlexaVideoErrorResponseType::ACTION_NOT_PERMITTED_FOR_CONTENT: + return "ACTION_NOT_PERMITTED_FOR_CONTENT"; + case AlexaVideoErrorResponseType::CONFIRMATION_REQUIRED: + return "CONFIRMATION_REQUIRED"; + case AlexaVideoErrorResponseType::CONTENT_NOT_RECORDABLE: + return "CONTENT_NOT_RECORDABLE"; + case AlexaVideoErrorResponseType::NOT_SUBSCRIBED: + return "NOT_SUBSCRIBED"; + case AlexaVideoErrorResponseType::RECORDING_EXISTS: + return "RECORDING_EXISTS"; + case AlexaVideoErrorResponseType::STORAGE_FULL: + return "STORAGE_FULL"; + case AlexaVideoErrorResponseType::TITLE_DISAMBIGUATION_REQUIRED: + return "TITLE_DISAMBIGUATION_REQUIRED"; + case AlexaVideoErrorResponseType::TUNER_OCCUPIED: + return "TUNER_OCCUPIED"; + case AlexaVideoErrorResponseType::NONE: + return ""; + } + return "UNKNOWN"; +} + inline AlexaInterfaceMessageSenderInterface::ErrorResponseType AlexaInterfaceMessageSenderInterface:: alexaResponseTypeToErrorType(const avsCommon::avs::AlexaResponseType responseType) { switch (responseType) { diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioInputProcessorObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioInputProcessorObserverInterface.h index b99e176513..5b926f28ab 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioInputProcessorObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioInputProcessorObserverInterface.h @@ -55,6 +55,14 @@ class AudioInputProcessorObserverInterface { */ virtual void onStateChanged(State state) = 0; + /** + * This function is called when the active @c ASRProfile changes. + * + * @param profile The string representation of the active @c ASRProfile. + * Use the associated getASRProfile to retrieve the @c ASRProfile value. + */ + virtual void onASRProfileChanged(const std::string& profile){}; + /** * This function converts the provided @c State to a string. * diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h index 9d6297e9f4..96321071c6 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/BluetoothDeviceInterface.h @@ -209,6 +209,17 @@ class BluetoothDeviceInterface { */ virtual std::future disconnect() = 0; + /** + * Sets the pairing pin for the current pairing attempt. PIN length can range from 4 to 16 + * alphanumeric characters, though most devices will only accept numeric characters in the PIN. + * Expected call flow is: + * pair() -> PIN request callback -> setPairingPin() + * + * @param pin BT pairing pin + * @return Indicates whether pairing pin was set. + */ + virtual bool setPairingPin(const std::string& pin) = 0; + /// @return The Bluetooth Services that this device supports. virtual std::vector> getSupportedServices() = 0; diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h index d5cad86798..fe3db20de6 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/Bluetooth/Services/BluetoothServiceInterface.h @@ -28,8 +28,8 @@ namespace services { /** * Interface representing a Bluetooth Service. - * More Bluetooth Service information(e.g, UUID, NAME) could be found at - * https://www.bluetooth.com/specifications/assigned-numbers/service-discovery/ + * More Bluetooth Service information(e.g, UUID, NAME) could be found in the service discovery section at + * https://www.bluetooth.com/specifications/assigned-numbers/ */ class BluetoothServiceInterface { public: diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h index bea7e4e067..f4a0c43d97 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CallStateObserverInterface.h @@ -59,6 +59,10 @@ class CallStateObserverInterface { std::string inboundCalleeName; /// Textual description of exact call provider type. std::string callProviderType; + /// Call provider image url. + /// This image url may be null. For the case the provider image is not provided by url, + /// the application should have local image to display instead of downloading from this url. + std::string callProviderImageUrl; /// Inbound ringtone url. std::string inboundRingtoneUrl; /// Outbound ringtone url. diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/GUIActivityEvent.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/GUIActivityEvent.h new file mode 100644 index 0000000000..da674c736c --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/GUIActivityEvent.h @@ -0,0 +1,94 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_GUIACTIVITYEVENT_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_GUIACTIVITYEVENT_H_ + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/// Enumeration of activity events used to indicate the state of a GUI +enum class GUIActivityEvent { + /// GUI switched to active state (audio/video started). + ACTIVATED, + + /// GUI become inactive. + DEACTIVATED, + + /// Interrupt event (touch/scroll/etc). + INTERRUPT, + + /// Guard option for unknown received state. + UNKNOWN +}; + +/** + * This function converts the provided string to an @c GUIActivityEvent. + * + * @param string The string to convert to @c GUIActivityEvent. + * @return The @c GUIActivityEvent. + */ +inline GUIActivityEvent guiActivityEventFromString(const std::string& string) { + if ("ACTIVATED" == string) { + return GUIActivityEvent::ACTIVATED; + } else if ("DEACTIVATED" == string) { + return GUIActivityEvent::DEACTIVATED; + } else if ("INTERRUPT" == string) { + return GUIActivityEvent::INTERRUPT; + } else { + return GUIActivityEvent::UNKNOWN; + } +} + +/** + * This function converts a GUIActivityEvent to a string. + * + * @param event the GUIActivityEvent to be converted. + * @return the string representation of the GUIActivityEvent. + */ +inline std::string guiActivityEventToString(GUIActivityEvent event) { + switch (event) { + case GUIActivityEvent::ACTIVATED: + return "ACTIVATED"; + case GUIActivityEvent::DEACTIVATED: + return "DEACTIVATED"; + case GUIActivityEvent::INTERRUPT: + return "INTERRUPT"; + case GUIActivityEvent::UNKNOWN: + return "UNKNOWN"; + } + return "UNKNOWN"; +} + +/** + * Write an @c GUIActivityEvent value to an @c ostream as a string. + * + * @param stream The stream to write the value to. + * @param state The @c GUIActivityEvent value. + * @return The @c ostream that was passed in and written to. + */ +inline std::ostream& operator<<(std::ostream& stream, const GUIActivityEvent& activityEvent) { + return stream << guiActivityEventToString(activityEvent); +} + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_GUIACTIVITYEVENT_H_ \ No newline at end of file diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/GUIActivityEventObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/GUIActivityEventObserverInterface.h new file mode 100644 index 0000000000..a18231952a --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/GUIActivityEventObserverInterface.h @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_GUIACTIVITYEVENTOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_GUIACTIVITYEVENTOBSERVERINTERFACE_H_ + +#include "GUIActivityEvent.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * Observer interface for @c GUIActivityEvent + */ +class GUIActivityEventObserverInterface { +public: + virtual ~GUIActivityEventObserverInterface() = default; + + /** + * Observer method to be notified of activity event + * @param source The source of the activity event. + * @param event The activity event. + */ + virtual void onGUIActivityEventReceived(const std::string& source, const GUIActivityEvent& activityEvent) = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_GUIACTIVITYEVENTOBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PostConnectOperationInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PostConnectOperationInterface.h index 9fd4e34536..4a3cc855c2 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PostConnectOperationInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/PostConnectOperationInterface.h @@ -46,15 +46,16 @@ class PostConnectOperationInterface { /** * Returns the operation priority. The Priority is used to order the sequence of operations in ascending order. * - * @return unsigined int that representing the operation priority. + * @return unsigned int that representing the operation priority. */ virtual unsigned int getOperationPriority() = 0; /** - * Performs the post connect operation. The implementation should ensure that the performOperation returns - * immediately after the abortOperation() method is called. + * Performs the post connect operation. The implementation should ensure that the #performOperation() returns + * immediately after the #abortOperation() method is called. If #abortOperation() is called before + * #performOperation(), the method must immediately return with false result. * - * @note: The performOperation() method is not expected to be called twice. + * @note This method is not expected to be called twice. * * @param messageSender - The @c MessageSenderInterface to send post connect message. * @return True if the post connect operation is successful, else false. @@ -65,7 +66,8 @@ class PostConnectOperationInterface { /** * Aborts an operation that is currently being executed using the performOperation() method. * - * Note: This method will be called from a different thread from where the performOperation() is being called from. + * @note This method will be called from a different thread from where the performOperation() is being called from. + * It is possible, that the method is called before #performOperation() call is made. */ virtual void abortOperation() = 0; }; diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h index 60f097547c..72265233de 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeakerManagerInterface.h @@ -76,6 +76,25 @@ class SpeakerManagerInterface { int8_t volume, const NotificationProperties& properties) = 0; + /** + * Handle an external volume/mute state event in the system and update the settings. + * + * A volume could be changed either using SpeakerManager instance or using any other component + * which support volume change. In the case, volume on the device is being updated by some + * other component, this interface could be used to update the speaker settings of the + * associated @c ChannelVolumeInterface. This interface does not modify/change the volume + * or mute. It should be used to update speaker settings within SpeakerManager and notify + * AVS/observers if required of this change. + * + * @param type The type of @c ChannelVolumeInterface to retrieve settings for. + * @param speakerSettings New updated value. Values must be between [0,100] + * @param properties Notification properties that specify how the volume change will be notified. + */ + virtual void onExternalSpeakerSettingsUpdate( + ChannelVolumeInterface::Type type, + const SpeakerInterface::SpeakerSettings& speakerSettings, + const NotificationProperties& properties){}; + /** * Adjusts the volume for ChannelVolumeInterfaces of a certain @c Type with a volume delta. * diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeechInteractionHandlerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeechInteractionHandlerInterface.h index 830eadc351..d4e85c69eb 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeechInteractionHandlerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/SpeechInteractionHandlerInterface.h @@ -21,6 +21,7 @@ #include #include +#include #include #include diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDevice.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDevice.h index b9be92e81f..e7b1d0a337 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDevice.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Bluetooth/MockBluetoothDevice.h @@ -30,6 +30,10 @@ namespace bluetooth { namespace test { using namespace ::testing; + +static constexpr std::size_t PAIRING_PIN_LENGTH_MIN = 4; +static constexpr std::size_t PAIRING_PIN_LENGTH_MAX = 16; + /** * Mock class that implements BluetoothDeviceInterface. * Please note that MockBluetoothDevice doesn't support sending any @c BluetoothEvent to @c BluetoothEventBus, @@ -47,6 +51,7 @@ class MockBluetoothDevice : public BluetoothDeviceInterface { bool isConnected() override; std::future connect() override; std::future disconnect() override; + bool setPairingPin(const std::string& pin) override; std::vector> getSupportedServices() override; std::shared_ptr getService(std::string uuid) override; utils::bluetooth::MediaStreamingState getStreamingState() override; @@ -132,6 +137,13 @@ inline std::future MockBluetoothDevice::disconnect() { return connectionPromise.get_future(); } +inline bool MockBluetoothDevice::setPairingPin(const std::string& pin) { + if (pin.length() < PAIRING_PIN_LENGTH_MIN || pin.length() > PAIRING_PIN_LENGTH_MAX) { + return false; + } + return true; +} + inline std::vector> MockBluetoothDevice::getSupportedServices() { std::vector> services; for (auto service : m_supportedServices) { diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Endpoints/MockEndpointBuilder.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Endpoints/MockEndpointBuilder.h index e5db640773..cf5328c1d4 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Endpoints/MockEndpointBuilder.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/Endpoints/MockEndpointBuilder.h @@ -32,6 +32,7 @@ class MockEndpointBuilder : public EndpointBuilderInterface { /// @name @c EndpointBuilderInterface methods to be mocked. /// @{ MOCK_METHOD1(withDerivedEndpointId, EndpointBuilderInterface&(const std::string& suffix)); + MOCK_METHOD0(withDeviceRegistration, EndpointBuilderInterface&()); MOCK_METHOD1(withEndpointId, EndpointBuilderInterface&(const EndpointIdentifier& endpointId)); MOCK_METHOD1(withFriendlyName, EndpointBuilderInterface&(const std::string& friendlyName)); MOCK_METHOD1(withDescription, EndpointBuilderInterface&(const std::string& description)); @@ -50,7 +51,61 @@ class MockEndpointBuilder : public EndpointBuilderInterface { withConnections, EndpointBuilderInterface&(const std::vector>& connections)); MOCK_METHOD1(withCookies, EndpointBuilderInterface&(const std::map& cookies)); - MOCK_METHOD0(build, utils::Optional()); + MOCK_METHOD3( + withPowerController, + EndpointBuilderInterface&( + std::shared_ptr powerController, + bool isProactivelyReported, + bool isRetrievable)); + MOCK_METHOD6( + withToggleController, + EndpointBuilderInterface&( + std::shared_ptr toggleController, + const std::string& instance, + const avsCommon::sdkInterfaces::toggleController::ToggleControllerAttributes& toggleControllerAttributes, + bool isProactivelyReported, + bool isRetrievable, + bool isNonControllable)); + MOCK_METHOD6( + withModeController, + EndpointBuilderInterface&( + std::shared_ptr modeController, + const std::string& instance, + const avsCommon::sdkInterfaces::modeController::ModeControllerAttributes& modeControllerAttributes, + bool isProactivelyReported, + bool isRetrievable, + bool isNonControllable)); + MOCK_METHOD6( + withRangeController, + EndpointBuilderInterface&( + std::shared_ptr rangeController, + const std::string& instance, + const avsCommon::sdkInterfaces::rangeController::RangeControllerAttributes& rangeControllerAttributes, + bool isProactivelyReported, + bool isRetrievable, + bool isNonControllable)); + MOCK_METHOD1( + withEndpointCapabilitiesBuilder, + EndpointBuilderInterface&( + const std::shared_ptr&)); + MOCK_METHOD0(build, std::unique_ptr()); + /// @} + /// @name @c EndpointCapabilitiesRegistrarInterface methods to be mocked. + /// @{ + MOCK_METHOD2( + withCapability, + EndpointCapabilitiesRegistrarInterface&( + const avs::CapabilityConfiguration& configuration, + std::shared_ptr directiveHandler)); + MOCK_METHOD2( + withCapability, + EndpointCapabilitiesRegistrarInterface&( + const std::shared_ptr& configurationInterface, + std::shared_ptr directiveHandler)); + MOCK_METHOD1( + withCapabilityConfiguration, + EndpointCapabilitiesRegistrarInterface&( + const std::shared_ptr& configurationInterface)); /// @} }; diff --git a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h index bdc1df2421..95bc739d2d 100644 --- a/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h +++ b/AVSCommon/SDKInterfaces/test/AVSCommon/SDKInterfaces/MockSpeakerManager.h @@ -34,6 +34,13 @@ class MockSpeakerManager : public SpeakerManagerInterface { int8_t volume, const avsCommon::sdkInterfaces::SpeakerManagerInterface::NotificationProperties& properties)); + MOCK_METHOD3( + onExternalSpeakerSettingsUpdate, + void( + avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, + const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& speakerSettings, + const avsCommon::sdkInterfaces::SpeakerManagerInterface::NotificationProperties& properties)); + MOCK_METHOD3( adjustVolume, std::future( diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Configuration/ConfigurationNode.h b/AVSCommon/Utils/include/AVSCommon/Utils/Configuration/ConfigurationNode.h index cf57d30013..73b1d9a5bd 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Configuration/ConfigurationNode.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Configuration/ConfigurationNode.h @@ -194,6 +194,15 @@ class ConfigurationNode { */ ConfigurationNode operator[](const std::string& key) const; + /** + * Get @c ConfigurationNode value for @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. + */ + ConfigurationNode getChildNode(const char* key) const; + /** * operator bool(). Indicates of the @c ConfigurationNode references a valid object. * diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Error/FinallyGuard.h b/AVSCommon/Utils/include/AVSCommon/Utils/Error/FinallyGuard.h index d65c4076e3..8d4357d9de 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Error/FinallyGuard.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Error/FinallyGuard.h @@ -51,6 +51,13 @@ class FinallyGuard { */ FinallyGuard(const std::function& finallyFunction); + /** + * 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. */ @@ -64,6 +71,10 @@ class FinallyGuard { inline FinallyGuard::FinallyGuard(const std::function& finallyFunction) : m_function{finallyFunction} { } +inline FinallyGuard::FinallyGuard(const std::function&& finallyFunction) : + m_function{std::move(finallyFunction)} { +} + inline FinallyGuard::~FinallyGuard() { if (m_function) { m_function(); diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONGenerator.h b/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONGenerator.h index 644a8499c4..8533842e76 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONGenerator.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/JSON/JSONGenerator.h @@ -210,7 +210,8 @@ class JsonGenerator { template bool JsonGenerator::addStringArray(const std::string& key, const CollectionT& collection) { - if (!checkWriter() || !m_writer.Key(key.c_str(), key.length())) { + auto keyLength = static_cast(key.length()); + if (!checkWriter() || !m_writer.Key(key.c_str(), keyLength)) { return false; } m_writer.StartArray(); @@ -224,7 +225,8 @@ bool JsonGenerator::addStringArray(const std::string& key, const CollectionT& co template bool JsonGenerator::addMembersArray(const std::string& key, const CollectionT& collection) { - if (!checkWriter() || !m_writer.Key(key.c_str(), key.length())) { + auto keyLength = static_cast(key.length()); + if (!checkWriter() || !m_writer.Key(key.c_str(), keyLength)) { return false; } m_writer.StartArray(); @@ -238,7 +240,8 @@ bool JsonGenerator::addMembersArray(const std::string& key, const CollectionT& c template bool JsonGenerator::addCollectionOfStringArray(const std::string& key, const CollectionArrayT& collection) { - if (!checkWriter() || !m_writer.Key(key.c_str(), key.length())) { + auto keyLength = static_cast(key.length()); + if (!checkWriter() || !m_writer.Key(key.c_str(), keyLength)) { return false; } m_writer.StartArray(); diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlMultiHandleWrapper.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlMultiHandleWrapper.h index ecaa3632d1..3d8a277213 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlMultiHandleWrapper.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/CurlMultiHandleWrapper.h @@ -80,13 +80,29 @@ class CurlMultiHandleWrapper { CURLMcode perform(int* runningHandles); /** - * Wait for actions to perform on the @c libcurl @c handles added to this @c libcurl @c multi @c handle. + * Poll for actions to perform on the @c libcurl @c handles added to this @c libcurl @c multi @c handle. This can + * waken up by the @c wakeup() call. + * + * @note For Libcurl v7.68.0 or higher, this function will call curl_multi_poll(), this allows the polling thread to + * be woken up by the @c wakeup() call. Otherwise for Libcurl v7.67.0 or lower, it will call curl_multi_wait() and + * the call to @c wakeup() will do nothing. + * * * @param timeout How long to wait for actions to perform. * @param[out] countHandlesUpdated The number of handles for which actions are ready to be performed. * @return @c libcurl code indicating the result of this operation. */ - CURLMcode wait(std::chrono::milliseconds timeout, int* countHandlesUpdated); + CURLMcode poll(std::chrono::milliseconds timeout, int* countHandlesUpdated); + + /** + * This function can be called from any thread to wake up a sleeping @c poll(). + * + * @note For Libcurl v7.68.0 or higher, this function will call curl_multi_wakeup(). Otherwise, this is a no-op + * operation. + * + * @return @c true if the call succeeded, or @c false otherwise. + */ + bool wakeup(); /** * Receive the next messages about the @c libcurl @c handles added to this @c libcurl @c multi @c handle. diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h index 570025f4b9..b84e090b95 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/LibcurlUtils/LibcurlHTTP2Connection.h @@ -69,7 +69,10 @@ class LibcurlHTTP2Connection : public avsCommon::utils::http2::HTTP2ConnectionIn LibcurlHTTP2Connection( const std::shared_ptr& setCurlOptionsCallback = nullptr); -private: + /** + * Alias for c++ ordered map where key is curl handle and value is pointer to LibcurlHTTP2Request. + */ + using ActiveStreamMap = std::map>; /** * Adds a configured stream into this connection. * @param stream Request object with a configured curl handle. @@ -86,10 +89,10 @@ class LibcurlHTTP2Connection : public avsCommon::utils::http2::HTTP2ConnectionIn /** Release a stream. * - * @param stream The stream to release. + * @param[in,out] iterator The iterator to ActiveStreamMap to erase. * @return Whether the operation was successful. */ - bool releaseStream(LibcurlHTTP2Request& stream); + bool releaseStream(ActiveStreamMap::iterator& iterator); /** * Main network loop. Repeatedly call curl_multi_perform in order to transfer data on the incorporated streams. @@ -134,10 +137,10 @@ class LibcurlHTTP2Connection : public avsCommon::utils::http2::HTTP2ConnectionIn /** * Cancel an active stream and report CANCELLED completion status. * - * @param stream The stream to cancel. + * @param[in,out] iterator The iterator to ActiveStreamMap to erase. * @return Whether the operation was successful. */ - bool cancelActiveStream(LibcurlHTTP2Request& stream); + bool cancelActiveStream(ActiveStreamMap::iterator& iterator); /** * Release any active streams and report CANCELLED completion status. @@ -174,8 +177,8 @@ class LibcurlHTTP2Connection : public avsCommon::utils::http2::HTTP2ConnectionIn /// 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; + /// Represents a CURL multi handle. Intended to only be accessed by the network loop thread and in @c addStream. + std::shared_ptr m_multi; /// Serializes concurrent access to the m_requestQueue and m_isStopping members. std::mutex m_mutex; @@ -186,7 +189,7 @@ class LibcurlHTTP2Connection : public avsCommon::utils::http2::HTTP2ConnectionIn /// 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; + ActiveStreamMap m_activeStreams; /// Queue of requests send. Serialized by @c m_mutex. std::deque> m_requestQueue; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h index 93316ca196..7645b03e84 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LogEntry.h @@ -17,6 +17,7 @@ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LOGGER_LOGENTRY_H_ #include +#include #include #include #include @@ -34,24 +35,36 @@ 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. + * @param[in] source The name of the source of this log entry. If @a source is nullptr, it is treated as an empty + * string. + * @param[in] event The name of the event that this log entry describes. If @a event is nullptr, it is treated as + * an empty string. + */ + LogEntry(const char* source, const char* event); + + /** + * Constructor. + * + * @param[in] source The name of the source of this log entry. + * @param[in] event The name of the event that this log entry describes. If @a event is nullptr, it is treated as + * an empty string. */ LogEntry(const std::string& source, const char* event); /** * Constructor. * - * @param source The name of the source of this log entry. - * @param event The name of the event that this log entry describes. + * @param[in] source The name of the source of this log entry. + * @param[in] 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. + * @param[in] key The key identifying the value to add to this LogEntry. + * @param[in] value The value to add to this LogEntry. + * * @return This instance to facilitate adding more information to this log entry. */ template @@ -59,17 +72,30 @@ class LogEntry { /** * 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. + * + * @param[in] key The key identifying the value to add to this LogEntry. + * @param[in] value The value to add to this LogEntry. If @a value is nullptr, it is treated as an empty string. + * * @return This instance to facilitate adding more information to this log entry. */ LogEntry& d(const char* key, const char* value); + /** + * Add a @c key, @c value pair to the metadata of this log entry. + * + * @param[in] key The key identifying the value to add to this LogEntry. + * @param[in] value The value to add to this LogEntry. If @a value is nullptr, it is treated as an empty string. + * + * @return This instance to facilitate adding more information to this log entry. + */ + LogEntry& d(const char* key, char* 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. + * @param[in] key The key identifying the value to add to this LogEntry. + * @param[in] 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, const std::string& value); @@ -77,8 +103,9 @@ 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. + * @param[in] key The key identifying the value to add to this LogEntry. + * @param[in] value The boolean value to add to this LogEntry. + * * @return This instance to facilitate adding more information to this log entry. */ LogEntry& d(const char* key, bool value); @@ -86,23 +113,25 @@ 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. + * @param[in] key The key identifying the value to add to this LogEntry. + * @param[in] value The value to add to this LogEntry. + * * @return This instance to facilitate adding more information to this log entry. */ template - inline LogEntry& d(const char* key, const ValueType& value); + LogEntry& d(const char* key, const ValueType& value); /** * Add sensitive data in the form of a @c key, @c value pair to the metadata of this log entry. * Because the data is 'sensitive' it will only be emitted in DEBUG builds. * - * @param key The key identifying the value to add to this LogEntry. - * @param value The value to add to this LogEntry. + * @param[in] key The key identifying the value to add to this LogEntry. + * @param[in] value The value to add to this LogEntry. + * * @return This instance to facilitate adding more information to this log entry. */ template - inline LogEntry& sensitive(const char* key, const ValueType& value); + LogEntry& sensitive(const char* key, const ValueType& value); /** * Add data in the form of a @c key, @c value pair to the metadata of this log entry. @@ -110,17 +139,20 @@ class LogEntry { * This is done in a distinct method (instead of m or d) to avoid the cost of always checking * against the denylist. * - * @param key The key identifying the value to add to this LogEntry. - * @param value The value to add to this LogEntry, obfuscated if needed. + * @param[in] key The key identifying the value to add to this LogEntry. + * @param[in] value The value to add to this LogEntry, obfuscated if needed. + * * @return This instance to facilitate adding more information to this log entry. */ - inline LogEntry& obfuscatePrivateData(const char* key, const std::string& value); + LogEntry& obfuscatePrivateData(const char* key, const std::string& value); /** * 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. + * @param[in] message The message to add to the end of the text of this LogEntry. If @a message is a nullptr, it is + * treated as an empty string. + * * @return This instance to facilitate passing this instance on. */ LogEntry& m(const char* message); @@ -129,7 +161,8 @@ 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. + * @param[in] message The message to add to the end of the text of this LogEntry. + * * @return This instance to facilitate passing this instance on. */ LogEntry& m(const std::string& message); @@ -138,8 +171,10 @@ class LogEntry { * Add pointer (hence the name 'p') in the form of a @c key, address of the object pointed to by the shared_ptr @c * ptr to the metadata of this log entry. * - * @param key The key identifying the value to add to this LogEntry. - * @param ptr The shared_ptr of the object to add to this LogEntry. + * @param[in] key The key identifying the value to add to this LogEntry. If @a key is a nullptr, it is treated as an + * empty string. + * @param[in] ptr The shared_ptr of the object to add to this LogEntry. + * * @return This instance to facilitate adding more information to this log entry. */ template @@ -149,8 +184,10 @@ class LogEntry { * Add pointer (hence the name 'p') in the form of a @c key, address of the raw @c ptr to the metadata of this * log entry. * - * @param key The key identifying the value to add to this LogEntry. - * @param ptr The raw pointer of the object to add to this LogEntry. + * @param[in] key The key identifying the value to add to this LogEntry. If @a key is a nullptr, it is treated as an + * empty string. + * @param[in] ptr The raw pointer of the object to add to this LogEntry. + * * @return This instance to facilitate adding more information to this log entry. */ LogEntry& p(const char* key, const void* ptr); @@ -170,12 +207,6 @@ class LogEntry { /// Add the appropriate prefix for an arbitrary message that is about to be appended to the text of this LogEntry. void prefixMessage(); - /// Return a list of labels we will obfuscate if sent to obfuscatePrivateData - static std::vector getPrivateLabelDenyList() { - static std::vector privateLabelDenyList = {"ssid"}; - return privateLabelDenyList; - } - /** * Append an escaped string to m_stream. * Our metadata and subsequent optional message is of the form: @@ -202,14 +233,18 @@ inline LogEntry& LogEntry::d(const std::string& key, const ValueType& value) { } template -LogEntry& LogEntry::d(const char* key, const ValueType& value) { +inline LogEntry& LogEntry::d(const char* key, const ValueType& value) { prefixKeyValuePair(); m_stream << key << KEY_VALUE_SEPARATOR << value; return *this; } +inline LogEntry& LogEntry::d(const char* key, char* value) { + return d(key, const_cast(value)); +} + template -LogEntry& LogEntry::p(const char* key, const std::shared_ptr& ptr) { +inline LogEntry& LogEntry::p(const char* key, const std::shared_ptr& ptr) { return d(key, ptr.get()); } @@ -217,46 +252,16 @@ LogEntry& LogEntry::p(const char* key, const std::shared_ptr& ptr) { #ifdef ACSDK_EMIT_SENSITIVE_LOGS template -LogEntry& LogEntry::sensitive(const char* key, const ValueType& value) { +inline LogEntry& LogEntry::sensitive(const char* key, const ValueType& value) { return d(key, value); } #else template -LogEntry& LogEntry::sensitive(const char*, const ValueType&) { +inline LogEntry& LogEntry::sensitive(const char*, const ValueType&) { return *this; } #endif -LogEntry& LogEntry::obfuscatePrivateData(const char* key, const std::string& value) { - // if value contains any private label, obfuscate the section after the label - // since it can (but shouldn't) contain multiple, obfuscate from the earliest one found onward - auto firstPosition = value.length(); - - for (auto privateLabel : getPrivateLabelDenyList()) { - auto it = std::search( - value.begin(), - value.end(), - privateLabel.begin(), - privateLabel.end(), - [](char valueChar, char denyListChar) { return std::tolower(valueChar) == std::tolower(denyListChar); }); - if (it != value.end()) { - // capture the least value - auto thisPosition = std::distance(value.begin(), it) + privateLabel.length(); - if (thisPosition < firstPosition) { - firstPosition = thisPosition; - } - } - } - - if (firstPosition <= value.length()) { - // hash everything after the label itself - auto labelPart = value.substr(0, firstPosition); - auto obfuscatedPart = std::to_string(std::hash{}(value.substr(firstPosition))); - return d(key, labelPart + obfuscatedPart); - } - return d(key, value); -} - } // namespace logger } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Logger.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Logger.h index 2f0ab56556..9bedd30626 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Logger.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/Logger.h @@ -85,7 +85,7 @@ namespace logger { * are passed to the @c LogEntry constructor. Here is an example of the definitions that typically appear at * the start of a .cpp file: * - * static const std::string TAG = "MyClass"; + * #define TAG "MyClass" * #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) * * When an event is to be logged, a wrapper macro named @c ACSDK_ is invoked with an expression that starts @@ -138,14 +138,11 @@ namespace logger { * * add_definitions("-DACSDK_LOG_MODULE=foo") * - * All logs (module specific or not) are output to a @c Sink @c Logger. By default, the @c Sink @c Logger - * is @c ConsoleLogger. This can be overridden by defining @c ACSDK_LOG_SINK. The value of @c ACSDK_LOG_SINK - * specifies a function name of the form: - * - * Logger& getLogger() + * For modules that does not have the @c ACSDK_LOG_MODULE definition, logs will be defaulted to use the + * ConsoleLogger module Logger. * - * That function to used to get the @c Sink @c Logger. When @c ACSDK_LOG_SINK is overridden, it is necessary - * to provide an implementation of that function that returns the desired @c Logger. + * All logs (module specific or not) are output to a @c Sink @c Logger. By default, the @c Sink @c Logger + * is @c ConsoleLogger. The sink logger can be changed by calling initialize in @c LoggerSinkManager. */ class Logger { public: @@ -169,7 +166,7 @@ class Logger { /** * Return true of logs of a specified severity should be emitted by this Logger. * - * @param level The Level to check. + * @param[in] level The Level to check. * @return Returns true if logs of the specified Level should be emitted. */ inline bool shouldLog(Level level) const; @@ -177,8 +174,8 @@ class Logger { /** * Send a log entry to this Logger. * - * @param level The severity Level to associate with this log entry. - * @param entry Object used to build the text of this log entry. + * @param[in] level The severity Level to associate with this log entry. + * @param[in] entry Object used to build the text of this log entry. */ void log(Level level, const LogEntry& entry); @@ -190,8 +187,8 @@ class Logger { * * @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. + * @param[in] level The severity Level to associate with this log entry. + * @param[in] entry Object used to build the text of this log entry. */ void logAtExit(Level level, const LogEntry& entry); @@ -200,10 +197,10 @@ class Logger { * NOTE: This method must be thread-safe. * NOTE: Delays in returning from this method may hold up calls to Logger::log(). * - * @param level The severity Level of this log line. - * @param time The time that the event to log occurred. - * @param threadMoniker Moniker of the thread that generated the event. - * @param text The text of the entry to log. + * @param[in] level The severity Level of this log line. + * @param[in] time The time that the event to log occurred. + * @param[in] threadMoniker Moniker of the thread that generated the event. + * @param[in] text The text of the entry to log. */ virtual void emit( Level level, @@ -214,16 +211,14 @@ class Logger { /** * Add an observer to this object. * - * @param An observer to this class, which will be notified when - * the logLevel changes. + * @param An observer to this class, which will be notified when the logLevel changes. */ void addLogLevelObserver(LogLevelObserverInterface* observer); /** * Remove an observer to this object. * - * @param An observer to this class that will be removed from the - * notificaiton of logLevel changes. + * @param An observer to this class that will be removed from the notification of logLevel changes. */ void removeLogLevelObserver(LogLevelObserverInterface* observer); @@ -270,27 +265,21 @@ bool Logger::shouldLog(Level level) const { */ #define ACSDK_GET_LOGGER_FUNCTION_NAME(type) ACSDK_CONCATENATE(ACSDK_CONCATENATE(get, type), Logger) -// If @c ACSDK_LOG_SINK was not defined, default to logging to console. -#ifndef ACSDK_LOG_SINK -#define ACSDK_LOG_SINK Console -#endif - -/// Build the getLogger function name for whatever @c Logger logs will be sent to. -#define ACSDK_GET_SINK_LOGGER ACSDK_GET_LOGGER_FUNCTION_NAME(ACSDK_LOG_SINK) - /** * Get the @c Logger that logs should be sent to. * * @return The @c Logger that logs should be sent to. */ -std::shared_ptr ACSDK_GET_SINK_LOGGER(); +std::shared_ptr getConsoleLogger(); } // namespace logger } // namespace utils } // namespace avsCommon } // namespace alexaClientSDK -#ifdef ACSDK_LOG_MODULE +#ifndef ACSDK_LOG_MODULE +#define ACSDK_LOG_MODULE ConsoleLogger +#endif // ACSDK_LOG_MODULE #include "AVSCommon/Utils/Logger/ModuleLogger.h" @@ -320,38 +309,16 @@ inline std::shared_ptr ACSDK_GET_LOGGER_FUNCTION() { } // namespace avsCommon } // namespace alexaClientSDK -#else // ACSDK_LOG_MODULE - -namespace alexaClientSDK { -namespace avsCommon { -namespace utils { -namespace logger { - -/** - * Inline method to get the function that ACSDK_ macros will send logs to. - * In this case @c ACSDK_LOG_MODULE was not defined, so logs are sent to the @c Logger returned by - * @c getLogger(). - */ -inline std::shared_ptr ACSDK_GET_LOGGER_FUNCTION() { - static std::shared_ptr logger = ACSDK_GET_SINK_LOGGER(); - return logger; -} - -} // namespace logger -} // namespace utils -} // namespace avsCommon -} // namespace alexaClientSDK - -#endif - -/// Define log macro if logging is enabled. Else do nothing with params to avoid unused variable error. -#ifdef ACSDK_LOG_ENABLED /** - * Common implementation for sending entries to the log. + * @def ACSDK_LOG + * @brief Common implementation for sending entries to the log. + * + * @note If @c ACSDK_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param level The log level to associate with the log line. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] level The log level to associate with the log line. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ +#ifdef ACSDK_LOG_ENABLED #define ACSDK_LOG(level, entry) \ do { \ auto loggerInstance = alexaClientSDK::avsCommon::utils::logger::ACSDK_GET_LOGGER_FUNCTION(); \ @@ -359,231 +326,172 @@ inline std::shared_ptr ACSDK_GET_LOGGER_FUNCTION() { loggerInstance->log(level, entry); \ } \ } while (false) -#else - /** - * Null implementation for sending entries to the log. - * - * @param level Unused. - * @param entry Unused. - */ +#else // ACSDK_LOG_ENABLED #define ACSDK_LOG(level, entry) \ do { \ - (void)level; \ - (void)entry; \ + (void)sizeof(level); \ + (void)sizeof(entry); \ } while (false) -#endif - -#ifdef ACSDK_DEBUG_LOG_ENABLED +#endif // ACSDK_LOG_ENABLED /** - * Send a DEBUG9 severity log line. + * @def ACSDK_DEBUG_LOG + * @brief Common implementation for sending debug entries to the log. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG9(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG9, entry) - -/** - * Send a DEBUG8 severity log line. + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] level The log level to associate with the log line. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG8(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG8, entry) -/** - * Send a DEBUG7 severity log line. - * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG7(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG7, entry) +#ifdef ACSDK_DEBUG_LOG_ENABLED +#define ACSDK_DEBUG_LOG(level, entry) ACSDK_LOG((level), (entry)) +#else // ACSDK_DEBUG_LOG_ENABLED +#define ACSDK_DEBUG_LOG(level, entry) \ + do { \ + (void)sizeof(level); \ + (void)sizeof(entry); \ + } while (false) +#endif // ACSDK_DEBUG_LOG_ENABLED /** - * Send a DEBUG6 severity log line. + * Send a DEBUG9 severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG6(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG6, entry) - -/** - * Send a DEBUG5 severity log line. + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG5(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG5, entry) +#define ACSDK_DEBUG9(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG9, entry) /** - * Send a DEBUG4 severity log line. + * Send a DEBUG8 severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG4(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG4, entry) - -/** - * Send a DEBUG3 severity log line. + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG3(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG3, entry) +#define ACSDK_DEBUG8(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG8, entry) /** - * Send a DEBUG2 severity log line. + * Send a DEBUG7 severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG2(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG2, entry) - -/** - * Send a DEBUG1 severity log line. + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG1(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG1, entry) +#define ACSDK_DEBUG7(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG7, entry) /** - * Send a DEBUG0 severity log line. + * Send a DEBUG6 severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG0(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG0, entry) - -/** - * Send a log line at the default debug level (DEBUG0). + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG0, entry) - -#else // ACSDK_DEBUG_LOG_ENABLED +#define ACSDK_DEBUG6(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG6, entry) /** - * Compile out a DEBUG9 severity log line. + * Send a DEBUG5 severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG9(entry) - -/** - * Compile out a DEBUG8 severity log line. + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG8(entry) +#define ACSDK_DEBUG5(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG5, entry) /** - * Compile out a DEBUG7 severity log line. + * Send a DEBUG4 severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG7(entry) - -/** - * Compile out a DEBUG6 severity log line. + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG6(entry) +#define ACSDK_DEBUG4(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG4, entry) /** - * Compile out a DEBUG5 severity log line. + * Send a DEBUG3 severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG5(entry) - -/** - * Compile out a DEBUG4 severity log line. + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG4(entry) +#define ACSDK_DEBUG3(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG3, entry) /** - * Compile out a DEBUG3 severity log line. + * Send a DEBUG2 severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. - */ -#define ACSDK_DEBUG3(entry) - -/** - * Compile out a DEBUG2 severity log line. + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG2(entry) +#define ACSDK_DEBUG2(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG2, entry) /** - * Compile out a DEBUG1 severity log line. + * Send a DEBUG1 severity log line. + * + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG1(entry) +#define ACSDK_DEBUG1(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG1, entry) /** - * Compile out a DEBUG0 severity log line. + * Send a DEBUG0 severity log line. + * + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG0(entry) +#define ACSDK_DEBUG0(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG0, entry) /** - * Compile out a DEBUG severity log line. + * Send a log line at the default debug level (DEBUG0). * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @note If @c ACSDK_DEBUG_LOG_ENABLED is set to OFF, then logging is disabled. + * + * @param[in] entry A constructed @a LogEntry object with the log message text. */ -#define ACSDK_DEBUG(entry) - -#endif // ACSDK_DEBUG_LOG_ENABLED +#define ACSDK_DEBUG(entry) ACSDK_DEBUG_LOG(alexaClientSDK::avsCommon::utils::logger::Level::DEBUG0, entry) /** * Send a INFO severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @note If @c ACSDK_LOG_ENABLED is set to OFF, then logging is disabled. + * + * @param[in] entry A constructed @a LogEntry object with the log message text. */ #define ACSDK_INFO(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::INFO, entry) /** * Send a WARN severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @note If @c ACSDK_LOG_ENABLED is set to OFF, then logging is disabled. + * + * @param[in] entry A constructed @a LogEntry object with the log message text. */ #define ACSDK_WARN(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::WARN, entry) /** * Send a ERROR severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @note If @c ACSDK_LOG_ENABLED is set to OFF, then logging is disabled. + * + * @param[in] entry A constructed @a LogEntry object with the log message text. */ #define ACSDK_ERROR(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::ERROR, entry) /** * Send a CRITICAL severity log line. * - * @param loggerArg The Logger to send the line to. - * @param entry The text (or builder of the text) for the log entry. + * @note If @c ACSDK_LOG_ENABLED is set to OFF, then logging is disabled. + * + * @param[in] entry A constructed @a LogEntry object with the log message text. */ #define ACSDK_CRITICAL(entry) ACSDK_LOG(alexaClientSDK::avsCommon::utils::logger::Level::CRITICAL, entry) +#ifndef ACSDK_LOGS_KEEP_FUNC_MACRO +// In older Android releases __func__ is redefined as __PRETTY_FUNCTION__ making log messages difficult to read and +// analyse. We undefine this macro if it is defined. See Logger.cmake for details on how to keep the macro intact. +#undef __func__ +#endif // ACSDK_LOGS_KEEP_FUNC_MACRO + #endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_LOGGER_LOGGER_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LoggerSinkManager.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LoggerSinkManager.h index 252d03123f..f433e3441e 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LoggerSinkManager.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/LoggerSinkManager.h @@ -67,8 +67,7 @@ class LoggerSinkManager { * * @param sink The new @c Logger to forward logs to. * - * @note If this function is not called, the default sink logger - * will be the one returned by getLogger(). + * @note If this function is not called, the default sink logger is @c ConsoleLogger. */ void initialize(const std::shared_ptr& sink); diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ThreadMoniker.h b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ThreadMoniker.h index ec70c280a6..09b638231a 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ThreadMoniker.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Logger/ThreadMoniker.h @@ -27,82 +27,64 @@ namespace utils { namespace logger { /** - * Class to provide @c std::this_thread access to unique name for itself. + * Class to provide unique name for execution context. * - * 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. + * This class provides management for thread-local execution context identifiers. Execution context identifier is a + * hexadecimal string which is managed by facilities like @c Executor and @c Timer. Logging framework adds identifier + * value to all messages to identify which executor or timer is running the task. + * + * @note The same identifier can be reused by different threads, but will have the same context. */ class ThreadMoniker { public: + /// Prefix value for executor monikers. + static constexpr char PREFIX_EXECUTOR = 'e'; + + /// Prefix value for timer monikers. + static constexpr char PREFIX_TIMER = 't'; + /** - * Get the moniker for @c std::this_thread. + * Get the moniker for the caller's thread. * - * @return The moniker for @c std::this_thread. + * Method returns identifier value assigned to the current thread by a call to #setThisThreadMoniker(). If the value + * has not been previously set, a new value is generated by #generateMoniker(), assigned to the current thread, and + * returned. + * + * @return The moniker for the caller's thread. */ - static inline std::string getThisThreadMoniker(); + static std::string getThisThreadMoniker() noexcept; /** * Generate a unique moniker. * + * This method generates a fixed-width moniker string. By default the value is a alpha-numeric string, prepended + * with spaces. If the \a prefix is specified, the value also includes prefix character with colon. + * + * @param prefix Optional prefix for moniker value. If @a prefix if not 0, it is used for generating moniker. + * Some of prefix values are reserved for use by ACSDK: 't' prefix is used by timers, and 'e' prefix is used by + * executors. + * * @return A new unique moniker. */ - static std::string generateMoniker(); + static std::string generateMoniker(char prefix = 0) noexcept; /** - * Set the moniker for @c std::this_thread. This method should be called before @c getThisThreadMoniker() in order - * to take effect. + * Set the moniker for the caller's thread. + * + * This method sets identifier value for the caller's thread. Any subsequent calls to #getThisThreadMoniker() will + * return @a moniker value. * * @param moniker The moniker for @c std::this_thread. */ - static inline void setThisThreadMoniker(const std::string& moniker); + static void setThisThreadMoniker(const std::string& moniker) noexcept; 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; + ThreadMoniker() = delete; }; -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{moniker}; - - return m_threadMoniker; -#endif -} - } // namespace logger } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/ErrorTypes.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/ErrorTypes.h index 0878ed33ec..b17b083789 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/ErrorTypes.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/ErrorTypes.h @@ -17,6 +17,7 @@ #define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_MEDIAPLAYER_ERRORTYPES_H_ #include +#include namespace alexaClientSDK { namespace avsCommon { diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaDescription.h b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaDescription.h index fb1c8db68e..ec7ef33344 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaDescription.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/MediaPlayer/MediaDescription.h @@ -69,7 +69,7 @@ inline MediaDescription emptyMediaDescription() { "", Optional(), Optional>(), - {}, + std::unordered_map(), false}; } @@ -81,6 +81,7 @@ inline MediaDescription emptyMediaDescription() { * @return The stream that was passed in and written to. */ inline std::ostream& operator<<(std::ostream& stream, const MediaDescription& mediaDescription) { + stream << "MixingBehavior:"; switch (mediaDescription.mixingBehavior) { case sdkInterfaces::audio::MixingBehavior::BEHAVIOR_PAUSE: stream << "BEHAVIOR_PAUSE"; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/MetricRecorderInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/MetricRecorderInterface.h index 6f6cccc33f..b6323fa900 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/MetricRecorderInterface.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Metrics/MetricRecorderInterface.h @@ -43,10 +43,10 @@ class MetricRecorderInterface { }; /** - * Inline record metric function to handle if ACSDK_ENABLE_METRICS_RECORDING flag is defined or not + * Inline record metric function to handle if ACSDK_ENABLE_METRICS_RECORDING flag is defined or not. * - * @param recorder is the pointer to the MetricRecorderInterface. - * @param metricEvent is the pointer to the MetricEvent. + * @param recorder Optional pointer to MetricRecorderInterface. If this parameter is nullptr, metric is not sent. + * @param metricEvent Pointer to the MetricEvent. */ inline void recordMetric( const std::shared_ptr& recorder, @@ -55,6 +55,9 @@ inline void recordMetric( if (recorder) { recorder->recordMetric(std::move(metricEvent)); } +#else + (void)recorder; + (void)metricEvent; #endif } diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h b/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h index e47afd3c1c..e879baa732 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/PlatformDefinitions.h @@ -40,7 +40,79 @@ typedef SSIZE_T ssize_t; #endif #if defined(_MSC_VER) +#ifndef ACSDK_USE_RTTI #define ACSDK_USE_RTTI ON #endif +#endif + +/** + * @macro ACSDK_ALWAYS_INLINE + * + * Compiler-specific macro to disable cost-benefit analysis and always inline a method. + */ + +/** + * @macro ACSDK_NO_INLINE + * + * Compiler-specific macro to disable cost-benefit analysis and never inline a method. + */ + +/** + * @macro ACSDK_HIDDEN + * + * Compiler-specific macro to make symbol not visible outside of binary object. + */ + +/** + * @macro ACSDK_INTERNAL_LINKAGE + * + * Compiler-specific macro to make symbol not visible outside of the scope it is instantiated. + */ + +/** + * @macro ACSDK_HIDE_FROM_ABI + * @brief Compiler-specific macro to exclude symbol from binary exports. + * + * Macro excludes symbols from binary export table. This helps to reduce binary size when building shared libraries. + */ + +#if defined(_MSC_VER) +#define ACSDK_ALWAYS_INLINE __forceinline +#define ACSDK_NO_INLINE __declspec(noinline) +#define ACSDK_HIDDEN +#define ACSDK_INTERNAL_LINKAGE ACSDK_ALWAYS_INLINE +#elif defined(__GNUC__) + +#ifndef __has_attribute +#define __has_attribute(x) 0 +#endif + +#define ACSDK_ALWAYS_INLINE inline __attribute__((__always_inline__)) +#define ACSDK_NO_INLINE __attribute__((__noinline__)) +#define ACSDK_HIDDEN __attribute__((__visibility__("hidden"))) + +#if defined(__clang__) +#if __has_attribute(internal_linkage) +#define ACSDK_INTERNAL_LINKAGE __attribute__((internal_linkage)) +#else +#define ACSDK_INTERNAL_LINKAGE __attribute__((__visibility__("hidden"))) +#endif +#else +#define ACSDK_INTERNAL_LINKAGE __attribute__((__visibility__("internal"))) +#endif +#else +#define ACSDK_ALWAYS_INLINE inline +#define ACSDK_NO_INLINE +#define ACSDK_HIDDEN +#define ACSDK_INTERNAL_LINKAGE ACSDK_ALWAYS_INLINE +#endif +#define ACSDK_HIDE_FROM_ABI ACSDK_INTERNAL_LINKAGE + +/** + * @brief Compiler-specific macro for inline-only methods. + * + * Macro excludes symbols from binary export table. This helps to reduce binary size when building shared libraries. + */ +#define ACSDK_INLINE_VISIBILITY ACSDK_HIDE_FROM_ABI #endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_PLATFORMDEFINITIONS_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Power/AggregatedPowerResourceManager.h b/AVSCommon/Utils/include/AVSCommon/Utils/Power/AggregatedPowerResourceManager.h index 069b6b2a12..ffbeb03132 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Power/AggregatedPowerResourceManager.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Power/AggregatedPowerResourceManager.h @@ -61,11 +61,13 @@ class AggregatedPowerResourceManager : public avsCommon::sdkInterfaces::PowerRes /// @name PowerResourceManagerInterface Legacy Methods /// @{ + /**********************************Deprecated Legacy APIs**********************************************/ void acquirePowerResource( const std::string& component, const PowerResourceLevel level = PowerResourceLevel::STANDBY_MED) override; void releasePowerResource(const std::string& component) override; bool isPowerResourceAcquired(const std::string& component) override; + /******************************************************************************************************/ std::shared_ptr create( const std::string& resourceId, bool isRefCounted = true, diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/BufferLayout.h b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/BufferLayout.h index 67517a5e6c..507ccd0953 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/BufferLayout.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/BufferLayout.h @@ -489,6 +489,13 @@ bool SharedDataStream::BufferLayout::init(size_t wordSize, size_t maxReaders, .d("maxReadersLimit", max_field_limit(&Header::maxReaders))); return false; } + if (maxEphemeralReaders > max_field_limit(&Header::maxEphemeralReaders)) { + logger::acsdkError(logger::LogEntry(TAG, "initFailed") + .d("reason", "maxEphermalReadersTooLarge") + .d("maxEphemeralReaders", maxEphemeralReaders) + .d("maxEphemeralReaders", max_field_limit(&Header::maxEphemeralReaders))); + return false; + } // Pre-calculate some pointers and sizes that are frequently accessed. calculateAndCacheConstants(wordSize, maxReaders); @@ -508,9 +515,9 @@ bool SharedDataStream::BufferLayout::init(size_t wordSize, size_t maxReaders, header->magic = MAGIC_NUMBER; header->version = VERSION; header->traitsNameHash = stableHash(T::traitsName); - header->wordSize = wordSize; - header->maxReaders = maxReaders; - header->maxEphemeralReaders = maxEphemeralReaders; + header->wordSize = static_cast(wordSize); + header->maxReaders = static_cast(maxReaders); + header->maxEphemeralReaders = static_cast(maxEphemeralReaders); header->isWriterEnabled = false; header->hasWriterBeenClosed = false; header->writeStartCursor = 0; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Reader.h b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Reader.h index e7beb43f80..2cbba7304c 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Reader.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDS/Reader.h @@ -90,7 +90,7 @@ class SharedDataStream::Reader { * @param bufferLayout The @c BufferLayout to use for reading stream data. * @param id The id of the reader, assigned by the @c SharedDataStream. */ - Reader(Policy policy, std::shared_ptr bufferLayout, uint8_t id); + Reader(Policy policy, std::shared_ptr bufferLayout, size_t id); /// This destructor detaches the @c Reader from a @c BufferLayout. ~Reader(); @@ -245,7 +245,7 @@ class SharedDataStream::Reader { * The index in @c BufferLayout::getReaderCursorArray() and @c BufferLayout::getReaderCloseIndexArray() assigned to * this @c Reader. */ - uint8_t m_id; + size_t m_id; /// Pointer to this reader's cursor in BufferLayout::getReaderCursorArray(). AtomicIndex* m_readerCursor; @@ -258,7 +258,7 @@ template const std::string SharedDataStream::Reader::TAG = "SdsReader"; template -SharedDataStream::Reader::Reader(Policy policy, std::shared_ptr bufferLayout, uint8_t id) : +SharedDataStream::Reader::Reader(Policy policy, std::shared_ptr bufferLayout, size_t id) : m_policy{policy}, m_bufferLayout{bufferLayout}, m_id{id}, diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/Executor.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/Executor.h index af8e471393..a51e29bcde 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/Executor.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/Executor.h @@ -26,8 +26,9 @@ #include #include -#include "AVSCommon/Utils/Threading/TaskThread.h" -#include "AVSCommon/Utils/Power/PowerResource.h" +#include +#include +#include namespace alexaClientSDK { namespace avsCommon { @@ -35,134 +36,174 @@ namespace utils { namespace threading { /** + * @brief Single-thread executor implementation. + * * An Executor is used to run callable types asynchronously. + * + * This type is a wrapper around ExecutorInterface implementation. */ class Executor { public: + /** + * Constructs an Executor. + */ + Executor() noexcept; + /** * 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. + * @param unused Unused parameter. + * + * @deprecated This method is kept for backwards compatibility. */ - Executor(const std::chrono::milliseconds& delayExit = std::chrono::milliseconds(1000)); + Executor(const std::chrono::milliseconds& unused) noexcept; /** * Destructs an Executor. */ - ~Executor(); + ~Executor() noexcept; + + /** + * @brief Schedules a function for execution. + * + * Submits a function to be executed on an Executor thread. + * + * @param[in] function Function to execute. Function must not be empty. + * @return True if @a function is accepted for execution, false if @a function is empty or executor is shutdown. + */ + bool execute(std::function&& function) noexcept; + + /** + * @brief Schedules a function for execution. + * + * Submits a function to be executed on an Executor thread. + * + * @param[in] function Function to execute. + * @return True if @a function is accepted for execution, false if @a function is empty or executor is shutdown. + */ + bool execute(const std::function& function) noexcept; /** * Submits a callable type (function, lambda expression, bind expression, or another function object) to be executed * on an Executor thread. The future must be checked for validity before waiting on it. * + * @tparam Task Callable type. + * @tparam Args Argument types. + * * @param task A callable type representing a task. * @param args The arguments to call the task with. - * @returns A @c std::future for the return value of the task. + * @return A @c std::future for the return value of the task. + * + * @note This method is less memory and speed efficient then #execute() and should not be used unless std::future + * result is required. */ template - auto submit(Task task, Args&&... args) -> std::future; + auto submit(Task task, Args&&... args) noexcept -> std::future; /** * Submits a callable type (function, lambda expression, bind expression, or another function object) to the front * of the internal queue to be executed on an Executor thread. The future must be checked for validity before * waiting on it. * + * @tparam Task Callable type. + * @tparam Args Argument types. + * * @param task A callable type representing a task. * @param args The arguments to call the task with. - * @returns A @c std::future for the return value of the task. + * @return A @c std::future for the return value of the task. + * + * @note This method is less memory and speed efficient then #execute() and should not be used unless std::future + * result is required. */ template - auto submitToFront(Task task, Args&&... args) -> std::future; + auto submitToFront(Task task, Args&&... args) noexcept -> std::future; /** * Waits for any previously submitted tasks to complete. */ - void waitForSubmittedTasks(); + void waitForSubmittedTasks() noexcept; /// Clears the executor of outstanding tasks and refuses any additional tasks to be submitted. - void shutdown(); + void shutdown() noexcept; /// Returns whether or not the executor is shutdown. - bool isShutdown(); - -private: - /// The queue type to use for holding tasks. - using Queue = std::deque>; + bool isShutdown() noexcept; /** - * Executes the next job in the queue. + * @brief Provides access to ExecutorInterface reference. * - * @return @c true if there's a next job; @c false if the job queue is empty. + * @return Reference to internal ExecutorInterface. */ - bool runNext(); + operator std::shared_ptr() const noexcept; + +private: + // Friend declaration. + friend class SharedExecutor; /** - * 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. + * @brief Ordering hint when submitting a new task to executor. */ - bool hasNext(); + enum class QueuePosition { + /// Add task to front of task queue. + Front = 1, + /// Add task to back of task queue. + Back + }; /** - * Returns and removes the task at the front of the queue. If there are no tasks, this call will return an empty - * function. + * @brief Schedules a function for execution. + * + * Submits a function to be executed on an Executor thread. * - * @returns A function that represents a new task. The function will be empty if the queue has no job. + * @param[in] function Function to execute. + * @param[in] queuePosition Position in the queue for the new task. + * @return True on success, false on error: if @a function is empty, @a queuePosition is not valid, or executor is + * in shutdown. */ - std::function pop(); + bool execute(std::function&& function, QueuePosition queuePosition) noexcept; /** * 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 queuePosition Position in the queue for the new task. * @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; + auto pushTo(QueuePosition queuePosition, Task&& task, Args&&... args) noexcept + -> std::future; - /// 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; - - /// A @c PowerResource. - std::shared_ptr m_powerResource; - - /// The condition variable used to detect new job or timeout. - std::condition_variable m_delayedCondition; - - /// The id of this instance. - const uint64_t m_id; + /** + * Pushes a function on the the queue. If the queue is shutdown, the function will be dropped, and an invalid + * future will be returned. + * + * @tparam T Return type for @a function and resulting future value type. + * @param queuePosition Position in the queue for the new task. + * @param function A function to push. + * @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 + std::future pushFunction(QueuePosition queuePosition, std::function&& function) noexcept; - /// The thread to execute tasks on. The thread must be declared last to be destructed first. - TaskThread m_taskThread; + /// Internal shared executor reference. + std::shared_ptr m_executor; }; +inline Executor::Executor(const std::chrono::milliseconds&) noexcept : Executor() { +} + template -auto Executor::submit(Task task, Args&&... args) -> std::future { - bool front = false; - return pushTo(front, std::forward(task), std::forward(args)...); +auto Executor::submit(Task task, Args&&... args) noexcept -> std::future { + return pushTo(QueuePosition::Back, std::forward(task), std::forward(args)...); } template -auto Executor::submitToFront(Task task, Args&&... args) -> std::future { - bool front = true; - return pushTo(front, std::forward(task), std::forward(args)...); +auto Executor::submitToFront(Task task, Args&&... args) noexcept -> std::future { + return pushTo(QueuePosition::Front, std::forward(task), std::forward(args)...); } /** @@ -172,14 +213,14 @@ auto Executor::submitToFront(Task task, Args&&... args) -> std::future -inline static void forwardPromise(std::shared_ptr> promise, std::future* future) { +inline static void forwardPromise(std::promise& promise, std::future& future) noexcept { #if __cpp_exceptions || defined(__EXCEPTIONS) try { #endif - promise->set_value(future->get()); + promise.set_value(future.get()); #if __cpp_exceptions || defined(__EXCEPTIONS) } catch (...) { - promise->set_exception(std::current_exception()); + promise.set_exception(std::current_exception()); } #endif } @@ -191,28 +232,32 @@ inline static void forwardPromise(std::shared_ptr> promise, std: * @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) { +inline void forwardPromise(std::promise& promise, std::future& future) noexcept { #if __cpp_exceptions || defined(__EXCEPTIONS) try { #endif - future->get(); - promise->set_value(); + future.get(); + promise.set_value(); #if __cpp_exceptions || defined(__EXCEPTIONS) } catch (...) { - promise->set_exception(std::current_exception()); + promise.set_exception(std::current_exception()); } #endif } template -auto Executor::pushTo(bool front, Task task, Args&&... args) -> std::future { +inline auto Executor::pushTo(QueuePosition queuePosition, Task&& task, Args&&... args) noexcept + -> std::future { + using ValueType = decltype(task(args...)); // Remove arguments from the tasks type by binding the arguments to the task. - auto boundTask = std::bind(std::forward(task), std::forward(args)...); + std::function fn{std::bind(std::forward(task), std::forward(args)...)}; + return pushFunction(queuePosition, std::move(fn)); +} +template +std::future Executor::pushFunction(QueuePosition queuePosition, std::function&& function) noexcept { /* - * 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. + * Create a std::packaged_task with the correct return 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 @@ -223,53 +268,69 @@ auto Executor::pushTo(bool front, Task task, Args&&... args) -> std::future; - 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(); + /** + * @brief Structure to carry parameters into lambda through shared pointer. + * @private + */ + struct CallCtx { + /** + * @brief Construct object and assigned function to packaged task. + * + * @param function Function to wrap into packaged task. + */ + inline CallCtx(std::function&& function) : packagedTask{std::move(function)} { + } + + /// Packaged task. + std::packaged_task packagedTask; + /// Promise for result forwarding. + std::promise cleanupPromise; + }; + + auto callCtx = std::make_shared(std::move(function)); // Remove the return type from the task by wrapping it in a lambda with no return value. - auto translated_task = [packaged_task, cleanupPromise]() mutable { + auto translated_task = [callCtx]() mutable { // Execute the task. - packaged_task->operator()(); + callCtx->packagedTask(); // Note the future for the task's result. - auto taskFuture = packaged_task->get_future(); + auto taskFuture = callCtx->packagedTask.get_future(); // Clean up the task. - packaged_task.reset(); + callCtx->packagedTask.reset(); + auto cleanupPromise = std::move(callCtx->cleanupPromise); + // Release parameters. + callCtx.reset(); // Forward the task's result to our cleanup promise/future. - forwardPromise(cleanupPromise, &taskFuture); + forwardPromise(cleanupPromise, taskFuture); }; + // Create a promise/future that we will fulfill when we have cleaned up the task. + auto cleanupFuture = callCtx->cleanupPromise.get_future(); + // 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; - if (m_powerResource) { - m_powerResource->acquire(); - } - m_queue.emplace(front ? m_queue.begin() : m_queue.end(), std::move(translated_task)); - } else { - using FutureType = decltype(task(args...)); - return std::future(); - } + callCtx.reset(); - if (restart) { - // Restart task thread. - m_taskThread.start(std::bind(&Executor::runNext, this)); - m_threadRunning = true; - } + if (!execute(std::move(translated_task), queuePosition)) { + return std::future(); } - m_delayedCondition.notify_one(); return cleanupFuture; } +/// @name Externalize Executor::pushFunction() for common types. +/// @{ +extern template std::future Executor::pushFunction( + QueuePosition queuePosition, + std::function&& function) noexcept; +extern template std::future Executor::pushFunction( + QueuePosition queuePosition, + std::function&& function) noexcept; +extern template std::future Executor::pushFunction( + QueuePosition queuePosition, + std::function&& function) noexcept; +/// @} + } // namespace threading } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ExecutorFactory.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ExecutorFactory.h new file mode 100644 index 0000000000..be69d72d45 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ExecutorFactory.h @@ -0,0 +1,40 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_EXECUTORFACTORY_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_EXECUTORFACTORY_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace threading { + +/** + * @brief Create a single-thread executor. + * + * @return New executor reference or nullptr on error. + */ +std::shared_ptr createSingleThreadExecutor() noexcept; + +} // namespace threading +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_EXECUTORFACTORY_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ExecutorInterface.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ExecutorInterface.h new file mode 100644 index 0000000000..5762b85b73 --- /dev/null +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ExecutorInterface.h @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_EXECUTORINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_EXECUTORINTERFACE_H_ + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace threading { + +/** + * @brief Interface for asynchronous execution of functions. + * + * This interface enables submission of functions for asynchronous execution. The implementations should use thread pool + * to acquire threads for running functions, and may be single- or multi-threaded. + * + * Executors can be in normal mode, when they accept tasks, and in shutdown mode. In shutdown mode executors do not + * accept new tasks for processing, and any tasks, that haven't started an execution will be dropped. + * + * @code + * auto error = executor->execute([]{ ... }); + * if (error) { + * ACSDK_ERROR(LX(__func__).("executorError", error.message())); + * } + * @endcode + * + * @see Executor + * @see ThreadPool + */ +class ExecutorInterface { +public: + /** + * @brief Destructs an Executor. + * + * This method awaits till all running tasks are completed, and drops all enqueued tasks that haven't started + * execution. + */ + virtual ~ExecutorInterface() noexcept = default; + + /** + * @{ + * @brief Schedules a function for execution. + * + * Submits a function to be executed on an executor thread. + * + * @param[in] function Function to execute. Function must not be empty. + * + * @return Platform-independent error code. If successful, the error value is zero. + * @retval std::errc::invalid_argument If @a function is empty. + * @retval std::errc::operation_not_permitted If executor is shutdown. + */ + virtual std::error_condition execute(std::function&& function) noexcept = 0; + virtual std::error_condition execute(const std::function& function) noexcept = 0; + /// @} +}; + +} // namespace threading +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_THREADING_EXECUTORINTERFACE_H_ diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h index 3a26dffee5..1a4096cbe7 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/TaskThread.h @@ -53,9 +53,12 @@ class TaskThread { * * @param jobRunner Function that should execute jobs. The function should return @c true if there's more tasks * to be executed. + * @param moniker Thread moniker to use when executing @a jobRunner. All logging messages will use provided + * moniker for identifying the execution owner. + * * @return @c true if it succeeds to start the new jobRunner thread; @c false if it fails. */ - bool start(std::function jobRunner); + bool start(std::function jobRunner, const std::string& moniker); private: /** @@ -66,9 +69,6 @@ class TaskThread { */ void run(std::function jobRunner); - /// The monotonic start time of the thread. - std::chrono::steady_clock::time_point m_startTime; - /// Mutex for protecting the worker thread. std::mutex m_mutex; @@ -81,9 +81,6 @@ class TaskThread { /// Flag used to indicate that there is a new job starting. std::atomic_bool m_alreadyStarting; - /// The task thread moniker. - std::string m_moniker; - /// ThreadPool to use for obtaining threads. std::shared_ptr m_threadPool; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ThreadPool.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ThreadPool.h index c6ab1f83f2..8b06b86149 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ThreadPool.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/ThreadPool.h @@ -18,9 +18,6 @@ #include #include -#ifdef THREAD_AFFINITY -#include -#endif #include #include "WorkerThread.h" @@ -57,10 +54,12 @@ class ThreadPool { /** * Obtain a worker thread to operate on. * - * @param optionalMoniker the moniker of the worker desired. - * @return the worker thread obtained. + * This method returns a reference to worker object. If thread pool has worker objects in the pool, it returns one + * of them. If thread pool is empty, a newly constructed object is returned. + * + * @return Worker thread object. */ - std::unique_ptr obtainWorker(std::string optionalMoniker = ""); + std::unique_ptr obtainWorker(); /** * Release a worker @@ -81,7 +80,7 @@ class ThreadPool { * Obtain the current maximum threads for the thread pool. * @return the max threads the thread pool is currently set to hold. */ - uint32_t getMaxThreads(); + size_t getMaxThreads(); /** * Obtain statics for the thread pool @@ -103,10 +102,6 @@ class ThreadPool { static std::shared_ptr getDefaultThreadPool(); private: -#ifdef THREAD_AFFINITY - /// Map of Worker thread monikers to worker thread iterator in the worker queue. - std::map>::iterator> m_workerMap; -#endif /// Queue of worker threads to vend. std::list> m_workerQueue; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/WorkerThread.h b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/WorkerThread.h index 8ee992a49d..3daaa3b1b8 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Threading/WorkerThread.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Threading/WorkerThread.h @@ -57,10 +57,11 @@ class WorkerThread { void cancel(); /** - * Return the moniker for the worker thread. - * @return the worker thread moniker. + * Return thread id. + * + * @return Thread id for the allocated thread. */ - std::string getMoniker() const; + std::thread::id getThreadId() const; private: /** @@ -68,9 +69,6 @@ class WorkerThread { */ void runInternal(); - /// The thread moniker for the worker thread. - const std::string m_moniker; - /// Flag indicating the thread is stopping. std::atomic m_stop; @@ -88,6 +86,9 @@ class WorkerThread { /// Condition variable for waking the thread. std::condition_variable m_workReady; + + /// Platform-specific thread identifier. + std::thread::id m_threadId; }; } // namespace threading diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/MultiTimer.h b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/MultiTimer.h index 169a80b57b..bc86dedfd9 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/MultiTimer.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/MultiTimer.h @@ -45,17 +45,17 @@ class MultiTimer { * Factory method that creates a shared pointer to a MultiTimer. * @return A new instance of MultiTimer. */ - static std::shared_ptr createMultiTimer(); + static std::shared_ptr createMultiTimer() noexcept; /** * Constructor. */ - MultiTimer(); + MultiTimer() noexcept; /** * Destructor. */ - ~MultiTimer(); + ~MultiTimer() noexcept; /** * Submits a task to be executed after a given delay. @@ -66,14 +66,14 @@ class MultiTimer { * @param task The task to be executed. * @return A unique token that can be used to cancel this task. */ - Token submitTask(const std::chrono::milliseconds& delay, std::function task); + Token submitTask(const std::chrono::milliseconds& delay, std::function task) noexcept; /** * Removes a task from the queue. * * @param token The token used to identify the task to be canceled. */ - void cancelTask(Token token); + void cancelTask(Token token) noexcept; private: /** @@ -81,7 +81,7 @@ class MultiTimer { * * @return @c true if there are more timers scheduled; @c false otherwise. */ - bool executeTimer(); + bool executeTimer() noexcept; /** * Checks if there are any pending tasks within a grace period determined by an internal timeout. @@ -89,11 +89,15 @@ class MultiTimer { * @param lock The lock being held during the check. * @return @c true if there's at least one task left; @c false if there is no pending task. */ - bool hasNextLocked(std::unique_lock& lock); + bool hasNextLocked(std::unique_lock& lock) noexcept; /// Alias for the time point used in this class. using TimePoint = std::chrono::time_point; + /// Moniker for timer tasks. + /// Whenever timer runs a task, it guarantees that task's thread has this value set with @c ThreadMoniker. + const std::string m_timerMoniker; + /// The condition variable used to wait for the next task. std::condition_variable m_waitCondition; diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Timer.h b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Timer.h index bad0d3e6d4..c0edac49d8 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Timer.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/Timer.h @@ -23,10 +23,8 @@ #include #include -#include "AVSCommon/Utils/Error/FinallyGuard.h" -#include "AVSCommon/AVS/Initialization/SDKPrimitivesProvider.h" -#include "AVSCommon/Utils/Logger/LoggerUtils.h" -#include "AVSCommon/Utils/Timing/TimerDelegate.h" +#include +#include namespace alexaClientSDK { namespace avsCommon { @@ -34,6 +32,8 @@ namespace utils { namespace timing { /** + * @brief Timer to schedule task for delayed and periodic execution. + * * A @c Timer is used to schedule a callable type to run in the future. */ class Timer { @@ -42,7 +42,7 @@ class Timer { * Value for @c start()'s @c maxCount parameter which indicates that the @c Timer should continue firing * indefinitely. */ - static const size_t FOREVER = 0; + static constexpr size_t FOREVER = 0; /// Specifies different ways to apply the period of a recurring task. enum class PeriodType { @@ -51,8 +51,8 @@ class Timer { * period type ensures task calls occur on a predictable cadence. * * @note A timer makes one task call at a time, so if a task call takes more than one period to execute, - * the subsequent calls which would have occured while the task was still executing will be skipped, and - * the next call will not occur until the next period-multiple after the original task call completes. + * the subsequent calls which would have occured while the task was still executing will be skipped, and + * the next call will not occur until the next period-multiple after the original task call completes. */ ABSOLUTE, @@ -64,163 +64,188 @@ class Timer { }; /** - * Contructs a @c Timer. + * @brief Constructs a @c Timer. * - * @param timerDelegateFactory A factory to create the @c TimerDelegate. + * @param[in] timerDelegateFactory A factory to create the @c TimerDelegate. By if the parameter is nullptr, + * implementation will use TimerDelegateFactory. */ Timer( - std::shared_ptr timerDelegateFactory = - avs::initialization::SDKPrimitivesProvider::getInstance()->getTimerDelegateFactory()); + const std::shared_ptr& timerDelegateFactory = + avs::initialization::SDKPrimitivesProvider::getInstance()->getTimerDelegateFactory()) noexcept; /** - * Destructs a @c Timer. + * @brief Destructs a @c Timer. */ - ~Timer(); + ~Timer() noexcept; /// Static member function to get FOREVER. - static size_t getForever() { + static inline size_t getForever() noexcept { return FOREVER; } - /// Static member function to get TAG. - static std::string getTag() { - return "Timer"; - } - /** + * @brief Submit a callable type for periodic execution. + * * Submits a callable type (function, lambda expression, bind expression, or another function object) to be * executed after an initial delay, and then called repeatedly on a fixed time schedule. A @c Timer instance * manages only one running @c Timer at a time; calling @c start() on an already-running @c Timer will fail. * Note that the template parameters are typically inferred from the function paramters when calling @c start(). * - * @tparam Rep A type for measuring 'ticks' in a generic @c std::chrono::duration. - * @tparam Period A type for representing the number of ticks per second in a generic @c std::chrono::duration. - * @tparam Task The type of task to execute. - * @tparam Args The argument types for the task to execute. + * @tparam Delay Initial delay type. This can be any specialization of std::chrono::duration convertible to + * std::chrono::nanoseconds. + * @tparam Period Period type. This can be any specialization of std::chrono::duration convertible to + * std::chrono::nanoseconds. + * @tparam Task The type of task to execute. + * @tparam Args The argument types for the task to execute. + * + * @param[in] delay The non-negative time to wait before making the first task call. Negative values will cause + * this function to return false. + * @param[in] period The non-negative time to wait between subsequent task calls. Negative values will cause + * this function to return false. + * @param[in] periodType The type of period to use when making subsequent task calls. + * @param[in] maxCount The desired number of times to call task. Timer::getForever() means to call forever until + * Timer::stop() is called. Note that fewer than @a maxCount calls may occur if @a periodType + * is PeriodType::ABSOLUTE and the task runtime exceeds @a period. + * @param[in] task A callable type representing a task. + * @param[in] args The arguments to call the task with. * - * @param delay The non-negative time to wait before making the first task call. Negative values will cause this - * function to return false. - * @param period The non-negative time to wait between subsequent task calls. Negative values will cause this - * function to return false. - * @param periodType The type of period to use when making subsequent task calls. - * @param maxCount The desired number of times to call task. @c Timer::getForever() means to call forever until - * @c stop() is called. Note that fewer than @c maxCount calls may occur if @c periodType is - * @c PeriodType::ABSOLUTE and the task runtime exceeds @c period. - * @param task A callable type representing a task. - * @param args The arguments to call the task with. * @returns @c true if the timer started, else @c false. */ - template + template bool start( - const std::chrono::duration& delay, - const std::chrono::duration& period, + Delay&& delay, + Period&& period, PeriodType periodType, size_t maxCount, - Task task, - Args&&... args); + Task&& task, + Args&&... args) noexcept; /** + * @brief Submit a callable type for periodic execution. + * * Submits a callable type (function, lambda expression, bind expression, or another function object) to be * executed repeatedly on a fixed time schedule. A @c Timer instance manages only one running @c Timer at a time; * calling @c start() on an already-running @c Timer will fail. Note that the template parameters are typically * inferred from the function paramters when calling @c start(). * - * @tparam Rep A type for measuring 'ticks' in a generic @c std::chrono::duration. - * @tparam Period A type for representing the number of ticks per second in a generic @c std::chrono::duration. - * @tparam Task The type of task to execute. - * @tparam Args The argument types for the task to execute. + * @tparam Period Period and initial delay type. This can be any specialization of std::chrono::duration + * convertible to std::chrono::nanoseconds. + * @tparam Task The type of task to execute. This must be callable with @a args parameters. + * @tparam Args The argument types for the task to execute. + * + * @param[in] period The non-negative time to wait before each @a task call. Negative values will cause this + * function to return false. + * @param[in] periodType The type of period to use when making subsequent task calls. + * @param[in] maxCount The desired number of times to call task. Timer::getForever() means to call forever until + * Timer::stop() is called. Note that fewer than @a maxCount calls may occur if @a periodType + * is PeriodType::ABSOLUTE and the task runtime exceeds @a period. + * @param[in] task A callable type representing a task. + * @param[in] args The arguments to call @a task with. * - * @param period The non-negative time to wait before each @c task call. Negative values will cause this - * function to return false. - * @param periodType The type of period to use when making subsequent task calls. - * @param maxCount The desired number of times to call task. @c Timer::getForever() means to call forever until - * @c stop() is called. Note that fewer than @c maxCount calls may occur if @c periodType is - * @c PeriodType::ABSOLUTE and the task runtime exceeds @c period. - * @param task A callable type representing a task. - * @param args The arguments to call @c task with. * @returns @c true if the timer started, else @c false. */ - template - bool start( - const std::chrono::duration& period, - PeriodType periodType, - size_t maxCount, - Task task, - Args&&... args); + template + bool start(Period&& period, PeriodType periodType, size_t maxCount, Task&& task, Args&&... args) noexcept; /** + * @brief Submit a callable type for single execution with future result. + * * Submits a callable type (function, lambda expression, bind expression, or another function object) to be * executed once, after the specified duration. A @c Timer instance manages only one running @c Timer at a time; * calling @c start() on an already-running @c Timer will fail. Note that the template parameters are typically - * inferred from the function paramters when calling @c start(). + * inferred from the function parameters when calling @c start(). * - * @tparam Rep A type for measuring 'ticks' in a generic @c std::chrono::duration. - * @tparam Period A type for representing the number of ticks per second in a generic @c std::chrono::duration. - * @tparam Task The type of task to execute. - * @tparam Args The argument types for the task to execute. + * @tparam Delay Delay type. This can be any specialization of std::chrono::duration convertible to + * std::chrono::nanoseconds. + * @tparam Task The type of task to execute. + * @tparam Args The argument types for the task to execute. * - * @param delay The non-negative time to wait before calling @c task. Negative values will cause this - * function to return an invalid future. - * @param task A callable type representing a task. - * @param args The arguments to call @c task with. - * @returns A valid @c std::future for the return value of @c task if the timer started, else an invalid - * @c std::future. Note that the promise will be broken if @c stop() is called before @c task is called. + * @param[in] delay The non-negative time to wait before calling @a task. Negative values will cause this function + * to return an invalid future. + * @param[in] task A callable type representing a task. + * @param[in] args The arguments to call @c task with. + * + * @returns A valid @c std::future for the return value of @c task if the timer started, else an invalid @c + * std::future. Note that the promise will be broken if Timer::stop() is called before @a task is called. */ - template - auto start(const std::chrono::duration& delay, Task task, Args&&... args) - -> std::future; + template + auto start(Delay&& delay, Task&& task, Args&&... args) noexcept -> std::future; /** + * @brief Stop the timer. + * * Stops the @c Timer (if running). This will not interrupt an active call to the task, but will prevent any - * subequent calls to the task. If @c stop() is called while the task is executing, this function will block until + * subsequent calls to the task. If @c stop() is called while the task is executing, this function will block until * the task completes. * * @note In the special case that @c stop() is called from inside the task function, @c stop() will still prevent - * any subsequent calls to the task, but will *not* block as described above. + * any subsequent calls to the task, but will *not* block as described above. */ - void stop(); + void stop() noexcept; /** + * @brief Check if timer is active. + * * Reports whether the @c Timer is active. A timer is considered active if it is waiting to start a call to the * task, or if a call to the task is in progress. A timer is only considered inactive if it has not been started, * if all requested/scheduled calls to the task have completed, or after a call to @c stop(). * * @returns @c true if the @c Timer is active, else @c false. */ - bool isActive() const; + bool isActive() const noexcept; private: + /** + * Value for Timer::start()'s @a maxCount parameter which indicates that the @c Timer should fire once. + */ + static constexpr size_t ONCE = 1u; + /** * Atomically activates this @c Timer (by setting @c m_running). * * @returns @c true if the @c Timer was previously inactive, else @c false. */ - bool activate(); + bool activate() noexcept; /** * Waits for the @c Timer @c period, then calls @c task. * - * @tparam Rep A type for measuring 'ticks' in a generic @c std::chrono::duration. - * @tparam Period A type for representing the number of ticks per second in a generic @c std::chrono::duration. - * - * @param delay The non-negative time to wait before making the first @c task call. - * @param period The non-negative time to wait between subsequent @c task calls. - * @param periodType The type of period to use when making subsequent task calls. - * @param maxCount The desired number of times to call task. @c Timer::getForever() means to call forever until - * @c stop() is called. Note that fewer than @c maxCount calls may occur if @c periodType is - * @c PeriodType::ABSOLUTE and the task runtime exceeds @c period. - * @param task A callable type representing a task. + * @param[in] delayNano The non-negative time to wait before making the first @a task call. + * @param[in] periodNano The non-negative time to wait between subsequent @a task calls. + * @param[in] periodType The type of period to use when making subsequent task calls. + * @param[in] maxCount The desired number of times to call task. Timer::getForever() means to call forever until + * Timer::stop() is called. Note that fewer than @a maxCount calls may occur if @a periodType + * is PeriodType::ABSOLUTE and the task runtime exceeds @a period. + * @param[in] task Function object to call. It must be not empty. */ - template - void callTask( - std::chrono::duration delay, - std::chrono::duration period, + bool callTask( + const std::chrono::nanoseconds& delayNano, + const std::chrono::nanoseconds& periodNano, PeriodType periodType, size_t maxCount, - std::function task); + std::function&& task) noexcept; - /// The mutex to protect the @c TimerDelegate. - mutable std::mutex m_mutex; + /** + * @brief Proxy method for convering delay and period to nanoseconds. + * + * @tparam Delay Initial delay type. This can be any specialization of std::chrono::duration convertible to + * std::chrono::nanoseconds. + * @tparam Period Period type. This can be any specialization of std::chrono::duration convertible to + * std::chrono::nanoseconds. + * @param delayNano + * @param periodNano + * @param periodType + * @param maxCount + * @param task + * @return + */ + template + bool adaptTypesAndCallTask( + const Delay& delayNano, + const Period& periodNano, + PeriodType periodType, + size_t maxCount, + std::function&& task) noexcept; /// The @c TimerDelegateInterface which contains timer related logic. std::unique_ptr m_timer; @@ -229,114 +254,88 @@ class Timer { std::thread m_thread; }; -template +template bool Timer::start( - const std::chrono::duration& delay, - const std::chrono::duration& period, + Delay&& delay, + Period&& period, PeriodType periodType, size_t maxCount, - Task task, - Args&&... args) { - if (delay < std::chrono::duration::zero()) { - logger::acsdkError(logger::LogEntry(Timer::getTag(), "startFailed").d("reason", "negativeDelay")); - return false; - } - if (period < std::chrono::duration::zero()) { - logger::acsdkError(logger::LogEntry(Timer::getTag(), "startFailed").d("reason", "negativePeriod")); - return false; - } - - // Don't start if already running. - if (!activate()) { - logger::acsdkError(logger::LogEntry(Timer::getTag(), "startFailed").d("reason", "timerAlreadyActive")); - return false; - } - - // Remove arguments from the task's type by binding the arguments to the task. - using BoundTaskType = decltype(std::bind(std::forward(task), std::forward(args)...)); - auto boundTask = std::make_shared(std::bind(std::forward(task), std::forward(args)...)); - - // Remove the return type from the task by wrapping it in a lambda with no return value. - auto translatedTask = [boundTask]() { boundTask->operator()(); }; + Task&& task, + Args&&... args) noexcept { + // convert arguments to lambda + auto boundTask = std::bind(std::forward(task), std::forward(args)...); // Kick off the new timer thread. - callTask(delay, period, periodType, maxCount, translatedTask); - - return true; + return adaptTypesAndCallTask( + std::forward(delay), std::forward(period), periodType, maxCount, std::move(boundTask)); } -template -bool Timer::start( - const std::chrono::duration& period, - PeriodType periodType, - size_t maxCount, - Task task, - Args&&... args) { - return start(period, period, periodType, maxCount, std::forward(task), std::forward(args)...); +template +bool Timer::start(Period&& period, PeriodType periodType, size_t maxCount, Task&& task, Args&&... args) noexcept { + // convert arguments to lambda + auto boundTask = std::bind(std::forward(task), std::forward(args)...); + + // Kick off the new timer thread. + return adaptTypesAndCallTask( + std::forward(period), std::forward(period), periodType, maxCount, std::move(boundTask)); } -template -auto Timer::start(const std::chrono::duration& delay, Task task, Args&&... args) - -> std::future { - // Don't start if already running. - if (!activate()) { - logger::acsdkError(logger::LogEntry(Timer::getTag(), "startFailed").d("reason", "timerAlreadyActive")); - using FutureType = decltype(task(args...)); - return std::future(); - } +template +auto Timer::start(Delay&& delay, Task&& task, Args&&... args) noexcept -> std::future { + // Task result value type. + using Value = decltype(task(args...)); // Remove arguments from the task's 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. + * Create a std::packaged_task with the correct return type. The reference will be passed into execution context + * and will not be shared between threads. */ - using PackagedTaskType = std::packaged_task; - auto packagedTask = std::make_shared(boundTask); + auto packagedTask = std::make_shared>(std::move(boundTask)); + // Get future object reference for function result. + auto packagedTaskFuture = packagedTask->get_future(); // Remove the return type from the task by wrapping it in a lambda with no return value. - auto translatedTask = [packagedTask]() { packagedTask->operator()(); }; + auto translatedTask = std::bind(&std::packaged_task::operator(), std::move(packagedTask)); // Kick off the new timer thread. - static const size_t once = 1; - callTask(delay, delay, PeriodType::ABSOLUTE, once, translatedTask); + if (!adaptTypesAndCallTask( + std::forward(delay), + std::forward(delay), + PeriodType::ABSOLUTE, + ONCE, + std::move(translatedTask))) { + return std::future(); + } - return packagedTask->get_future(); + return packagedTaskFuture; } -template -void Timer::callTask( - std::chrono::duration delay, - std::chrono::duration period, +template +bool Timer::adaptTypesAndCallTask( + const Delay& delay, + const Period& period, PeriodType periodType, size_t maxCount, - std::function task) { - if (!m_timer) { - logger::acsdkError(logger::LogEntry(Timer::getTag(), "callTaskFailed").d("reason", "nullTimerDelegate")); - return; - } - - auto delegatePeriodType = sdkInterfaces::timing::TimerDelegateInterface::PeriodType::ABSOLUTE; - - switch (periodType) { - case PeriodType::ABSOLUTE: - delegatePeriodType = sdkInterfaces::timing::TimerDelegateInterface::PeriodType::ABSOLUTE; - break; - case PeriodType::RELATIVE: - delegatePeriodType = sdkInterfaces::timing::TimerDelegateInterface::PeriodType::RELATIVE; - break; - } - - m_timer->start( - std::chrono::duration_cast(delay), - std::chrono::duration_cast(period), - delegatePeriodType, - maxCount, - task); + std::function&& task) noexcept { + return callTask(delay, period, periodType, maxCount, std::move(task)); } +// Externalize templates for common parameter types. +extern template bool Timer::adaptTypesAndCallTask( + const std::chrono::milliseconds&, + const std::chrono::milliseconds&, + PeriodType, + size_t, + std::function&&) noexcept; +extern template bool Timer::adaptTypesAndCallTask( + const std::chrono::nanoseconds&, + const std::chrono::nanoseconds&, + PeriodType, + size_t, + std::function&&) noexcept; + } // namespace timing } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimerDelegate.h b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimerDelegate.h index 3a98f15b36..5795d42967 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimerDelegate.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/Timing/TimerDelegate.h @@ -57,13 +57,15 @@ class TimerDelegate : public sdkInterfaces::timing::TimerDelegateInterface { * @param periodType The type of period to use when making subsequent task calls. * @param maxCount The desired number of times to call task. * @param task A callable type representing a task. + * @param moniker Moniker value to use for a new thread. */ void timerLoop( std::chrono::nanoseconds delay, std::chrono::nanoseconds period, PeriodType periodType, size_t maxCount, - std::function task); + std::function task, + std::string moniker); /// Cleanup logic which waits for the thread to join if possible. @c m_callMutex must be held before calling. void cleanupLocked(); diff --git a/shared/acsdkManufactory/include/acsdkManufactory/internal/TypeIndex.h b/AVSCommon/Utils/include/AVSCommon/Utils/TypeIndex.h similarity index 82% rename from shared/acsdkManufactory/include/acsdkManufactory/internal/TypeIndex.h rename to AVSCommon/Utils/include/AVSCommon/Utils/TypeIndex.h index 68efa7cafb..28cfcc110b 100644 --- a/shared/acsdkManufactory/include/acsdkManufactory/internal/TypeIndex.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/TypeIndex.h @@ -13,10 +13,9 @@ * permissions and limitations under the License. */ -#ifndef ACSDKMANUFACTORY_INTERNAL_TYPEINDEX_H_ -#define ACSDKMANUFACTORY_INTERNAL_TYPEINDEX_H_ +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_TYPEINDEX_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_TYPEINDEX_H_ -#include #include #include #include @@ -26,12 +25,9 @@ #include #include -#include "acsdkManufactory/internal/TypeTraitsHelper.h" - namespace alexaClientSDK { -namespace acsdkManufactory { -namespace internal { - +namespace avsCommon { +namespace utils { /** * TypeIndex provide a sortable and hashable identity for C++ types, much like std::type_index, * but with optional use of RTTI. @@ -101,7 +97,7 @@ struct TypeIndex { * * @param value A value that uniquely identifies a type. */ - TypeIndex(Value value); + explicit TypeIndex(Value value); /// The value used to uniquely identify a type. Value m_value; @@ -135,7 +131,8 @@ char TypeIndex::TypeSpecific::m_instance; */ template inline TypeIndex getTypeIndex() { - return TypeIndex{&TypeIndex::TypeSpecific >::m_instance}; + return TypeIndex{&TypeIndex::TypeSpecific< + typename std::remove_cv::type>::type>::m_instance}; } inline std::string TypeIndex::getName() const { @@ -172,7 +169,7 @@ template inline void logTypeIndex(const std::string& name) { avsCommon::utils::logger::acsdkInfo(avsCommon::utils::logger::LogEntry("TypeIndex", __func__) .d("name", name) - .d("TypeIndex", internal::getTypeIndex().getName())); + .d("TypeIndex", getTypeIndex().getName())); } /** @@ -180,22 +177,22 @@ inline void logTypeIndex(const std::string& name) { */ #define ACSDK_LOG_TYPE_INDEX(type) logTypeIndex(#type) -} // namespace internal -} // namespace acsdkManufactory +} // namespace utils +} // namespace avsCommon } // namespace alexaClientSDK namespace std { template <> -struct hash { - std::size_t operator()(alexaClientSDK::acsdkManufactory::internal::TypeIndex typeIndex) const; +struct hash { + std::size_t operator()(alexaClientSDK::avsCommon::utils::TypeIndex typeIndex) const; }; -inline std::size_t hash::operator()( - alexaClientSDK::acsdkManufactory::internal::TypeIndex typeIndex) const { - return hash()(typeIndex.m_value); +inline std::size_t hash::operator()( + alexaClientSDK::avsCommon::utils::TypeIndex typeIndex) const { + return hash()(typeIndex.m_value); } } // namespace std -#endif // ACSDKMANUFACTORY_INTERNAL_TYPEINDEX_H_ \ No newline at end of file +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_INCLUDE_AVSCOMMON_UTILS_TYPEINDEX_H_ diff --git a/AVSCommon/Utils/privateInclude/AVSCommon/Utils/Threading/private/SharedExecutor.h b/AVSCommon/Utils/privateInclude/AVSCommon/Utils/Threading/private/SharedExecutor.h new file mode 100644 index 0000000000..d9472c4329 --- /dev/null +++ b/AVSCommon/Utils/privateInclude/AVSCommon/Utils/Threading/private/SharedExecutor.h @@ -0,0 +1,155 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_PRIVATEINCLUDE_AVSCOMMON_UTILS_THREADING_PRIVATE_SHAREDEXECUTOR_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_PRIVATEINCLUDE_AVSCOMMON_UTILS_THREADING_PRIVATE_SHAREDEXECUTOR_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace threading { + +/** + * @brief Shared executor implementation. + * + * This implementation is managed by std::shared_ptr. + */ +class SharedExecutor : public virtual ExecutorInterface { +public: + /** + * @brief Constructs an object. + */ + SharedExecutor() noexcept; + + /** + * @brief Destructs an Executor. + * + * This method awaits till all running tasks are completed, and drops all enqueued tasks that haven't started + * execution. + * + * @see #shutdown() + * @see #waitForSubmittedTasks() + */ + ~SharedExecutor() noexcept; + + /// @name Methods from ExecutorInterface. + /// @{ + std::error_condition execute(std::function&& function) noexcept override; + std::error_condition execute(const std::function& function) noexcept override; + /// @} + + /** + * Waits for any previously submitted tasks to complete. + */ + void waitForSubmittedTasks() noexcept; + + /// Clears the executor of outstanding tasks and refuses any additional tasks to be submitted. + void shutdown() noexcept; + + /** + * @brief Returns whether or not the executor is shutdown. + * + * @return True if Executor::shutdown() has been called. + */ + bool isShutdown() noexcept; + +private: + // Friend declaration. + friend class Executor; + + /// Alias for queue position. + using QueuePosition = Executor::QueuePosition; + + /// 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() noexcept; + + /** + * 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() noexcept; + + /** + * 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() noexcept; + + /** + * @brief Schedules a function for execution. + * + * Submits a function to be executed on an Executor thread. + * + * @param[in] function Function to execute. + * @param[in] queuePosition Position in the queue for the new task. + * + * @return Portable error code. If successful, value is zero. + */ + std::error_condition execute(std::function&& function, QueuePosition queuePosition) noexcept; + + /// Moniker for executed tasks. + /// Whenever executor runs a task, it guarantees that task's thread has this value set with @c ThreadMoniker. + const std::string m_executorMoniker; + + /// The queue of tasks + Queue m_queue; + + /// Flag to indicate if the taskThread already have an executing job. + bool m_threadRunning; + + /// 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; + + /// A @c PowerResource. + std::shared_ptr m_powerResource; + + /// The thread to execute tasks on. The thread must be declared last to be destructed first. + TaskThread m_taskThread; +}; + +} // namespace threading +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_PRIVATEINCLUDE_AVSCOMMON_UTILS_THREADING_PRIVATE_SHAREDEXECUTOR_H_ diff --git a/AVSCommon/Utils/src/BluetoothEventBus.cpp b/AVSCommon/Utils/src/BluetoothEventBus.cpp index 3753a2e744..811b259cca 100644 --- a/AVSCommon/Utils/src/BluetoothEventBus.cpp +++ b/AVSCommon/Utils/src/BluetoothEventBus.cpp @@ -23,7 +23,7 @@ namespace utils { namespace bluetooth { /// String to identify log entries originating from this file. -static const std::string TAG{"BluetoothEventBus"}; +#define TAG "BluetoothEventBus" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Configuration/ConfigurationNode.cpp b/AVSCommon/Utils/src/Configuration/ConfigurationNode.cpp index 59838ecd6c..f474ec255f 100644 --- a/AVSCommon/Utils/src/Configuration/ConfigurationNode.cpp +++ b/AVSCommon/Utils/src/Configuration/ConfigurationNode.cpp @@ -28,7 +28,7 @@ namespace utils { namespace configuration { /// String to identify log entries originating from this file. -static const std::string TAG("ConfigurationNode"); +#define TAG "ConfigurationNode" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -59,6 +59,9 @@ static std::string valueToString(const Value& value) { ACSDK_ERROR(LX("valueToStringFailed").d("reason", "AcceptReturnedFalse")); return ""; } +#else +// This method is not called when debug logging is disabled, but we need it for sizeof() evaluation. +std::string valueToString(const Value& value); #endif // ACSDK_DEBUG_LOG_ENABLED /** @@ -169,10 +172,14 @@ bool ConfigurationNode::getString(const std::string& key, const char** out, cons } ConfigurationNode ConfigurationNode::operator[](const std::string& key) const { - if (!*this) { + return getChildNode(key.c_str()); +} + +ConfigurationNode ConfigurationNode::getChildNode(const char* key) const { + if (!*this || !key) { return ConfigurationNode(); } - auto it = m_object->FindMember(key.c_str()); + auto it = m_object->FindMember(key); if (m_object->MemberEnd() == it || !it->value.IsObject()) { return ConfigurationNode(); } @@ -256,7 +263,7 @@ ConfigurationNode ConfigurationNode::operator[](const std::size_t index) const { return ConfigurationNode(); } const rapidjson::Value& objectRef = *m_object; - return ConfigurationNode(&objectRef[index]); + return ConfigurationNode(&objectRef[static_cast(index)]); } } // namespace configuration diff --git a/AVSCommon/Utils/src/DeviceInfo.cpp b/AVSCommon/Utils/src/DeviceInfo.cpp index 60f052ef03..7c77f5e456 100644 --- a/AVSCommon/Utils/src/DeviceInfo.cpp +++ b/AVSCommon/Utils/src/DeviceInfo.cpp @@ -23,7 +23,7 @@ namespace avsCommon { namespace utils { /// String to identify log entries originating from this file. -static const std::string TAG("DeviceInfo"); +#define TAG "DeviceInfo" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Executor.cpp b/AVSCommon/Utils/src/Executor.cpp deleted file mode 100644 index ac14bfc82e..0000000000 --- a/AVSCommon/Utils/src/Executor.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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/Memory/Memory.h" -#include "AVSCommon/Utils/Power/PowerMonitor.h" -#include "AVSCommon/Utils/Threading/Executor.h" - -namespace alexaClientSDK { -namespace avsCommon { -namespace utils { -namespace threading { - -/// An id for identifying instances. -static std::atomic g_id{0}; - -Executor::~Executor() { - shutdown(); -} - -Executor::Executor(const std::chrono::milliseconds& delayExit) : - m_threadRunning{false}, - m_timeout{delayExit}, - m_shutdown{false}, - m_id{g_id++} { - m_powerResource = power::PowerMonitor::getInstance()->createLocalPowerResource("Executor:" + std::to_string(m_id)); -} - -void Executor::waitForSubmittedTasks() { - 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(); - } - - if (m_powerResource) { - m_powerResource->release(); - } - - // It is acceptable that we enter LPM before - // the wait. TaskThread will still wait the intended m_timeout relative to the system - // not in LPM. - - return hasNext(); -} - -void Executor::shutdown() { - std::unique_lock lock{m_queueMutex}; - m_queue.clear(); - m_shutdown = true; - lock.unlock(); - waitForSubmittedTasks(); -} - -bool Executor::isShutdown() { - return m_shutdown; -} - -} // namespace threading -} // namespace utils -} // namespace avsCommon -} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/FileSystem/FileSystemUtilsLinux.cpp b/AVSCommon/Utils/src/FileSystem/FileSystemUtilsLinux.cpp index 1e53d1dfe4..4f93ed5395 100644 --- a/AVSCommon/Utils/src/FileSystem/FileSystemUtilsLinux.cpp +++ b/AVSCommon/Utils/src/FileSystem/FileSystemUtilsLinux.cpp @@ -37,7 +37,7 @@ namespace utils { namespace filesystem { /// String to identify log entries originating from this file. -static const std::string TAG("FileSystemUtils"); +#define TAG "FileSystemUtils" /// Create a LogEntry using this file's TAG and the specified event std::string. #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -67,6 +67,7 @@ class UmaskLocker { std::mutex UmaskLocker::s_umaskMutex; +#ifdef ACSDK_LOG_ENABLED static std::string getStrError(int error) { static const size_t BUFFER_SIZE = 255; char buffer[BUFFER_SIZE + 1]{}; @@ -74,6 +75,10 @@ static std::string getStrError(int error) { (void)ignore; // unused since the type can differ depending on the gnu or posix return buffer; } +#else +// Declaref function so it can be used in sizeof() expression when logging is disabled. +std::string getStrError(int); +#endif bool changePermissions(const std::string& path, Permissions perms) { if (chmod(path.c_str(), perms) != 0) { diff --git a/AVSCommon/Utils/src/FileSystem/FileSystemUtilsWindows.cpp b/AVSCommon/Utils/src/FileSystem/FileSystemUtilsWindows.cpp index 2047b834c8..49cc9f12e7 100644 --- a/AVSCommon/Utils/src/FileSystem/FileSystemUtilsWindows.cpp +++ b/AVSCommon/Utils/src/FileSystem/FileSystemUtilsWindows.cpp @@ -32,7 +32,7 @@ namespace utils { namespace filesystem { /// String to identify log entries originating from this file. -static const std::string TAG("FileSystemUtils"); +#define TAG "FileSystemUtils" /// Create a LogEntry using this file's TAG and the specified event std::string. #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -47,6 +47,7 @@ const Permissions DEFAULT_FILE_PERMISSIONS = OWNER_READ | OWNER_WRITE | GROUP_RE #define S_ISREG(mode) (((mode)&S_IFMT) == S_IFREG) #endif +#ifdef ACSDK_LOG_ENABLED static std::string getStrError(int error) { static const size_t BUFFER_SIZE = 255; char buffer[BUFFER_SIZE + 1]{}; @@ -54,6 +55,10 @@ static std::string getStrError(int error) { (void)ignore; // unused since the type can differ depending on the gnu or posix return buffer; } +#else +// Declaref function so it can be used in sizeof() expression when logging is disabled. +std::string getStrError(int); +#endif static std::string getBackslashPath(std::string path) { replace(path.begin(), path.end(), '/', '\\'); @@ -252,7 +257,7 @@ static bool removeDirectory(const std::string& path) { } while (FindNextFileA(handle, &data)); FindClose(handle); - result &= RemoveDirectoryA(path.c_str()); + result &= static_cast(RemoveDirectoryA(path.c_str())); return result; } diff --git a/AVSCommon/Utils/src/FileUtils.cpp b/AVSCommon/Utils/src/FileUtils.cpp index a3aee25376..8fb62965d2 100644 --- a/AVSCommon/Utils/src/FileUtils.cpp +++ b/AVSCommon/Utils/src/FileUtils.cpp @@ -26,7 +26,7 @@ namespace utils { namespace file { /// String to identify log entries originating from this file. -static const std::string TAG("FileUtils"); +#define TAG "FileUtils" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/FormattedAudioStreamAdapter.cpp b/AVSCommon/Utils/src/FormattedAudioStreamAdapter.cpp index 79a60652cf..54c3428e6b 100644 --- a/AVSCommon/Utils/src/FormattedAudioStreamAdapter.cpp +++ b/AVSCommon/Utils/src/FormattedAudioStreamAdapter.cpp @@ -22,7 +22,7 @@ namespace utils { namespace bluetooth { /// String to identify log entries originating from this file. -static const std::string TAG("FormattedAudioStreamAdapter"); +#define TAG "FormattedAudioStreamAdapter" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp b/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp index 632fd37de3..fa4b77b483 100644 --- a/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp +++ b/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp @@ -25,7 +25,7 @@ #include "AVSCommon/Utils/HTTP2/HTTP2MimeRequestSourceInterface.h" /// String to identify log entries originating from this file. -static const std::string TAG("HTTP2MimeRequestEncoder"); +#define TAG "HTTP2MimeRequestEncoder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp b/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp index a6598cb021..a5fe08722f 100644 --- a/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp +++ b/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp @@ -27,7 +27,7 @@ namespace http2 { using namespace avsCommon::utils::http; /// String to identify log entries originating from this file. -static const std::string TAG("HTTP2MimeResponseDecoder"); +#define TAG "HTTP2MimeResponseDecoder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/ID3Tags/ID3v2Tags.cpp b/AVSCommon/Utils/src/ID3Tags/ID3v2Tags.cpp index 46ee629fac..6a874b7a6c 100644 --- a/AVSCommon/Utils/src/ID3Tags/ID3v2Tags.cpp +++ b/AVSCommon/Utils/src/ID3Tags/ID3v2Tags.cpp @@ -19,7 +19,7 @@ #include "AVSCommon/Utils/Logger/Logger.h" /// String to identify log entries originating from this file. -static const std::string TAG("ID3v2Tags"); +#define TAG "ID3v2Tags" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/JSON/JSONGenerator.cpp b/AVSCommon/Utils/src/JSON/JSONGenerator.cpp index 8a9a2cc5c4..6d0ac1384a 100644 --- a/AVSCommon/Utils/src/JSON/JSONGenerator.cpp +++ b/AVSCommon/Utils/src/JSON/JSONGenerator.cpp @@ -21,7 +21,7 @@ #include "AVSCommon/Utils/JSON/JSONUtils.h" /// String to identify log entries originating from this file. -static const std::string TAG("JSONGenerator"); +#define TAG "JSONGenerator" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -40,7 +40,8 @@ JsonGenerator::JsonGenerator() : m_buffer{}, m_writer{m_buffer} { } bool JsonGenerator::startObject(const std::string& key) { - return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.StartObject(); + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && m_writer.StartObject(); } bool JsonGenerator::finishObject() { @@ -48,35 +49,43 @@ bool JsonGenerator::finishObject() { } 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); + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && 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); + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && 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); + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && 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); + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && 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); + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && 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); + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && 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); + auto keyLength = static_cast(key.length()); + return value && checkWriter() && m_writer.Key(key.c_str(), keyLength) && m_writer.String(value); } bool JsonGenerator::addMember(const std::string& key, double value) { - return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.Double(value); + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && m_writer.Double(value); } bool JsonGenerator::addRawJsonMember(const std::string& key, const std::string& json, bool validate) { @@ -88,12 +97,14 @@ bool JsonGenerator::addRawJsonMember(const std::string& key, const std::string& return false; } } - return checkWriter() && m_writer.Key(key.c_str(), key.length()) && + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && m_writer.RawValue(json.c_str(), json.length(), rapidjson::kStringType); } bool JsonGenerator::startArray(const std::string& key) { - return checkWriter() && m_writer.Key(key.c_str(), key.length()) && m_writer.StartArray(); + auto keyLength = static_cast(key.length()); + return checkWriter() && m_writer.Key(key.c_str(), keyLength) && m_writer.StartArray(); } bool JsonGenerator::finishArray() { diff --git a/AVSCommon/Utils/src/JSON/JSONUtils.cpp b/AVSCommon/Utils/src/JSON/JSONUtils.cpp index 3cf8edb1d0..327d0bab09 100644 --- a/AVSCommon/Utils/src/JSON/JSONUtils.cpp +++ b/AVSCommon/Utils/src/JSON/JSONUtils.cpp @@ -28,7 +28,7 @@ namespace json { namespace jsonUtils { /// String to identify log entries originating from this file. -static const std::string TAG("JsonUtils"); +#define TAG "JsonUtils" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/LibcurlUtils/CallbackData.cpp b/AVSCommon/Utils/src/LibcurlUtils/CallbackData.cpp index a0eb9d9a7c..7d470b61b4 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/CallbackData.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/CallbackData.cpp @@ -29,7 +29,7 @@ namespace libcurlUtils { using namespace alexaClientSDK::avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("CallbackData"); +#define TAG "CallbackData" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp b/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp index e72244aedd..b508f8e77b 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/CurlEasyHandleWrapper.cpp @@ -35,7 +35,7 @@ namespace libcurlUtils { using namespace avsCommon::utils::http; /// String to identify log entries originating from this file. -static const std::string TAG("CurlEasyHandleWrapper"); +#define TAG "CurlEasyHandleWrapper" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/LibcurlUtils/CurlMultiHandleWrapper.cpp b/AVSCommon/Utils/src/LibcurlUtils/CurlMultiHandleWrapper.cpp index 0e73398813..32eb291c76 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/CurlMultiHandleWrapper.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/CurlMultiHandleWrapper.cpp @@ -22,7 +22,7 @@ namespace utils { namespace libcurlUtils { /// String to identify log entries originating from this file. -static const std::string TAG("CurlMultiHandleWrapper"); +#define TAG "CurlMultiHandleWrapper" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -91,14 +91,32 @@ CURLMcode CurlMultiHandleWrapper::perform(int* runningHandles) { return result; } -CURLMcode CurlMultiHandleWrapper::wait(std::chrono::milliseconds timeout, int* countHandlesUpdated) { - auto result = curl_multi_wait(m_handle, NULL, 0, timeout.count(), countHandlesUpdated); +CURLMcode CurlMultiHandleWrapper::poll(std::chrono::milliseconds timeout, int* countHandlesUpdated) { +#if CURL_AT_LEAST_VERSION(7, 68, 0) + auto result = curl_multi_poll(m_handle, nullptr, 0, timeout.count(), countHandlesUpdated); +#else + auto result = curl_multi_wait(m_handle, nullptr, 0, static_cast(timeout.count()), countHandlesUpdated); +#endif if (result != CURLM_OK) { ACSDK_ERROR(LX("curlMultiWaitFailed").d("error", curl_multi_strerror(result))); } return result; } +bool CurlMultiHandleWrapper::wakeup() { + bool returnVal = true; +#if CURL_AT_LEAST_VERSION(7, 68, 0) + auto result = curl_multi_wakeup(m_handle); + if (result != CURLM_OK) { + ACSDK_ERROR(LX("curlMultiWakeupFailed").d("error", curl_multi_strerror(result))); + returnVal = false; + } +#else + // no-op +#endif + return returnVal; +} + CURLMsg* CurlMultiHandleWrapper::infoRead(int* messagesInQueue) { return curl_multi_info_read(m_handle, messagesInQueue); } diff --git a/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp b/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp index 0baa0dae9a..bf77fb389a 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/HTTPContentFetcherFactory.cpp @@ -24,7 +24,7 @@ namespace utils { namespace libcurlUtils { /// String to identify log entries originating from this file. -static const std::string TAG("HTTPContentFetcherFactory"); +#define TAG "HTTPContentFetcherFactory" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp b/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp index 42713cb77a..1b2610d982 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/HttpPost.cpp @@ -28,7 +28,7 @@ namespace libcurlUtils { using namespace alexaClientSDK::avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("HttpPost"); +#define TAG "HttpPost" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/LibcurlUtils/HttpPut.cpp b/AVSCommon/Utils/src/LibcurlUtils/HttpPut.cpp index 8690615f36..84d062658e 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/HttpPut.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/HttpPut.cpp @@ -31,7 +31,7 @@ namespace libcurlUtils { using namespace alexaClientSDK::avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("HttpPut"); +#define TAG "HttpPut" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp index db3955e792..30a370131e 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibCurlHttpContentFetcher.cpp @@ -31,10 +31,9 @@ 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"); +#define TAG "LibCurlHttpContentFetcher" /** * The timeout for a blocking write call to an @c AttachmentWriter. This value may be increased to decrease wakeups but @@ -412,7 +411,7 @@ std::unique_ptr LibCurlHttpContentFetcher::getCon } int numTransfersUpdated = 0; - result = curlMultiHandle->wait(WAIT_FOR_ACTIVITY_TIMEOUT, &numTransfersUpdated); + result = curlMultiHandle->poll(WAIT_FOR_ACTIVITY_TIMEOUT, &numTransfersUpdated); if (result != CURLM_OK) { ACSDK_ERROR(LX("getContentFailed") .d("reason", "multiWaitFailed") @@ -470,7 +469,7 @@ std::unique_ptr LibCurlHttpContentFetcher::getCon } int numTransfersUpdated = 0; - result = curlMultiHandle->wait(WAIT_FOR_ACTIVITY_TIMEOUT, &numTransfersUpdated); + result = curlMultiHandle->poll(WAIT_FOR_ACTIVITY_TIMEOUT, &numTransfersUpdated); if (result != CURLM_OK) { ACSDK_ERROR(LX("getContentFailed") .d("reason", "multiWaitFailed") diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp index 2e473873bd..e4702ba5c5 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Connection.cpp @@ -26,10 +26,9 @@ 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"); +#define TAG "LibcurlHTTP2Connection" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -167,6 +166,10 @@ void LibcurlHTTP2Connection::setIsStopping() { std::lock_guard lock(m_mutex); m_isStopping = true; m_cv.notify_one(); + auto multiCurlHandle = m_multi; + if (multiCurlHandle) { + multiCurlHandle->wakeup(); + } } std::shared_ptr LibcurlHTTP2Connection::dequeueRequest() { @@ -246,7 +249,7 @@ void LibcurlHTTP2Connection::networkLoop() { } int numTransfersUpdated = 0; - result = m_multi->wait(multiWaitTimeout, &numTransfersUpdated); + result = m_multi->poll(multiWaitTimeout, &numTransfersUpdated); if (result != CURLM_OK) { ACSDK_ERROR( LX_P("networkLoopStopping").d("reason", "multiWaitFailed").d("error", curl_multi_strerror(result))); @@ -329,6 +332,10 @@ bool LibcurlHTTP2Connection::addStream(std::shared_ptr stre } m_requestQueue.push_back(std::move(stream)); m_cv.notify_one(); + auto multiCurlHandle = m_multi; + if (multiCurlHandle) { + multiCurlHandle->wakeup(); + } return true; } @@ -351,7 +358,7 @@ void LibcurlHTTP2Connection::cleanupFinishedStreams() { .d("streamId", it->second->getId()) .d("result", curl_easy_strerror(message->data.result)) .d("CURLcode", message->data.result)); - releaseStream(*(it->second)); + releaseStream(it); } else { ACSDK_ERROR( LX_P("cleanupFinishedStreamError").d("reason", "streamNotFound").d("handle", message->easy_handle)); @@ -363,13 +370,15 @@ void LibcurlHTTP2Connection::cleanupFinishedStreams() { void LibcurlHTTP2Connection::cleanupCancelledAndStalledStreams() { auto it = m_activeStreams.begin(); while (it != m_activeStreams.end()) { - auto stream = (it++)->second; - if (stream->isCancelled()) { - cancelActiveStream(*stream); - } else if (stream->hasProgressTimedOut()) { + auto stream = it->second; + if (stream->hasProgressTimedOut()) { ACSDK_WARN(LX_P("streamProgressTimedOut").d("streamId", stream->getId())); stream->reportCompletion(HTTP2ResponseFinishedStatus::TIMEOUT); - releaseStream(*stream); + releaseStream(it); + } else if (stream->isCancelled()) { + cancelActiveStream(it); + } else { + it++; } } } @@ -395,16 +404,17 @@ void LibcurlHTTP2Connection::unPauseActiveStreams() { } } -bool LibcurlHTTP2Connection::cancelActiveStream(LibcurlHTTP2Request& stream) { - ACSDK_INFO(LX_P("cancelActiveStream").d("streamId", stream.getId())); - stream.reportCompletion(HTTP2ResponseFinishedStatus::CANCELLED); - return releaseStream(stream); +bool LibcurlHTTP2Connection::cancelActiveStream(ActiveStreamMap::iterator& iterator) { + auto stream = iterator->second; + ACSDK_INFO(LX_P("cancelActiveStream").d("streamId", stream->getId())); + stream->reportCompletion(HTTP2ResponseFinishedStatus::CANCELLED); + return releaseStream(iterator); } void LibcurlHTTP2Connection::cancelActiveStreams() { auto it = m_activeStreams.begin(); while (it != m_activeStreams.end()) { - cancelActiveStream(*(it++)->second); + cancelActiveStream(it); } } @@ -427,13 +437,14 @@ void LibcurlHTTP2Connection::cancelAllStreams() { cancelActiveStreams(); } -bool LibcurlHTTP2Connection::releaseStream(LibcurlHTTP2Request& stream) { - auto handle = stream.getCurlHandle(); - ACSDK_DEBUG9(LX_P("releaseStream").d("streamId", stream.getId())); +bool LibcurlHTTP2Connection::releaseStream(ActiveStreamMap::iterator& iterator) { + auto stream = iterator->second; + auto handle = stream->getCurlHandle(); + ACSDK_DEBUG9(LX_P("releaseStream").d("streamId", stream->getId())); auto result = m_multi->removeHandle(handle); - m_activeStreams.erase(handle); + iterator = m_activeStreams.erase(iterator); if (result != CURLM_OK) { - ACSDK_ERROR(LX_P("releaseStreamFailed").d("reason", "removeHandleFailed").d("streamId", stream.getId())); + ACSDK_ERROR(LX_P("releaseStreamFailed").d("reason", "removeHandleFailed").d("streamId", stream->getId())); return false; } return true; diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2ConnectionFactory.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2ConnectionFactory.cpp index ca262ee74b..9bbd9c26ec 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2ConnectionFactory.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2ConnectionFactory.cpp @@ -24,7 +24,7 @@ namespace utils { namespace libcurlUtils { /// String to identify log entries originating from this file. -static const std::string TAG("LibcurlHTTP2ConnectionFactory"); +#define TAG "LibcurlHTTP2ConnectionFactory" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp index 5bdafb015e..ebba4d2012 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlHTTP2Request.cpp @@ -28,7 +28,7 @@ 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"); +#define TAG "LibcurlHTTP2Request" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/LibcurlUtils/LibcurlUtils.cpp b/AVSCommon/Utils/src/LibcurlUtils/LibcurlUtils.cpp index 3bedd403fd..497d7db9d3 100644 --- a/AVSCommon/Utils/src/LibcurlUtils/LibcurlUtils.cpp +++ b/AVSCommon/Utils/src/LibcurlUtils/LibcurlUtils.cpp @@ -28,7 +28,7 @@ namespace utils { namespace libcurlUtils { /// String to identify log entries originating from this file. -static const std::string TAG("LibcurlUtils"); +#define TAG "LibcurlUtils" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Logger/LogEntry.cpp b/AVSCommon/Utils/src/Logger/LogEntry.cpp index 593d687171..5cde811583 100644 --- a/AVSCommon/Utils/src/Logger/LogEntry.cpp +++ b/AVSCommon/Utils/src/Logger/LogEntry.cpp @@ -24,44 +24,53 @@ namespace utils { namespace logger { /// List of characters we need to escape. -static const char* RESERVED_METADATA_CHARS = R"(\,=:)"; +static constexpr auto RESERVED_METADATA_CHARS = R"(\,=:)"; /// Escape sequence for '%'. -static const std::string ESCAPED_METADATA_ESCAPE = R"(\\)"; +static constexpr auto ESCAPED_METADATA_ESCAPE = R"(\\)"; /// Escape sequence for ','. -static const std::string ESCAPED_PAIR_SEPARATOR = R"(\,)"; +static constexpr auto ESCAPED_PAIR_SEPARATOR = R"(\,)"; /// Escape sequence for ':'. -static const std::string ESCAPED_SECTION_SEPARATOR = R"(\:)"; +static constexpr auto ESCAPED_SECTION_SEPARATOR = R"(\:)"; /// Escape sequence for '='. -static const std::string ESCAPED_KEY_VALUE_SEPARATOR = R"(\=)"; +static constexpr auto ESCAPED_KEY_VALUE_SEPARATOR = R"(\=)"; /// Reserved in metadata sequences for escaping other reserved values. -static const char METADATA_ESCAPE = '\\'; +static constexpr char METADATA_ESCAPE = '\\'; /// Reserved in metadata sequences to separate key,value pairs. -static const char PAIR_SEPARATOR = ','; +static constexpr char PAIR_SEPARATOR = ','; /// Reserved in metadata sequences to separate them from a preceding event and an optional terminal message. -static const char SECTION_SEPARATOR = ':'; +static constexpr char SECTION_SEPARATOR = ':'; /// String for boolean TRUE -static const std::string BOOL_TRUE = "true"; +static constexpr auto BOOL_TRUE = "true"; /// String for boolean FALSE -static const std::string BOOL_FALSE = "false"; +static constexpr auto BOOL_FALSE = "false"; -LogEntry::LogEntry(const std::string& source, const char* event) : m_hasMetadata(false) { - m_stream << source << SECTION_SEPARATOR; +/// Array of strings for obfuscation. +/// @see LogEntry::obfuscatePrivateData() +static constexpr const char* const DENYLIST[] = {"ssid"}; + +LogEntry::LogEntry(const char* source, const char* event) : m_hasMetadata(false) { + if (source) { + m_stream << source; + } + m_stream << 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(const std::string& source, const char* event) : LogEntry(source.c_str(), event) { +} + +LogEntry::LogEntry(const std::string& source, const std::string& event) : LogEntry(source.c_str(), event.c_str()) { } LogEntry& LogEntry::d(const char* key, const char* value) { @@ -153,6 +162,39 @@ void LogEntry::appendEscapedString(const char* in) { } } +LogEntry& LogEntry::obfuscatePrivateData(const char* key, const std::string& value) { + // if value contains any private label, obfuscate the section after the label + // since it can (but shouldn't) contain multiple, obfuscate from the earliest one found onward + auto firstPosition = value.length(); + + const size_t count = sizeof(DENYLIST) / sizeof(DENYLIST[0]); + for (size_t index = 0; index < count; ++index) { + const auto privateLabel = DENYLIST[index]; + const auto privateLabelLen = std::strlen(privateLabel); + auto it = std::search( + value.begin(), + value.end(), + privateLabel, + privateLabel + privateLabelLen, + [](char valueChar, char denyListChar) { return std::tolower(valueChar) == std::tolower(denyListChar); }); + if (it != value.end()) { + // capture the least value + auto thisPosition = std::distance(value.begin(), it) + privateLabelLen; + if (thisPosition < firstPosition) { + firstPosition = thisPosition; + } + } + } + + if (firstPosition <= value.length()) { + // hash everything after the label itself + auto labelPart = value.substr(0, firstPosition); + auto obfuscatedPart = std::to_string(std::hash{}(value.substr(firstPosition))); + return d(key, labelPart + obfuscatedPart); + } + return d(key, value); +} + } // namespace logger } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/Logger/LogEntryBuffer.cpp b/AVSCommon/Utils/src/Logger/LogEntryBuffer.cpp index 28c442d870..4932741cd2 100644 --- a/AVSCommon/Utils/src/Logger/LogEntryBuffer.cpp +++ b/AVSCommon/Utils/src/Logger/LogEntryBuffer.cpp @@ -50,7 +50,7 @@ LogEntryBuffer::int_type LogEntryBuffer::overflow(int_type ch) { setg(newBase, gptr() + delta, newEnd); m_base = newBase; - *pptr() = ch; + *pptr() = static_cast(ch); pbump(1); return ch; } diff --git a/AVSCommon/Utils/src/Logger/Logger.cpp b/AVSCommon/Utils/src/Logger/Logger.cpp index 77097fd1b9..b5c5c4eb3f 100644 --- a/AVSCommon/Utils/src/Logger/Logger.cpp +++ b/AVSCommon/Utils/src/Logger/Logger.cpp @@ -24,11 +24,13 @@ namespace utils { namespace logger { /// Configuration key for root level "logger" object. -static const std::string CONFIG_KEY_LOGGER = "logger"; +static constexpr auto 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 CONFIG_KEY_LOG_LEVEL = "logLevel"; +/// Predefined thread id for messages that are logged when application exits. +/// @see Logger::logAtExit() static constexpr auto AT_EXIT_THREAD_ID = "0"; Logger::Logger(Level level) : m_level{level} { @@ -42,7 +44,7 @@ void Logger::log(Level level, const LogEntry& entry) { void Logger::init(const configuration::ConfigurationNode configuration) { if (!initLogLevel(configuration)) { - initLogLevel(configuration::ConfigurationNode::getRoot()[CONFIG_KEY_LOGGER]); + initLogLevel(configuration::ConfigurationNode::getRoot()[std::string{CONFIG_KEY_LOGGER}]); } } diff --git a/AVSCommon/Utils/src/Logger/LoggerSinkManager.cpp b/AVSCommon/Utils/src/Logger/LoggerSinkManager.cpp index c4f6767384..7271f35fee 100644 --- a/AVSCommon/Utils/src/Logger/LoggerSinkManager.cpp +++ b/AVSCommon/Utils/src/Logger/LoggerSinkManager.cpp @@ -15,6 +15,7 @@ #include +#include "AVSCommon/Utils/Logger/ConsoleLogger.h" #include "AVSCommon/Utils/Logger/LoggerSinkManager.h" namespace alexaClientSDK { @@ -85,7 +86,7 @@ void LoggerSinkManager::initialize(const std::shared_ptr& sink) { } } -LoggerSinkManager::LoggerSinkManager() : m_sink{ACSDK_GET_SINK_LOGGER()}, m_level(Level::UNKNOWN) { +LoggerSinkManager::LoggerSinkManager() : m_sink{getConsoleLogger()}, m_level(Level::UNKNOWN) { } } // namespace logger diff --git a/AVSCommon/Utils/src/Logger/LoggerUtils.cpp b/AVSCommon/Utils/src/Logger/LoggerUtils.cpp index b887a05468..4dce08379f 100644 --- a/AVSCommon/Utils/src/Logger/LoggerUtils.cpp +++ b/AVSCommon/Utils/src/Logger/LoggerUtils.cpp @@ -13,6 +13,7 @@ * permissions and limitations under the License. */ +#include #include #include #include diff --git a/AVSCommon/Utils/src/Logger/ModuleLogger.cpp b/AVSCommon/Utils/src/Logger/ModuleLogger.cpp index 7b7222a592..92c90c12d0 100644 --- a/AVSCommon/Utils/src/Logger/ModuleLogger.cpp +++ b/AVSCommon/Utils/src/Logger/ModuleLogger.cpp @@ -51,12 +51,10 @@ void ModuleLogger::onSinkChanged(const std::shared_ptr& logger) { } void ModuleLogger::updateLogLevel() { - if (Level::UNKNOWN == m_sinkLogLevel) { + if (Level::UNKNOWN != m_moduleLogLevel) { Logger::setLevel(m_moduleLogLevel); - } else if (Level::UNKNOWN == m_moduleLogLevel) { - Logger::setLevel(m_sinkLogLevel); } else { - Logger::setLevel((m_sinkLogLevel > m_moduleLogLevel) ? m_sinkLogLevel : m_moduleLogLevel); + Logger::setLevel(m_sinkLogLevel); } } diff --git a/AVSCommon/Utils/src/Logger/ThreadMoniker.cpp b/AVSCommon/Utils/src/Logger/ThreadMoniker.cpp index 363ae08bd9..572799d3be 100644 --- a/AVSCommon/Utils/src/Logger/ThreadMoniker.cpp +++ b/AVSCommon/Utils/src/Logger/ThreadMoniker.cpp @@ -13,55 +13,144 @@ * permissions and limitations under the License. */ +#include #include +#include #include -#include #include + +#if defined(_WIN32) || defined(__QNX__) +#include #include #include +#endif -#include "AVSCommon/Utils/Logger/ThreadMoniker.h" +#include namespace alexaClientSDK { namespace avsCommon { namespace utils { namespace logger { -/// Counter to generate (small) unique thread monikers. -static std::atomic g_nextThreadMoniker(1); +/// Space character for moniker formatting. +static constexpr char CHAR_SPACE = ' '; -ThreadMoniker::ThreadMoniker(const std::string& moniker) : m_moniker{moniker.empty() ? generateMoniker() : moniker} { -} +/// Colon character for moniker prefix separation. +static constexpr char CHAR_COLON = ':'; -std::string ThreadMoniker::generateMoniker() { - std::ostringstream stream; - stream << std::setw(3) << std::hex << std::right << g_nextThreadMoniker++; - return stream.str(); -} +/// Size of formatted moniker in characters. +static constexpr size_t MONIKER_SIZE_CHARS = 5u; -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; +#if defined(_WIN32) || defined(__QNX__) +/// Map each thread to a moniker. +static std::unordered_map monikerMap; +/// Lock used to synchronize access to the local maps. +static std::mutex monikerMutex; - std::lock_guard lock{monikerMutex}; +/** + * Return the moniker value reference for the current thread for OS that don't support thread local variables. + * + * @return The moniker for the @c std::this_thread. + */ +static inline std::string getMonikerValue() noexcept { 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; + std::lock_guard lock{monikerMutex}; + auto entry = monikerMap.find(id); + if (entry == monikerMap.end()) { + entry = monikerMap.emplace(std::make_pair(id, ThreadMoniker::generateMoniker())).first; } return entry->second; } +/** + * @brief Set the moniker value reference for the current thread for OS that doesn't support thread local variables. + * + * @param moniker Moniker value to set. + */ +static inline void setMonikerValue(const std::string& moniker) noexcept { + auto id = std::this_thread::get_id(); + std::lock_guard lock{monikerMutex}; + auto entry = monikerMap.find(id); + if (entry == monikerMap.end()) { + monikerMap.emplace(std::make_pair(id, moniker)); + } else { + entry->second = moniker; + } +} +#else + +/** + * @brief Thread-local moniker value. + * + * Thread local destructors are called before static member destructors. If client code contains any static instance, + * that attempts to use Logging API from destructor, and exit() call is issued, the @a threadMoniker variable is + * released and running threads attempt to access string data through dangling pointers. + * + * To address the issue we use a type with a trivial destructor. + * + * @note This variable is defined only for platforms, that support @a thread_local. + * + * @see [basic.start.term]/p1 from C++ spec. + * @private + */ +static thread_local struct { + // Number of characters in @a value. It can be between 0 and sizeof(value) - 1. + std::size_t len; + // Moniker value. This value is not null-terminated. + char value[16]; +} threadMoniker = {0, {0}}; + +/** + * @brief Changes moniker value for the caller's thread. + * + * @param moniker Value to set. + */ +static inline void setMonikerValue(const std::string& moniker) noexcept { + auto& tm = threadMoniker; + tm.len = std::min(sizeof(tm.value), moniker.length()); + std::memcpy(tm.value, moniker.data(), tm.len); +} + +/** + * @brief Returns reference to this thread's moniker value. + */ +static inline std::string getMonikerValue() noexcept { + auto& tm = threadMoniker; + if (!tm.len) { + setMonikerValue(ThreadMoniker::generateMoniker()); + } + return std::string(tm.value, tm.value + tm.len); +} + +#endif + +std::string ThreadMoniker::generateMoniker(char prefix) noexcept { + /// Counter to generate (small) unique thread monikers. + static std::atomic g_nextThreadMoniker(1); + + std::stringstream ss; + if (prefix) { + ss << prefix << CHAR_COLON; + } + ss << std::hex << g_nextThreadMoniker++; + auto nextMoniker = ss.str(); + + if (nextMoniker.size() < MONIKER_SIZE_CHARS) { + nextMoniker.reserve(MONIKER_SIZE_CHARS); + nextMoniker.insert(nextMoniker.begin(), MONIKER_SIZE_CHARS - nextMoniker.size(), CHAR_SPACE); + } + + return nextMoniker; +} + +std::string ThreadMoniker::getThisThreadMoniker() noexcept { + return getMonikerValue(); +} + +void ThreadMoniker::setThisThreadMoniker(const std::string& moniker) noexcept { + setMonikerValue(moniker); +} + } // namespace logger } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/MacAddressString.cpp b/AVSCommon/Utils/src/MacAddressString.cpp index 98489d5b66..ff592c67f4 100644 --- a/AVSCommon/Utils/src/MacAddressString.cpp +++ b/AVSCommon/Utils/src/MacAddressString.cpp @@ -15,6 +15,7 @@ #include +#include #include #include diff --git a/AVSCommon/Utils/src/MediaPlayer/PlaybackContext.cpp b/AVSCommon/Utils/src/MediaPlayer/PlaybackContext.cpp index 1975d473e3..e6f4fce4c1 100644 --- a/AVSCommon/Utils/src/MediaPlayer/PlaybackContext.cpp +++ b/AVSCommon/Utils/src/MediaPlayer/PlaybackContext.cpp @@ -24,7 +24,7 @@ namespace utils { namespace mediaPlayer { /// String to identify log entries originating from this file. -static const std::string TAG("PlaybackContext"); +#define TAG "PlaybackContext" /** * Create a @c LogEntry using this file's @c TAG and the specified event string. diff --git a/AVSCommon/Utils/src/MediaPlayer/PooledMediaPlayerFactory.cpp b/AVSCommon/Utils/src/MediaPlayer/PooledMediaPlayerFactory.cpp index 435866fb30..9dd503af55 100644 --- a/AVSCommon/Utils/src/MediaPlayer/PooledMediaPlayerFactory.cpp +++ b/AVSCommon/Utils/src/MediaPlayer/PooledMediaPlayerFactory.cpp @@ -23,7 +23,7 @@ namespace mediaPlayer { using namespace avsCommon::utils::mediaPlayer; -static const std::string TAG("PooledMediaPlayerFactory"); +#define TAG "PooledMediaPlayerFactory" #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/Utils/src/MediaPlayer/PooledMediaResourceProvider.cpp b/AVSCommon/Utils/src/MediaPlayer/PooledMediaResourceProvider.cpp index 06858a3020..9bea774dde 100644 --- a/AVSCommon/Utils/src/MediaPlayer/PooledMediaResourceProvider.cpp +++ b/AVSCommon/Utils/src/MediaPlayer/PooledMediaResourceProvider.cpp @@ -25,7 +25,7 @@ namespace mediaPlayer { using namespace avsCommon::sdkInterfaces; -static const std::string TAG("PooledMediaResourceProvider"); +#define TAG "PooledMediaResourceProvider" #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) diff --git a/AVSCommon/Utils/src/Metrics/DataPointCounterBuilder.cpp b/AVSCommon/Utils/src/Metrics/DataPointCounterBuilder.cpp index fb9d8c1a04..c74a2efafb 100644 --- a/AVSCommon/Utils/src/Metrics/DataPointCounterBuilder.cpp +++ b/AVSCommon/Utils/src/Metrics/DataPointCounterBuilder.cpp @@ -25,7 +25,7 @@ namespace utils { namespace metrics { /// String to identify log entries originating from this file. -static const std::string TAG("DataPointCounterBuilder"); +#define TAG "DataPointCounterBuilder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Metrics/DataPointDurationBuilder.cpp b/AVSCommon/Utils/src/Metrics/DataPointDurationBuilder.cpp index 3099243963..ab728f8413 100644 --- a/AVSCommon/Utils/src/Metrics/DataPointDurationBuilder.cpp +++ b/AVSCommon/Utils/src/Metrics/DataPointDurationBuilder.cpp @@ -23,7 +23,7 @@ namespace utils { namespace metrics { /// String to identify log entries originating from this file. -static const std::string TAG("DataPointDurationBuilder"); +#define TAG "DataPointDurationBuilder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Metrics/MetricEvent.cpp b/AVSCommon/Utils/src/Metrics/MetricEvent.cpp index c93393dc63..f8993a1ba4 100644 --- a/AVSCommon/Utils/src/Metrics/MetricEvent.cpp +++ b/AVSCommon/Utils/src/Metrics/MetricEvent.cpp @@ -25,7 +25,7 @@ namespace utils { namespace metrics { /// String to identify log entries originating from this file. -static const std::string TAG("MetricEvent"); +#define TAG "MetricEvent" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Metrics/MetricEventBuilder.cpp b/AVSCommon/Utils/src/Metrics/MetricEventBuilder.cpp index 1b49ebf7f1..b50608877e 100644 --- a/AVSCommon/Utils/src/Metrics/MetricEventBuilder.cpp +++ b/AVSCommon/Utils/src/Metrics/MetricEventBuilder.cpp @@ -22,7 +22,7 @@ namespace utils { namespace metrics { /// String to identify log entries originating from this file. -static const std::string TAG("MetricEventBuilder"); +#define TAG "MetricEventBuilder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Metrics/UplData.cpp b/AVSCommon/Utils/src/Metrics/UplData.cpp index 846547e8ca..d1028f0571 100644 --- a/AVSCommon/Utils/src/Metrics/UplData.cpp +++ b/AVSCommon/Utils/src/Metrics/UplData.cpp @@ -23,7 +23,7 @@ namespace utils { namespace metrics { /// String to identify log entries originating from this file. -static const std::string TAG("UplData"); +#define TAG "UplData" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/MultiTimer.cpp b/AVSCommon/Utils/src/MultiTimer.cpp index a5ba08c68b..bf3890ed65 100644 --- a/AVSCommon/Utils/src/MultiTimer.cpp +++ b/AVSCommon/Utils/src/MultiTimer.cpp @@ -15,9 +15,9 @@ #include #include - -#include "AVSCommon/Utils/Logger/Logger.h" -#include "AVSCommon/Utils/Timing/Timer.h" +#include +#include +#include namespace alexaClientSDK { namespace avsCommon { @@ -25,26 +25,31 @@ namespace utils { namespace timing { /// String to identify log entries originating from this file. -static const std::string TAG("MultiTimer"); +#define TAG "MultiTimer" /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /// Grace period used to avoid restarting the internal thread too often. static const std::chrono::milliseconds GRACE_PERIOD{500}; -std::shared_ptr MultiTimer::createMultiTimer() { +std::shared_ptr MultiTimer::createMultiTimer() noexcept { return std::make_shared(); } -MultiTimer::MultiTimer() : m_isRunning{false}, m_isBeingDestroyed{false}, m_nextToken{0} { +MultiTimer::MultiTimer() noexcept : + m_timerMoniker{utils::logger::ThreadMoniker::generateMoniker(utils::logger::ThreadMoniker::PREFIX_TIMER)}, + m_isRunning{false}, + m_isBeingDestroyed{false}, + m_nextToken{0} { + ACSDK_DEBUG5(LX("init").d("moniker", m_timerMoniker)); } -MultiTimer::~MultiTimer() { +MultiTimer::~MultiTimer() noexcept { std::unique_lock lock{m_waitMutex}; m_timers.clear(); m_tasks.clear(); @@ -57,7 +62,7 @@ MultiTimer::~MultiTimer() { } } -MultiTimer::Token MultiTimer::submitTask(const std::chrono::milliseconds& delay, std::function task) { +MultiTimer::Token MultiTimer::submitTask(const std::chrono::milliseconds& delay, std::function task) noexcept { std::unique_lock lock{m_waitMutex}; auto token = m_nextToken++; @@ -69,7 +74,7 @@ MultiTimer::Token MultiTimer::submitTask(const std::chrono::milliseconds& delay, // Kick-off task execution if needed. if (!m_isRunning) { m_isRunning = true; - m_timerThread.start(std::bind(&MultiTimer::executeTimer, this)); + m_timerThread.start(std::bind(&MultiTimer::executeTimer, this), m_timerMoniker); } else { // Wake up timer thread if the new task is the next to expire. if (m_timers.begin()->second == token) { @@ -80,7 +85,7 @@ MultiTimer::Token MultiTimer::submitTask(const std::chrono::milliseconds& delay, return token; } -void MultiTimer::cancelTask(Token token) { +void MultiTimer::cancelTask(Token token) noexcept { std::unique_lock lock{m_waitMutex}; auto taskIt = m_tasks.find(token); if (taskIt != m_tasks.end()) { @@ -99,7 +104,7 @@ void MultiTimer::cancelTask(Token token) { } } -bool MultiTimer::executeTimer() { +bool MultiTimer::executeTimer() noexcept { std::unique_lock lock{m_waitMutex}; while (!m_timers.empty()) { auto now = std::chrono::steady_clock::now(); @@ -115,17 +120,23 @@ bool MultiTimer::executeTimer() { // Execute task. auto taskIt = m_tasks.find(nextIt->second); if (taskIt != m_tasks.end()) { - auto& task = taskIt->second.second; - task(); + auto task = std::move(taskIt->second.second); m_tasks.erase(taskIt); + m_timers.erase(nextIt); + lock.unlock(); + if (task) { + task(); + } + lock.lock(); + } else { + m_timers.erase(nextIt); } - m_timers.erase(nextIt); } } return hasNextLocked(lock); } -bool MultiTimer::hasNextLocked(std::unique_lock& lock) { +bool MultiTimer::hasNextLocked(std::unique_lock& lock) noexcept { m_waitCondition.wait_for(lock, GRACE_PERIOD, [this] { return (!m_tasks.empty()) || m_isBeingDestroyed; }); m_isRunning = (!m_isBeingDestroyed) && !m_tasks.empty(); return m_isRunning; diff --git a/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp b/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp index 327cf5528f..94a35adf46 100644 --- a/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp +++ b/AVSCommon/Utils/src/Network/InternetConnectionMonitor.cpp @@ -26,7 +26,7 @@ using namespace avsCommon::utils::sds; using namespace utils::timing; /// String to identify log entries originating from this file. -static const std::string TAG("InternetConnectionMonitor"); +#define TAG "InternetConnectionMonitor" /** * Create a @c LogEntry using this file's @c TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Power/AggregatedPowerResourceManager.cpp b/AVSCommon/Utils/src/Power/AggregatedPowerResourceManager.cpp index 0a20750f66..d03484ee47 100644 --- a/AVSCommon/Utils/src/Power/AggregatedPowerResourceManager.cpp +++ b/AVSCommon/Utils/src/Power/AggregatedPowerResourceManager.cpp @@ -26,7 +26,7 @@ namespace power { using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("AggregatedPowerResourceManager"); +#define TAG "AggregatedPowerResourceManager" /// Prefix to uniquely identify the resource. static const std::string PREFIX = "ACSDK_"; @@ -89,24 +89,16 @@ AggregatedPowerResourceManager::AggregatedPowerResourceManager( void AggregatedPowerResourceManager::acquirePowerResource( const std::string& component, const PowerResourceLevel level) { - ACSDK_DEBUG9(LX(__func__)); - - std::lock_guard lock(m_mutex); - m_appPowerResourceManager->acquirePowerResource(component, level); + ACSDK_ERROR(LX(__func__).m("API is deprecated.Please see PowerResourceManagerInterface for alternatives")); } void AggregatedPowerResourceManager::releasePowerResource(const std::string& component) { - ACSDK_DEBUG9(LX(__func__)); - - std::lock_guard lock(m_mutex); - m_appPowerResourceManager->releasePowerResource(component); + ACSDK_ERROR(LX(__func__).m("API is deprecated.Please see PowerResourceManagerInterface for alternatives")); }; bool AggregatedPowerResourceManager::isPowerResourceAcquired(const std::string& component) { - ACSDK_DEBUG9(LX(__func__)); - - std::lock_guard lock(m_mutex); - return m_appPowerResourceManager->isPowerResourceAcquired(component); + ACSDK_ERROR(LX(__func__).m("API is deprecated.Please see PowerResourceManagerInterface for alternatives")); + return false; } std::shared_ptr AggregatedPowerResourceManager:: diff --git a/AVSCommon/Utils/src/Power/PowerMonitor.cpp b/AVSCommon/Utils/src/Power/PowerMonitor.cpp index ab5b958765..9b91a9790a 100644 --- a/AVSCommon/Utils/src/Power/PowerMonitor.cpp +++ b/AVSCommon/Utils/src/Power/PowerMonitor.cpp @@ -23,7 +23,7 @@ namespace utils { namespace power { /// String to identify log entries originating from this file. -static const std::string TAG("PowerMonitor"); +#define TAG "PowerMonitor" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Power/PowerResource.cpp b/AVSCommon/Utils/src/Power/PowerResource.cpp index 2c509c6596..db7b004ac1 100644 --- a/AVSCommon/Utils/src/Power/PowerResource.cpp +++ b/AVSCommon/Utils/src/Power/PowerResource.cpp @@ -24,7 +24,7 @@ namespace power { using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("PowerResource"); +#define TAG "PowerResource" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/RequiresShutdown.cpp b/AVSCommon/Utils/src/RequiresShutdown.cpp index 0394d901cd..73ef87a6b9 100644 --- a/AVSCommon/Utils/src/RequiresShutdown.cpp +++ b/AVSCommon/Utils/src/RequiresShutdown.cpp @@ -24,7 +24,7 @@ namespace avsCommon { namespace utils { /// String to identify log entries originating from this file. -static const std::string TAG("RequiresShutdown"); +#define TAG "RequiresShutdown" /** * Create a @c LogEntry using this file's @c TAG and the specified event string. diff --git a/AVSCommon/Utils/src/RetryTimer.cpp b/AVSCommon/Utils/src/RetryTimer.cpp index 7470056c98..fbe08c8f8e 100644 --- a/AVSCommon/Utils/src/RetryTimer.cpp +++ b/AVSCommon/Utils/src/RetryTimer.cpp @@ -47,8 +47,8 @@ RetryTimer::RetryTimer(const std::vector& retryTable, int decreasePercentag std::chrono::milliseconds RetryTimer::calculateTimeToRetry(int retryCount) const { if (retryCount < 0) { retryCount = 0; - } else if ((size_t)retryCount >= m_RetrySize) { - retryCount = m_RetrySize - 1; + } else if (static_cast(retryCount) >= m_RetrySize) { + retryCount = static_cast(m_RetrySize) - 1; } std::mt19937 generator(static_cast(std::time(nullptr))); diff --git a/AVSCommon/Utils/src/Stopwatch.cpp b/AVSCommon/Utils/src/Stopwatch.cpp index 017c04793a..836af0c4aa 100644 --- a/AVSCommon/Utils/src/Stopwatch.cpp +++ b/AVSCommon/Utils/src/Stopwatch.cpp @@ -22,7 +22,7 @@ namespace utils { namespace timing { /// String to identify log entries originating from this file. -static const std::string TAG("Stopwatch"); +#define TAG "Stopwatch" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Stream/Streambuf.cpp b/AVSCommon/Utils/src/Stream/Streambuf.cpp index 6792a0bc78..1b1c52c6c4 100644 --- a/AVSCommon/Utils/src/Stream/Streambuf.cpp +++ b/AVSCommon/Utils/src/Stream/Streambuf.cpp @@ -12,7 +12,9 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ +#include +#include #include "AVSCommon/Utils/Stream/Streambuf.h" namespace alexaClientSDK { @@ -20,6 +22,22 @@ namespace avsCommon { namespace utils { namespace stream { +/// String to identify log entries originating from this file. +#define TAG "Streambuf" + +/** + * 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) + +/// Constant to indicate the MAX stream offset of an integer. +static constexpr std::streamoff MAX_INT = std::numeric_limits::max(); + +/// Constant to indicate a invalid offset. +static constexpr std::streamoff INVALID_OFFSET = std::streamoff(-1); + // There are two casts, as a streambuf uses Type=char. This requires removing the const and removing the unsigned. // setg only is for reading, so this operation is safe, although ugly. Streambuf::Streambuf(const unsigned char* data, size_t length) : @@ -29,22 +47,27 @@ Streambuf::Streambuf(const unsigned char* data, size_t length) : } std::streampos Streambuf::seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which) { + auto errorPos = std::streampos(INVALID_OFFSET); switch (way) { case std::ios_base::beg: setg(m_begin, m_begin + off, m_end); break; case std::ios_base::cur: - gbump(off); + if (off > MAX_INT) { + ACSDK_ERROR(LX("seekoffFailed").d("reason", "offset out of limits").d("off", off).d("limit", MAX_INT)); + return errorPos; + } + gbump(static_cast(off)); break; case std::ios_base::end: setg(m_begin, m_end + off, m_end); break; default: - return std::streampos(std::streamoff(-1)); + return errorPos; } if (!gptr() || gptr() >= egptr() || gptr() < eback()) { - return std::streampos(std::streamoff(-1)); + return errorPos; } return gptr() - eback(); diff --git a/AVSCommon/Utils/src/StringUtils.cpp b/AVSCommon/Utils/src/StringUtils.cpp index f9d853e4f0..fba725e597 100644 --- a/AVSCommon/Utils/src/StringUtils.cpp +++ b/AVSCommon/Utils/src/StringUtils.cpp @@ -31,7 +31,7 @@ namespace string { using namespace avsCommon::utils::logger; /// String to identify log entries originating from this file. -static const std::string TAG("StringUtils"); +#define TAG "StringUtils" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/TaskThread.cpp b/AVSCommon/Utils/src/TaskThread.cpp index af362ac9c8..4c1a8488e1 100644 --- a/AVSCommon/Utils/src/TaskThread.cpp +++ b/AVSCommon/Utils/src/TaskThread.cpp @@ -15,17 +15,17 @@ #include -#include "AVSCommon/Utils/Logger/Logger.h" -#include "AVSCommon/Utils/Logger/ThreadMoniker.h" -#include "AVSCommon/Utils/Threading/TaskThread.h" +#include +#include +#include /// String to identify log entries originating from this file. -static const std::string TAG("TaskThread"); +#define TAG "TaskThread" /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -62,7 +62,7 @@ TaskThread::~TaskThread() { } } -bool TaskThread::start(std::function jobRunner) { +bool TaskThread::start(std::function jobRunner, const std::string& moniker) { if (!jobRunner) { ACSDK_ERROR(LX("startFailed").d("reason", "invalidFunction")); return false; @@ -74,19 +74,17 @@ bool TaskThread::start(std::function jobRunner) { return false; } - m_startTime = steady_clock::now(); - m_stop = true; std::lock_guard guard(m_mutex); if (m_shuttingDown) { ACSDK_ERROR(LX("startFailed").d("reason", "shuttingDown")); return false; } - m_workerThread = m_threadPool->obtainWorker(m_moniker); + m_workerThread = m_threadPool->obtainWorker(); - m_moniker = m_workerThread->getMoniker(); - m_workerThread->run([this, jobRunner] { - TaskThread::run(jobRunner); + m_workerThread->run([this, jobRunner, moniker] { + utils::logger::ThreadMoniker::setThisThreadMoniker(moniker); + TaskThread::run(std::move(jobRunner)); return false; }); return true; @@ -94,15 +92,14 @@ bool TaskThread::start(std::function jobRunner) { void TaskThread::run(std::function jobRunner) { std::lock_guard guard(m_mutex); - ACSDK_DEBUG9(LX("startThread") - .d("moniker", m_moniker) - .d("duration", duration_cast(steady_clock::now() - m_startTime).count())); + // Reset stop flag and already starting flag. m_stop = false; m_alreadyStarting = false; while (!m_stop && jobRunner()) ; + m_threadPool->releaseWorker(std::move(m_workerThread)); } diff --git a/AVSCommon/Utils/src/ThreadPool.cpp b/AVSCommon/Utils/src/ThreadPool.cpp index bb9abbf6e8..1e4b9c25ea 100644 --- a/AVSCommon/Utils/src/ThreadPool.cpp +++ b/AVSCommon/Utils/src/ThreadPool.cpp @@ -14,20 +14,18 @@ */ #include -#include #include "AVSCommon/Utils/Logger/Logger.h" -#include "AVSCommon/Utils/Logger/ThreadMoniker.h" #include "AVSCommon/Utils/Memory/Memory.h" #include "AVSCommon/Utils/Threading/ThreadPool.h" /// String to identify log entries originating from this file. -static const std::string TAG("ThreadPool"); +#define TAG "ThreadPool" /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -39,8 +37,6 @@ namespace threading { using namespace std; using namespace logger; -static ThreadPool SINGLETON_THREAD_POOL{}; - ThreadPool::ThreadPool(size_t maxThreads) : m_maxPoolThreads{maxThreads}, m_created{0}, @@ -55,35 +51,18 @@ ThreadPool::~ThreadPool() { m_workerQueue.clear(); } -unique_ptr ThreadPool::obtainWorker(string optionalMoniker) { +unique_ptr ThreadPool::obtainWorker() { std::lock_guard lock(m_queueMutex); - ACSDK_DEBUG9(LX("obtainWorker") - .d("created", m_created) - .d("obtained", m_obtained) - .d("releasedToPool", m_releasedToPool) - .d("releasedFromPool", m_releasedFromPool) - .d("outstanding", m_obtained - (m_releasedToPool + m_releasedFromPool))); + m_obtained++; unique_ptr ret; if (m_workerQueue.empty()) { m_created++; ret = memory::make_unique(); } else { -#ifdef THREAD_AFFINITY - bool containsMoniker = false; - if (!optionalMoniker.empty()) { - containsMoniker = m_workerMap.count(optionalMoniker) > 0; - } - auto workerIterator = containsMoniker ? m_workerMap[optionalMoniker] : m_workerQueue.begin(); -#else auto workerIterator = m_workerQueue.begin(); -#endif ret = std::move(*workerIterator); m_workerQueue.erase(workerIterator); -#ifdef THREAD_AFFINITY - std::string moniker = (*workerIterator)->getMoniker(); - m_workerMap.erase(moniker); -#endif } return ret; @@ -91,7 +70,6 @@ unique_ptr ThreadPool::obtainWorker(string optionalMoniker) { void ThreadPool::releaseWorker(std::unique_ptr workerThread) { std::lock_guard lock(m_queueMutex); - if (m_workerQueue.size() >= m_maxPoolThreads) { // In order to allow this to be called from the thread being released, // we release the first thread in the queue when we want to stop growing. @@ -101,10 +79,6 @@ void ThreadPool::releaseWorker(std::unique_ptr workerThread) { m_releasedToPool++; } m_workerQueue.push_back(std::move(workerThread)); -#ifdef THREAD_AFFINITY - std::string moniker = workerThread->getMoniker(); - m_workerMap[moniker] = --m_workerQueue.end(); -#endif } void ThreadPool::setMaxThreads(size_t maxThreads) { @@ -115,7 +89,7 @@ void ThreadPool::setMaxThreads(size_t maxThreads) { } } -uint32_t ThreadPool::getMaxThreads() { +size_t ThreadPool::getMaxThreads() { std::lock_guard lock(m_queueMutex); return m_maxPoolThreads; } diff --git a/AVSCommon/Utils/src/Threading/ConditionVariableWrapper.cpp b/AVSCommon/Utils/src/Threading/ConditionVariableWrapper.cpp index 547e395c0f..43f6459709 100644 --- a/AVSCommon/Utils/src/Threading/ConditionVariableWrapper.cpp +++ b/AVSCommon/Utils/src/Threading/ConditionVariableWrapper.cpp @@ -26,7 +26,7 @@ namespace threading { using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG(ConditionVariableWrapper::getTag()); +#define TAG ConditionVariableWrapper::getTag() /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/Threading/Executor.cpp b/AVSCommon/Utils/src/Threading/Executor.cpp new file mode 100644 index 0000000000..9f3aa04d61 --- /dev/null +++ b/AVSCommon/Utils/src/Threading/Executor.cpp @@ -0,0 +1,87 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +/// String to identify log entries originating from this file. +#define TAG "ExecutorWrapper" + +/** + * 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) + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace threading { + +Executor::Executor() noexcept : m_executor{std::make_shared()} { + if (!m_executor) { + ACSDK_ERROR(LX("initError")); + } +} + +Executor::~Executor() noexcept { + m_executor.reset(); +} + +bool Executor::execute(std::function&& function) noexcept { + return execute(std::move(function), QueuePosition::Back); +} + +bool Executor::execute(std::function&& function, QueuePosition queuePosition) noexcept { + if (m_executor) { + auto error = m_executor->execute(std::move(function), queuePosition); + return !error; + } + return false; +} + +bool Executor::execute(const std::function& function) noexcept { + return execute(std::function(function), QueuePosition::Back); +} + +void Executor::waitForSubmittedTasks() noexcept { + if (m_executor) { + m_executor->waitForSubmittedTasks(); + } +} + +void Executor::shutdown() noexcept { + if (m_executor) { + m_executor->shutdown(); + } +} + +bool Executor::isShutdown() noexcept { + if (m_executor) { + return m_executor->isShutdown(); + } + return true; +} + +Executor::operator std::shared_ptr() const noexcept { + return m_executor; +} + +} // namespace threading +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/Threading/ExecutorFactory.cpp b/AVSCommon/Utils/src/Threading/ExecutorFactory.cpp new file mode 100644 index 0000000000..45eeb75e4a --- /dev/null +++ b/AVSCommon/Utils/src/Threading/ExecutorFactory.cpp @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +/// String to identify log entries originating from this file. +#define TAG "ExecutorFactory" + +/** + * 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) + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace threading { + +std::shared_ptr createSingleThreadExecutor() noexcept { + auto res = std::make_shared(); + if (!res) { + ACSDK_ERROR(LX("createExecutorFailed")); + } + return res; +} + +} // namespace threading +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/Threading/SharedExecutor.cpp b/AVSCommon/Utils/src/Threading/SharedExecutor.cpp new file mode 100644 index 0000000000..a80f6fc6fe --- /dev/null +++ b/AVSCommon/Utils/src/Threading/SharedExecutor.cpp @@ -0,0 +1,203 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 + +/// String to identify log entries originating from this file. +#define TAG "Executor" + +/** + * 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) + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace threading { + +template std::future Executor::pushFunction( + QueuePosition queuePosition, + std::function&& function) noexcept; +template std::future Executor::pushFunction( + QueuePosition queuePosition, + std::function&& function) noexcept; +template std::future Executor::pushFunction( + QueuePosition queuePosition, + std::function&& function) noexcept; + +/// Prefix for power resource owned by @c Executor instance. +/// @private +static constexpr auto POWER_RESOURCE_PREFIX = "Executor:"; + +/** + * Helper method to create power resource id from a moniker. + * + * This method concatenates @a POWER_RESOURCE_PREFIX with @a moniker without trailing spaces. + * + * @param moniker Moniker value to use. + * @return Power resource identifier. + * @private + */ +static std::string createPowerResourceName(const std::string& moniker) noexcept { + std::string::const_iterator it = moniker.begin(); + while (it != moniker.end() && std::isspace(*it)) { + ++it; + } + std::string result; + constexpr auto powerResourcePrefixLen = sizeof(POWER_RESOURCE_PREFIX) - 1; + result.reserve(std::distance(it, moniker.end()) + powerResourcePrefixLen); + result.append(POWER_RESOURCE_PREFIX, powerResourcePrefixLen); + std::copy(it, moniker.end(), back_inserter(result)); + return result; +} + +SharedExecutor::SharedExecutor() noexcept : + m_executorMoniker{utils::logger::ThreadMoniker::generateMoniker(utils::logger::ThreadMoniker::PREFIX_EXECUTOR)}, + m_threadRunning{false}, + m_shutdown{false} { + ACSDK_DEBUG5(LX("created").d("moniker", m_executorMoniker)); + m_powerResource = + power::PowerMonitor::getInstance()->createLocalPowerResource(createPowerResourceName(m_executorMoniker)); +} + +SharedExecutor::~SharedExecutor() noexcept { + shutdown(); + ACSDK_DEBUG5(LX("destroyed").d("moniker", m_executorMoniker)); +} + +std::error_condition SharedExecutor::execute(std::function&& function) noexcept { + return execute(std::move(function), QueuePosition::Back); +} + +std::error_condition SharedExecutor::execute(const std::function& function) noexcept { + // Forward copy of function reference. + return execute(std::function(function), QueuePosition::Back); +} + +void SharedExecutor::waitForSubmittedTasks() noexcept { + 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(); + flushedFuture.wait(); + } +} + +std::error_condition SharedExecutor::execute(std::function&& function, QueuePosition queuePosition) noexcept { + if (!function) { + ACSDK_ERROR(LX(__func__).d("reason", "emptyFunction")); + return std::errc::invalid_argument; + } + if (QueuePosition::Back != queuePosition && QueuePosition::Front != queuePosition) { + ACSDK_ERROR(LX(__func__).d("reason", "badQueuePosition")); + return std::errc::invalid_argument; + } + + std::lock_guard queueLock{m_queueMutex}; + if (m_shutdown) { + ACSDK_WARN(LX(__func__).d("reason", "shutdownState")); + return std::errc::operation_not_permitted; + } + + if (m_powerResource) { + m_powerResource->acquire(); + } + m_queue.emplace(QueuePosition::Front == queuePosition ? m_queue.begin() : m_queue.end(), std::move(function)); + + if (!m_threadRunning) { + m_threadRunning = true; + // Restart task thread. + m_taskThread.start(std::bind(&SharedExecutor::runNext, this), m_executorMoniker); + } + + return std::error_condition(); +} + +std::function SharedExecutor::pop() noexcept { + 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 SharedExecutor::hasNext() noexcept { + std::unique_lock lock{m_queueMutex}; + m_threadRunning = !m_queue.empty(); + return m_threadRunning; +} + +bool SharedExecutor::runNext() noexcept { + auto task = pop(); + if (task) { +#if __cpp_exceptions || defined(__EXCEPTIONS) + try { +#endif + task(); + // Ensure the task is released after executed. + task = nullptr; + +#if __cpp_exceptions || defined(__EXCEPTIONS) + } catch (const std::exception& ex) { + ACSDK_ERROR(LX(__func__).d("taskException", ex.what())); + } catch (...) { + ACSDK_ERROR(LX(__func__).d("taskException", "other")); + } +#endif + + if (m_powerResource) { + m_powerResource->release(); + } + } + + return hasNext(); +} + +void SharedExecutor::shutdown() noexcept { + Queue releaseQueue; + { + std::lock_guard lock{m_queueMutex}; + m_shutdown = true; + std::swap(releaseQueue, m_queue); + } + releaseQueue.clear(); + waitForSubmittedTasks(); +} + +bool SharedExecutor::isShutdown() noexcept { + return m_shutdown; +} + +} // namespace threading +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/src/TimePoint.cpp b/AVSCommon/Utils/src/TimePoint.cpp index 5083650c3c..e042977d79 100644 --- a/AVSCommon/Utils/src/TimePoint.cpp +++ b/AVSCommon/Utils/src/TimePoint.cpp @@ -26,7 +26,7 @@ namespace timing { using namespace avsCommon::utils::logger; /// String to identify log entries originating from this file. -static const std::string TAG("TimePoint"); +#define TAG "TimePoint" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/src/TimeUtils.cpp b/AVSCommon/Utils/src/TimeUtils.cpp index 03f470c6ee..4c50d6dc1c 100644 --- a/AVSCommon/Utils/src/TimeUtils.cpp +++ b/AVSCommon/Utils/src/TimeUtils.cpp @@ -33,33 +33,35 @@ using namespace avsCommon::utils::logger; using namespace avsCommon::utils::string; /// String to identify log entries originating from this file. -static const std::string TAG("TimeUtils"); +#define TAG "TimeUtils" /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /// The length of the year element in an ISO-8601 formatted string. -static const int ENCODED_TIME_STRING_YEAR_STRING_LENGTH = 4; +static const std::size_t ENCODED_TIME_STRING_YEAR_STRING_LENGTH = 4; /// The length of the month element in an ISO-8601 formatted string. -static const int ENCODED_TIME_STRING_MONTH_STRING_LENGTH = 2; +static const std::size_t ENCODED_TIME_STRING_MONTH_STRING_LENGTH = 2; /// The length of the day element in an ISO-8601 formatted string. -static const int ENCODED_TIME_STRING_DAY_STRING_LENGTH = 2; +static const std::size_t ENCODED_TIME_STRING_DAY_STRING_LENGTH = 2; /// The length of the hour element in an ISO-8601 formatted string. -static const int ENCODED_TIME_STRING_HOUR_STRING_LENGTH = 2; +static const std::size_t ENCODED_TIME_STRING_HOUR_STRING_LENGTH = 2; /// The length of the minute element in an ISO-8601 formatted string. -static const int ENCODED_TIME_STRING_MINUTE_STRING_LENGTH = 2; +static const std::size_t ENCODED_TIME_STRING_MINUTE_STRING_LENGTH = 2; /// The length of the second element in an ISO-8601 formatted string. -static const int ENCODED_TIME_STRING_SECOND_STRING_LENGTH = 2; +static const std::size_t ENCODED_TIME_STRING_SECOND_STRING_LENGTH = 2; /// The length of the post-fix element in an ISO-8601 formatted string. -static const int ENCODED_TIME_STRING_POSTFIX_STRING_LENGTH = 4; +static const std::size_t ENCODED_TIME_STRING_POSTFIX_STRING_LENGTH = 4; /// The dash separator used in an ISO-8601 formatted string. static const std::string ENCODED_TIME_STRING_DASH_SEPARATOR_STRING = "-"; /// The 'T' separator used in an ISO-8601 formatted string. static const std::string ENCODED_TIME_STRING_T_SEPARATOR_STRING = "T"; +/// The 'Z' zone designator used in an ISO-8601 formatted string for zero UTC offset. +static const std::string ENCODED_TIME_STRING_Z_DESIGNATOR = "Z"; /// The colon separator used in an ISO-8601 formatted string. static const std::string ENCODED_TIME_STRING_COLON_SEPARATOR_STRING = ":"; /// The plus separator used in an ISO-8601 formatted string. @@ -68,31 +70,36 @@ static const std::string ENCODED_TIME_STRING_PLUS_SEPARATOR_STRING = "+"; /// The offset into an ISO-8601 formatted string where the year begins. static const unsigned long ENCODED_TIME_STRING_YEAR_OFFSET = 0; /// The offset into an ISO-8601 formatted string where the month begins. -static const unsigned long ENCODED_TIME_STRING_MONTH_OFFSET = ENCODED_TIME_STRING_YEAR_OFFSET + - ENCODED_TIME_STRING_YEAR_STRING_LENGTH + - ENCODED_TIME_STRING_DASH_SEPARATOR_STRING.length(); -/// The offset into an ISO-8601 formatted string where the day begins. -static const unsigned long ENCODED_TIME_STRING_DAY_OFFSET = ENCODED_TIME_STRING_MONTH_OFFSET + - ENCODED_TIME_STRING_MONTH_STRING_LENGTH + +static const std::size_t ENCODED_TIME_STRING_MONTH_OFFSET = ENCODED_TIME_STRING_YEAR_OFFSET + + ENCODED_TIME_STRING_YEAR_STRING_LENGTH + ENCODED_TIME_STRING_DASH_SEPARATOR_STRING.length(); +/// The offset into an ISO-8601 formatted string where the day begins. +static const std::size_t ENCODED_TIME_STRING_DAY_OFFSET = ENCODED_TIME_STRING_MONTH_OFFSET + + ENCODED_TIME_STRING_MONTH_STRING_LENGTH + + ENCODED_TIME_STRING_DASH_SEPARATOR_STRING.length(); /// The offset into an ISO-8601 formatted string where the hour begins. -static const unsigned long ENCODED_TIME_STRING_HOUR_OFFSET = ENCODED_TIME_STRING_DAY_OFFSET + - ENCODED_TIME_STRING_DAY_STRING_LENGTH + - ENCODED_TIME_STRING_T_SEPARATOR_STRING.length(); +static const std::size_t ENCODED_TIME_STRING_HOUR_OFFSET = ENCODED_TIME_STRING_DAY_OFFSET + + ENCODED_TIME_STRING_DAY_STRING_LENGTH + + ENCODED_TIME_STRING_T_SEPARATOR_STRING.length(); /// The offset into an ISO-8601 formatted string where the minute begins. -static const unsigned long ENCODED_TIME_STRING_MINUTE_OFFSET = ENCODED_TIME_STRING_HOUR_OFFSET + - ENCODED_TIME_STRING_HOUR_STRING_LENGTH + - ENCODED_TIME_STRING_COLON_SEPARATOR_STRING.length(); +static const std::size_t ENCODED_TIME_STRING_MINUTE_OFFSET = ENCODED_TIME_STRING_HOUR_OFFSET + + ENCODED_TIME_STRING_HOUR_STRING_LENGTH + + ENCODED_TIME_STRING_COLON_SEPARATOR_STRING.length(); /// The offset into an ISO-8601 formatted string where the second begins. -static const unsigned long ENCODED_TIME_STRING_SECOND_OFFSET = ENCODED_TIME_STRING_MINUTE_OFFSET + - ENCODED_TIME_STRING_MINUTE_STRING_LENGTH + - ENCODED_TIME_STRING_COLON_SEPARATOR_STRING.length(); +static const std::size_t ENCODED_TIME_STRING_SECOND_OFFSET = ENCODED_TIME_STRING_MINUTE_OFFSET + + ENCODED_TIME_STRING_MINUTE_STRING_LENGTH + + ENCODED_TIME_STRING_COLON_SEPARATOR_STRING.length(); /// The total expected length of an ISO-8601 formatted string. -static const unsigned long ENCODED_TIME_STRING_EXPECTED_LENGTH = +static const std::size_t ENCODED_TIME_STRING_EXPECTED_LENGTH = ENCODED_TIME_STRING_SECOND_OFFSET + ENCODED_TIME_STRING_SECOND_STRING_LENGTH + ENCODED_TIME_STRING_PLUS_SEPARATOR_STRING.length() + ENCODED_TIME_STRING_POSTFIX_STRING_LENGTH; +/// The total expected length of an ISO-8601 formatted string with UTC time. +static const std::size_t ENCODED_TIME_STRING_EXPECTED_LENGTH_UTC = ENCODED_TIME_STRING_SECOND_OFFSET + + ENCODED_TIME_STRING_SECOND_STRING_LENGTH + + ENCODED_TIME_STRING_Z_DESIGNATOR.length(); + /** * Utility function that wraps localtime conversion to std::time_t. * @@ -178,7 +185,8 @@ bool TimeUtils::convert8601TimeStringToTimeT(const std::string& iso8601TimeStrin std::tm timeInfo; - if (iso8601TimeString.length() != ENCODED_TIME_STRING_EXPECTED_LENGTH) { + if (iso8601TimeString.length() != ENCODED_TIME_STRING_EXPECTED_LENGTH && + iso8601TimeString.length() != ENCODED_TIME_STRING_EXPECTED_LENGTH_UTC) { ACSDK_ERROR( LX("convert8601TimeStringToTimeTFailed").d("unexpected time string length:", iso8601TimeString.length())); return false; diff --git a/AVSCommon/Utils/src/Timer.cpp b/AVSCommon/Utils/src/Timer.cpp index 392bee1f99..13e441064e 100644 --- a/AVSCommon/Utils/src/Timer.cpp +++ b/AVSCommon/Utils/src/Timer.cpp @@ -14,60 +14,121 @@ */ #include - -#include "AVSCommon/Utils/Timing/Timer.h" -#include "AVSCommon/Utils/Timing/TimerDelegateFactory.h" +#include +#include namespace alexaClientSDK { namespace avsCommon { namespace utils { namespace timing { +using namespace sdkInterfaces::timing; + /// String to identify log entries originating from this file. -static const std::string TAG(Timer::getTag()); +#define TAG "Timer" /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) -Timer::Timer(std::shared_ptr timerDelegateFactory) { - if (!timerDelegateFactory) { +// Instantiate templates +template bool Timer::adaptTypesAndCallTask( + const std::chrono::milliseconds&, + const std::chrono::milliseconds&, + PeriodType, + size_t, + std::function&&) noexcept; +template bool Timer::adaptTypesAndCallTask( + const std::chrono::nanoseconds&, + const std::chrono::nanoseconds&, + PeriodType, + size_t, + std::function&&) noexcept; + +Timer::Timer(const std::shared_ptr& timerDelegateFactory) noexcept { + if (timerDelegateFactory) { + m_timer = timerDelegateFactory->getTimerDelegate(); + } else { ACSDK_WARN( LX(__func__).d("reason", "nullTimerDelegateFactory").m("Falling back to default TimerDelegateFactory")); - timerDelegateFactory = std::make_shared(); - if (!timerDelegateFactory) { - ACSDK_ERROR(LX(__func__).d("reason", "nullDefaultTimerDelegateFactory")); - return; - } + m_timer = TimerDelegateFactory{}.getTimerDelegate(); } - m_timer = timerDelegateFactory->getTimerDelegate(); if (!m_timer) { ACSDK_ERROR(LX(__func__).d("reason", "nullTimerDelegate")); } } -Timer::~Timer() { +Timer::~Timer() noexcept { stop(); } -void Timer::stop() { +void Timer::stop() noexcept { if (m_timer) { m_timer->stop(); } } -bool Timer::isActive() const { +bool Timer::isActive() const noexcept { return m_timer && m_timer->isActive(); } -bool Timer::activate() { +bool Timer::activate() noexcept { return m_timer && m_timer->activate(); } +bool Timer::callTask( + const std::chrono::nanoseconds& delayNano, + const std::chrono::nanoseconds& periodNano, + PeriodType periodType, + size_t maxCount, + std::function&& task) noexcept { + if (delayNano < std::chrono::nanoseconds::zero()) { + ACSDK_ERROR(LX(__func__).d("reason", "negativeDelay")); + return false; + } + if (periodNano < std::chrono::nanoseconds::zero()) { + ACSDK_ERROR(LX(__func__).d("reason", "negativePeriod")); + return false; + } + if (!task) { + ACSDK_ERROR(LX(__func__).d("reason", "nullTask")); + return false; + } + + // Don't start if already running. + if (!activate()) { + ACSDK_ERROR(LX(__func__).d("reason", "timerAlreadyActive")); + return false; + } + + if (!m_timer) { + ACSDK_ERROR(LX(__func__).d("reason", "nullTimerDelegate")); + return false; + } + + TimerDelegateInterface::PeriodType delegatePeriodType; + + switch (periodType) { + case PeriodType::ABSOLUTE: + delegatePeriodType = TimerDelegateInterface::PeriodType::ABSOLUTE; + break; + case PeriodType::RELATIVE: + delegatePeriodType = TimerDelegateInterface::PeriodType::RELATIVE; + break; + default: + ACSDK_ERROR(LX(__func__).d("reason", "badPeriodType")); + return false; + } + + m_timer->start(delayNano, periodNano, delegatePeriodType, maxCount, std::move(task)); + + return true; +} + } // namespace timing } // namespace utils } // namespace avsCommon diff --git a/AVSCommon/Utils/src/Timing/TimerDelegate.cpp b/AVSCommon/Utils/src/Timing/TimerDelegate.cpp index 2c818ad6a6..ce5b171066 100644 --- a/AVSCommon/Utils/src/Timing/TimerDelegate.cpp +++ b/AVSCommon/Utils/src/Timing/TimerDelegate.cpp @@ -14,11 +14,12 @@ */ #include -#include "AVSCommon/Utils/Timing/TimerDelegate.h" #include +#include +#include /// String to identify log entries originating from this file. -static const std::string TAG("TimerDelegate"); +#define TAG "TimerDelegate" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -32,6 +33,27 @@ namespace avsCommon { namespace utils { namespace timing { +using utils::logger::ThreadMoniker; + +/** + * @brief Helper method to invoke task and catch exception. + * + * @param[in, out] task Task reference. The reference is cleared before the method returns. + */ +static void safeInvokeTask(std::function& task) { +#if __cpp_exceptions || defined(__EXCEPTIONS) + try { +#endif + task(); +#if __cpp_exceptions || defined(__EXCEPTIONS) + } catch (const std::exception& ex) { + ACSDK_ERROR(LX(__func__).d("taskException", ex.what())); + } catch (...) { + ACSDK_ERROR(LX(__func__).d("taskException", "other")); + } +#endif +} + TimerDelegate::TimerDelegate() : m_running{false}, m_stopping{false} { } @@ -45,10 +67,18 @@ void TimerDelegate::start( PeriodType periodType, size_t maxCount, std::function task) { + auto moniker = ThreadMoniker::generateMoniker(ThreadMoniker::PREFIX_TIMER); + ACSDK_DEBUG5(LX("init").d("moniker", moniker)); + + if (!task) { + ACSDK_ERROR(LX(__func__).d("reason", "nullTask")); + } + std::lock_guard lock(m_callMutex); cleanupLocked(); activateLocked(); - m_thread = std::thread(&TimerDelegate::timerLoop, this, delay, period, periodType, maxCount, task); + m_thread = std::thread( + &TimerDelegate::timerLoop, this, delay, period, periodType, maxCount, std::move(task), std::move(moniker)); } void TimerDelegate::timerLoop( @@ -56,9 +86,13 @@ void TimerDelegate::timerLoop( std::chrono::nanoseconds period, PeriodType periodType, size_t maxCount, - std::function task) { + std::function task, + std::string moniker) { + ThreadMoniker::setThisThreadMoniker(std::move(moniker)); + // Timepoint to measure delay/period against. auto now = std::chrono::steady_clock::now(); + using utils::logger::ThreadMoniker; // Flag indicating whether we've drifted off schedule. bool offSchedule = false; @@ -83,7 +117,7 @@ void TimerDelegate::timerLoop( // Run the task if we're still on schedule. if (!offSchedule) { - task(); + safeInvokeTask(task); } // If the task runtime put us off schedule, skip the next task run. @@ -95,11 +129,15 @@ void TimerDelegate::timerLoop( break; case PeriodType::RELATIVE: - task(); + safeInvokeTask(task); now = std::chrono::steady_clock::now(); break; } } + + // release task reference + task = nullptr; + std::lock_guard lockGuard(m_waitMutex); m_stopping = false; m_running = false; @@ -108,7 +146,7 @@ void TimerDelegate::timerLoop( void TimerDelegate::stop() { std::lock_guard lock(m_callMutex); { - std::lock_guard lock(m_waitMutex); + std::lock_guard stateLock(m_waitMutex); if (m_running) { m_stopping = true; } diff --git a/AVSCommon/Utils/src/UUIDGeneration.cpp b/AVSCommon/Utils/src/UUIDGeneration.cpp index 0579398d0f..39d4d57ffa 100644 --- a/AVSCommon/Utils/src/UUIDGeneration.cpp +++ b/AVSCommon/Utils/src/UUIDGeneration.cpp @@ -34,7 +34,7 @@ namespace utils { namespace uuidGeneration { /// String to identify log entries originating from this file. -static const std::string TAG("UUIDGeneration"); +#define TAG "UUIDGeneration" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -177,13 +177,13 @@ static void addDefaultSeedLocked() { std::random_device rd; uint64_t timeSeed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); if (lastInvokeTime > 0) { - seedsPool.push_front((uint32_t)(timeSeed - lastInvokeTime)); // interval between two invocations + seedsPool.push_front(static_cast(timeSeed - lastInvokeTime)); // interval between two invocations } lastInvokeTime = timeSeed; - seedsPool.push_front((uint32_t)timeSeed); // lower 32bits of current time - seedsPool.push_front(rd()); // random device - seedsPool.push_front( - reinterpret_cast(&timeSeed)); // lower 32bits of memory address of temporary variable + seedsPool.push_front(static_cast(timeSeed)); // lower 32bits of current time + seedsPool.push_front(rd()); // random device + seedsPool.push_front(static_cast( + reinterpret_cast(&timeSeed))); // lower 32bits of memory address of temporary variable if (seedsPool.size() > MAX_SEEDS_POOL_SIZE) { seedsPool.resize(MAX_SEEDS_POOL_SIZE); diff --git a/AVSCommon/Utils/src/WavUtils.cpp b/AVSCommon/Utils/src/WavUtils.cpp index bbe5086075..ca714dc1dc 100644 --- a/AVSCommon/Utils/src/WavUtils.cpp +++ b/AVSCommon/Utils/src/WavUtils.cpp @@ -24,7 +24,7 @@ namespace alexaClientSDK { namespace avsCommon { namespace utils { -static const std::string TAG("WavUtils"); +#define TAG "WavUtils" #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -233,9 +233,9 @@ bool readWAVFile( // Check if the file size is at least the size of a .wav file header. inputFile.seekg(0, std::ios::end); - int fileLengthInBytes = inputFile.tellg(); + size_t fileLengthInBytes = static_cast(inputFile.tellg()); - const int headerSize = isPCM ? PCM_HEADER_SIZE : NON_PCM_HEADER_SIZE; + const size_t headerSize = isPCM ? PCM_HEADER_SIZE : NON_PCM_HEADER_SIZE; if (fileLengthInBytes <= headerSize) { ACSDK_ERROR(LX("readAudioFileFailed").d("reason", "file size less than RIFF header")); return false; @@ -249,7 +249,7 @@ bool readWAVFile( char* pBuffer = buffer.data(); - if (static_cast(inputFile.gcount()) != static_cast(headerSize)) { + if (static_cast(inputFile.gcount()) != headerSize) { ACSDK_ERROR(LX("readAudioFileFailed").d("reason", "failed reading header")); return false; } @@ -275,7 +275,7 @@ bool readWAVFile( wavHeader.dataSz = readLongFromHeader(pBuffer, DATA_SZ_OFFSET + (isPCM ? 0 : NON_PCM_OFFSET)); // Read the remainder of the wav file (excluding header) into the audioBuffer. - int numSamples = (fileLengthInBytes - headerSize) / sizeof(uint16_t); + size_t numSamples = (fileLengthInBytes - headerSize) / sizeof(uint16_t); audioBuffer->resize(numSamples, 0); inputFile.read(reinterpret_cast(&(audioBuffer->at(0))), numSamples * sizeof(uint16_t)); diff --git a/AVSCommon/Utils/src/WorkerThread.cpp b/AVSCommon/Utils/src/WorkerThread.cpp index e82640b7b7..349f8c5dfe 100644 --- a/AVSCommon/Utils/src/WorkerThread.cpp +++ b/AVSCommon/Utils/src/WorkerThread.cpp @@ -23,10 +23,7 @@ namespace avsCommon { namespace utils { namespace threading { -WorkerThread::WorkerThread() : - m_moniker{alexaClientSDK::avsCommon::utils::logger::ThreadMoniker::generateMoniker()}, - m_stop{false}, - m_cancel{false} { +WorkerThread::WorkerThread() : m_stop{false}, m_cancel{false} { m_thread = std::thread{std::bind(&WorkerThread::runInternal, this)}; } @@ -45,14 +42,14 @@ WorkerThread::~WorkerThread() { } } -std::string WorkerThread::getMoniker() const { - return m_moniker; -} - void WorkerThread::cancel() { m_cancel = true; } +std::thread::id WorkerThread::getThreadId() const { + return m_thread.get_id(); +} + void WorkerThread::run(std::function workFunc) { std::lock_guard lock(m_mutex); m_cancel = false; @@ -61,7 +58,6 @@ void WorkerThread::run(std::function workFunc) { } void WorkerThread::runInternal() { - alexaClientSDK::avsCommon::utils::logger::ThreadMoniker::setThisThreadMoniker(m_moniker); std::unique_lock lock(m_mutex); do { // If run is called before the thread starts, it will notify before we wait, so we guard against that by diff --git a/AVSCommon/Utils/test/AVSCommon/Utils/Power/AggregatedPowerResourceManagerTest.cpp b/AVSCommon/Utils/test/AVSCommon/Utils/Power/AggregatedPowerResourceManagerTest.cpp index 1845154d4e..c84e2b284f 100644 --- a/AVSCommon/Utils/test/AVSCommon/Utils/Power/AggregatedPowerResourceManagerTest.cpp +++ b/AVSCommon/Utils/test/AVSCommon/Utils/Power/AggregatedPowerResourceManagerTest.cpp @@ -301,7 +301,7 @@ TEST_F(AggregatedPowerResourceManagerTest, test_closeCleanup) { * Test ensuring that acquirePowerResource calls the underlying application PowerResourceManagerInterface methods. */ TEST_F(AggregatedPowerResourceManagerTest, test_acquirePowerResourceLegacy) { - EXPECT_CALL(*m_mockAppPowerManager, acquirePowerResource(TEST_ID, TEST_LEVEL)); + EXPECT_CALL(*m_mockAppPowerManager, acquirePowerResource(TEST_ID, TEST_LEVEL)).Times(0); m_powerManager->acquirePowerResource(TEST_ID, TEST_LEVEL); } @@ -309,7 +309,7 @@ TEST_F(AggregatedPowerResourceManagerTest, test_acquirePowerResourceLegacy) { * Test ensuring that releasePowerResource calls the underlying application PowerResourceManagerInterface methods. */ TEST_F(AggregatedPowerResourceManagerTest, test_releasePowerResourceLegacy) { - EXPECT_CALL(*m_mockAppPowerManager, releasePowerResource(TEST_ID)); + EXPECT_CALL(*m_mockAppPowerManager, releasePowerResource(TEST_ID)).Times(0); m_powerManager->releasePowerResource(TEST_ID); } @@ -317,7 +317,7 @@ TEST_F(AggregatedPowerResourceManagerTest, test_releasePowerResourceLegacy) { * Test ensuring that isPowerResourceAcquired calls the underlying application PowerResourceManagerInterface methods. */ TEST_F(AggregatedPowerResourceManagerTest, test_isPowerResourceAcquiredLegacy) { - EXPECT_CALL(*m_mockAppPowerManager, isPowerResourceAcquired(TEST_ID)); + EXPECT_CALL(*m_mockAppPowerManager, isPowerResourceAcquired(TEST_ID)).Times(0); m_powerManager->isPowerResourceAcquired(TEST_ID); } diff --git a/AVSCommon/Utils/test/CMakeLists.txt b/AVSCommon/Utils/test/CMakeLists.txt index 9ed5e4de50..3f86a666a9 100644 --- a/AVSCommon/Utils/test/CMakeLists.txt +++ b/AVSCommon/Utils/test/CMakeLists.txt @@ -1,6 +1,3 @@ add_subdirectory("Common") -set(INCLUDE_PATH - "${AVSCommon_INCLUDE_DIRS}") - -discover_unit_tests("${INCLUDE_PATH}" "AVSCommon;UtilsCommonTestLib;SDKInterfacesTests") +discover_unit_tests("" "AVSCommon;UtilsCommonTestLib;SDKInterfacesTests") diff --git a/AVSCommon/Utils/test/Common/Common.cpp b/AVSCommon/Utils/test/Common/Common.cpp index 91c6360a78..e1471bd049 100644 --- a/AVSCommon/Utils/test/Common/Common.cpp +++ b/AVSCommon/Utils/test/Common/Common.cpp @@ -37,9 +37,9 @@ std::string createRandomAlphabetString(int stringSize) { std::vector vec(stringSize); std::independent_bits_engine engine; std::random_device rd; - engine.seed( + engine.seed(static_cast( rd() + std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()) - .count()); + .count())); std::generate(begin(vec), end(vec), std::ref(engine)); std::vector vec8(stringSize); diff --git a/AVSCommon/Utils/test/Common/TestableMessageObserver.cpp b/AVSCommon/Utils/test/Common/TestableMessageObserver.cpp index 6764d208e2..fe774cc44e 100644 --- a/AVSCommon/Utils/test/Common/TestableMessageObserver.cpp +++ b/AVSCommon/Utils/test/Common/TestableMessageObserver.cpp @@ -24,7 +24,7 @@ namespace utils { using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("TestableMessageObserver"); +#define TAG "TestableMessageObserver" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/test/Common/Timing/StopTaskTimer.cpp b/AVSCommon/Utils/test/Common/Timing/StopTaskTimer.cpp index b96a3289cf..32bd2d8fcb 100644 --- a/AVSCommon/Utils/test/Common/Timing/StopTaskTimer.cpp +++ b/AVSCommon/Utils/test/Common/Timing/StopTaskTimer.cpp @@ -28,7 +28,7 @@ using namespace avsCommon::sdkInterfaces::timing; using namespace avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("StopTaskTimer"); +#define TAG "StopTaskTimer" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -80,7 +80,7 @@ void StopTaskTimer::stop() { if (isActive()) { // Wait until any current executing tasked has finished. // The objective is to force the task to run from the beginning. - std::lock_guard lock(m_taskMutex); + std::lock_guard taskLock(m_taskMutex); m_task(); m_delegate->stop(); } diff --git a/AVSCommon/Utils/test/ExecutorTest.cpp b/AVSCommon/Utils/test/ExecutorTest.cpp index abd87894bf..68a0dc97e1 100644 --- a/AVSCommon/Utils/test/ExecutorTest.cpp +++ b/AVSCommon/Utils/test/ExecutorTest.cpp @@ -13,8 +13,13 @@ * permissions and limitations under the License. */ -#include +#include +#include #include +#include +#include +#include +#include #include "ExecutorTestUtils.h" #include "AVSCommon/Utils/Threading/Executor.h" @@ -26,6 +31,9 @@ namespace utils { namespace threading { namespace test { +/// Maximum timeout for blocking wait when expecting a signal. +static const std::chrono::seconds EXECUTOR_SIGNAL_WAIT_TIMEOUT{30}; + class ExecutorTest : public ::testing::Test { public: Executor executor; @@ -34,24 +42,28 @@ class ExecutorTest : public ::testing::Test { TEST_F(ExecutorTest, testTimer_submitStdFunctionAndVerifyExecution) { std::function function = []() {}; auto future = executor.submit(function); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } TEST_F(ExecutorTest, testTimer_submitStdBindAndVerifyExecution) { auto future = executor.submit(std::bind(exampleFunctionParams, 0)); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } TEST_F(ExecutorTest, testTimer_submitLambdaAndVerifyExecution) { auto future = executor.submit([]() {}); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } TEST_F(ExecutorTest, testTimer_submitFunctionPointerAndVerifyExecution) { auto future = executor.submit(&exampleFunction); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } @@ -59,6 +71,7 @@ TEST_F(ExecutorTest, testTimer_submitFunctionPointerAndVerifyExecution) { TEST_F(ExecutorTest, testTimer_submitFunctorAndVerifyExecution) { ExampleFunctor exampleFunctor; auto future = executor.submit(exampleFunctor); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } @@ -66,6 +79,7 @@ TEST_F(ExecutorTest, testTimer_submitFunctorAndVerifyExecution) { TEST_F(ExecutorTest, testTimer_submitFunctionWithPrimitiveReturnTypeNoArgsAndVerifyExecution) { int value = VALUE; auto future = executor.submit([=]() { return value; }); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); ASSERT_EQ(future.get(), value); @@ -74,6 +88,7 @@ TEST_F(ExecutorTest, testTimer_submitFunctionWithPrimitiveReturnTypeNoArgsAndVer TEST_F(ExecutorTest, testTimer_submitFunctionWithObjectReturnTypeNoArgsAndVerifyExecution) { SimpleObject value(VALUE); auto future = executor.submit([=]() { return value; }); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); ASSERT_EQ(future.get().getValue(), value.getValue()); @@ -82,6 +97,7 @@ TEST_F(ExecutorTest, testTimer_submitFunctionWithObjectReturnTypeNoArgsAndVerify TEST_F(ExecutorTest, testTimer_submitFunctionWithNoReturnTypePrimitiveArgsAndVerifyExecution) { int value = VALUE; auto future = executor.submit([](int number) {}, value); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } @@ -89,6 +105,7 @@ TEST_F(ExecutorTest, testTimer_submitFunctionWithNoReturnTypePrimitiveArgsAndVer TEST_F(ExecutorTest, testTimer_submitFunctionWithNoReturnTypeObjectArgsAndVerifyExecution) { SimpleObject arg(0); auto future = executor.submit([](SimpleObject object) {}, arg); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); } @@ -97,6 +114,7 @@ TEST_F(ExecutorTest, testTimer_submitFunctionWithPrimitiveReturnTypeObjectArgsAn int value = VALUE; SimpleObject arg(0); auto future = executor.submit([=](SimpleObject object) { return value; }, arg); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); ASSERT_EQ(future.get(), value); @@ -106,6 +124,7 @@ TEST_F(ExecutorTest, testTimer_submitFunctionWithObjectReturnTypePrimitiveArgsAn int arg = 0; SimpleObject value(VALUE); auto future = executor.submit([=](int primitive) { return value; }, arg); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); ASSERT_EQ(future.get().getValue(), value.getValue()); @@ -115,6 +134,7 @@ TEST_F(ExecutorTest, testTimer_submitFunctionWithPrimitiveReturnTypePrimitiveArg int arg = 0; int value = VALUE; auto future = executor.submit([=](int number) { return value; }, arg); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); ASSERT_EQ(future.get(), value); @@ -124,6 +144,7 @@ TEST_F(ExecutorTest, testTimer_submitFunctionWithObjectReturnTypeObjectArgsAndVe SimpleObject value(VALUE); SimpleObject arg(0); auto future = executor.submit([=](SimpleObject object) { return value; }, arg); + ASSERT_TRUE(future.valid()); auto future_status = future.wait_for(SHORT_TIMEOUT_MS); ASSERT_EQ(future_status, std::future_status::ready); ASSERT_EQ(future.get().getValue(), value.getValue()); @@ -223,15 +244,14 @@ TEST_F(ExecutorTest, testTimer_futureWaitsForTaskCleanup) { SlowDestructor slowDestructor; // Submit a lambda to execute which captures a parameter by value that is slow to destruct. - executor - .submit([slowDestructor, &cleanedUp] { - // Update the captured copy of slowDestructor so that it will delay destruction and update the cleanedUp - // flag. - slowDestructor.cleanedUp = &cleanedUp; - } - // wait for the promise to be fulfilled. - ) - .wait(); + auto future = executor.submit([slowDestructor, &cleanedUp] { + // Update the captured copy of slowDestructor so that it will delay destruction and update the cleanedUp + // flag. + slowDestructor.cleanedUp = &cleanedUp; + }); + ASSERT_TRUE(future.valid()); + // wait for the promise to be fulfilled. + ASSERT_EQ(std::future_status::ready, future.wait_for(SHORT_TIMEOUT_MS * 2)); ASSERT_TRUE(cleanedUp); } @@ -264,7 +284,7 @@ TEST_F(ExecutorTest, testTimer_shutdown) { EXPECT_TRUE(executor.isShutdown()); // verify that the task has now completed - EXPECT_TRUE(done.valid()); + ASSERT_TRUE(done.valid()); done.get(); // try to submit a new task and verify that it is rejected @@ -294,7 +314,10 @@ TEST_F(ExecutorTest, testTimer_shutdownCancelJob) { auto jobToWaitDrop = [&jobToDropResult, &waitSetUp, &waitJobStart] { waitJobStart.wakeUp(); waitSetUp.wait(SHORT_TIMEOUT_MS); - jobToDropResult.wait_for(SHORT_TIMEOUT_MS); + // prevent unit test crash if jobToDropResult is not valid. It is checked below. + if (jobToDropResult.valid()) { + jobToDropResult.wait_for(SHORT_TIMEOUT_MS); + } }; // 1st job waits for setup to be done then wait for the second job to be cancelled. @@ -302,6 +325,7 @@ TEST_F(ExecutorTest, testTimer_shutdownCancelJob) { // 2nd job that should never run. When cancelled, its return will become available. jobToDropResult = executor.submit(jobToDrop); + ASSERT_TRUE(jobToDropResult.valid()); // Wake up first job and wait for it to start running. const std::chrono::seconds DEFAULT_TIMEOUT{5}; @@ -322,9 +346,9 @@ TEST_F(ExecutorTest, test_forwardPromise) { src.set_value(42); auto future = src.get_future(); - auto dst = std::make_shared>(); - forwardPromise(dst, &future); - EXPECT_EQ(dst->get_future().get(), 42); + std::promise dst; + forwardPromise(dst, future); + EXPECT_EQ(dst.get_future().get(), 42); } // Should forward the void value { @@ -332,9 +356,9 @@ TEST_F(ExecutorTest, test_forwardPromise) { src.set_value(); auto future = src.get_future(); - auto dst = std::make_shared>(); - forwardPromise(dst, &future); - EXPECT_NO_THROW(dst->get_future().get()); + std::promise dst; + forwardPromise(dst, future); + EXPECT_NO_THROW(dst.get_future().get()); } // Should forward the exception { @@ -342,9 +366,9 @@ TEST_F(ExecutorTest, test_forwardPromise) { src.set_exception(std::make_exception_ptr(std::exception())); auto future = src.get_future(); - auto dst = std::make_shared>(); - forwardPromise(dst, &future); - EXPECT_THROW(dst->get_future().get(), std::exception); + std::promise dst; + forwardPromise(dst, future); + EXPECT_THROW(dst.get_future().get(), std::exception); } // Should forward the exception { @@ -352,15 +376,16 @@ TEST_F(ExecutorTest, test_forwardPromise) { src.set_exception(std::make_exception_ptr(std::exception())); auto future = src.get_future(); - auto dst = std::make_shared>(); - forwardPromise(dst, &future); - EXPECT_THROW(dst->get_future().get(), std::exception); + std::promise dst; + forwardPromise(dst, future); + EXPECT_THROW(dst.get_future().get(), std::exception); } } TEST_F(ExecutorTest, test_taskException) { { auto future = executor.submit([] { throw std::exception(); }); + ASSERT_TRUE(future.valid()); EXPECT_THROW(future.get(), std::exception); } { @@ -370,10 +395,133 @@ TEST_F(ExecutorTest, test_taskException) { return param; }, 42); + ASSERT_TRUE(future.valid()); EXPECT_THROW(future.get(), std::runtime_error); } } +/// Verify that empty function is not accepted by executor using movable function. +TEST_F(ExecutorTest, test_executeEmptyMove) { + std::function fn; + ASSERT_FALSE(executor.execute(std::move(fn))); +} + +/// Verify that empty function is not accepted by executor using const reference function. +TEST_F(ExecutorTest, test_executeEmptyRef) { + const std::function fn; + ASSERT_FALSE(executor.execute(fn)); +} + +/// Verify that after task execution, the lambda is released if movable function is used. +TEST_F(ExecutorTest, test_executeLambdaMove) { + std::mutex mutex; + std::condition_variable cond; + volatile bool executed = false; + volatile bool canExecute = false; + volatile bool started = false; + std::error_condition error; + + auto shared = std::make_shared(); + auto weak = std::weak_ptr(shared); + auto lambda = [&, shared] { + std::unique_lock lock{mutex}; + started = true; + cond.notify_all(); + if (cond.wait_for(lock, EXECUTOR_SIGNAL_WAIT_TIMEOUT, [&] { return canExecute; })) { + executed = true; + } else { + error = std::errc::timed_out; + } + (void)&shared; + }; + + // Release strong reference and verify weak one is still valid (hold by lambda). + shared.reset(); + ASSERT_FALSE(shared); + ASSERT_TRUE(weak.lock()); + + // Initiate execution but block executor thread in lambda. + ASSERT_TRUE(executor.execute(std::move(lambda))); + // Ensure lambda execution has started and blocked. If there is a bug in lambda, the executed flag may be set only + // if we give enough time for new thread start. + { + std::unique_lock lock{mutex}; + ASSERT_TRUE(cond.wait_for(lock, EXECUTOR_SIGNAL_WAIT_TIMEOUT, [&] { return started; })); + } + ASSERT_FALSE(executed); + + // Check the reference is still valid. + ASSERT_TRUE(weak.lock()); + + // Allow lambda to complete + { + std::unique_lock lock{mutex}; + canExecute = true; + } + cond.notify_all(); + executor.waitForSubmittedTasks(); + + // Verify the task is completed and shared object is released. + ASSERT_TRUE(executed); + ASSERT_FALSE(weak.lock()); + ASSERT_FALSE(error); +} + +/// Verify that after task execution, the lambda is not released if movable function is not used. +TEST_F(ExecutorTest, test_executeLambdaRef) { + std::mutex mutex; + std::condition_variable cond; + volatile bool executed = false; + volatile bool canExecute = false; + volatile bool started = false; + std::error_condition error; + + auto shared = std::make_shared(); + auto weak = std::weak_ptr(shared); + const auto lambda = [&, shared] { + std::unique_lock lock{mutex}; + started = true; + cond.notify_all(); + if (cond.wait_for(lock, EXECUTOR_SIGNAL_WAIT_TIMEOUT, [&] { return canExecute; })) { + executed = true; + } else { + error = std::errc::timed_out; + } + (void)&shared; + }; + + // Release strong reference and verify weak one is still valid (hold by lambda). + shared.reset(); + ASSERT_FALSE(shared); + ASSERT_TRUE(weak.lock()); + + // Initiate execution but block executor thread in lambda. + ASSERT_TRUE(executor.execute(lambda)); + // Ensure lambda execution has started and blocked. If there is a bug in lambda, the executed flag may be set only + // if we give enough time for new thread start. + { + std::unique_lock lock{mutex}; + ASSERT_TRUE(cond.wait_for(lock, EXECUTOR_SIGNAL_WAIT_TIMEOUT, [&] { return started; })); + } + ASSERT_FALSE(executed); + + // Check the reference is still valid. + ASSERT_TRUE(weak.lock()); + + // Allow lambda to complete + { + std::unique_lock lock{mutex}; + canExecute = true; + } + cond.notify_all(); + executor.waitForSubmittedTasks(); + + // Verify task is completed and we still have shared object through lambda + ASSERT_TRUE(executed); + ASSERT_TRUE(weak.lock()); + ASSERT_FALSE(error); +} + } // namespace test } // namespace threading } // namespace utils diff --git a/AVSCommon/Utils/test/FileSystemUtilsTest.cpp b/AVSCommon/Utils/test/FileSystemUtilsTest.cpp index 1a91f61335..b9c18dcf4a 100644 --- a/AVSCommon/Utils/test/FileSystemUtilsTest.cpp +++ b/AVSCommon/Utils/test/FileSystemUtilsTest.cpp @@ -41,7 +41,7 @@ class FileSystemUtilsTest : public ::testing::Test { createDirectory(WORKING_DIR); ASSERT_TRUE(exists(WORKING_DIR)); -#if defined(__linux__) or defined(__APPLE__) +#if defined(__linux__) || defined(__APPLE__) // on some OS, the temp path is symbolically linked, which can cause issues for prefix tests // to accommodate this, get the realpath of the temp directory char resolved_path[PATH_MAX + 1]; diff --git a/AVSCommon/Utils/test/GmockExtensionTest.cpp b/AVSCommon/Utils/test/GmockExtensionTest.cpp new file mode 100644 index 0000000000..2064840f20 --- /dev/null +++ b/AVSCommon/Utils/test/GmockExtensionTest.cpp @@ -0,0 +1,221 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 test { + +using namespace ::testing; + +/** + * @brief Interface with noexcept and const noexcept methods. + */ +class SomeInterface { +public: + virtual ~SomeInterface() noexcept = default; + virtual int noexceptMethod0() noexcept = 0; + virtual int noexceptMethod1(int) noexcept = 0; + virtual int noexceptMethod2(int, int) noexcept = 0; + virtual int noexceptMethod3(int, int, int) noexcept = 0; + virtual int noexceptMethod4(int, int, int, int) noexcept = 0; + virtual int noexceptMethod5(int, int, int, int, int) noexcept = 0; + virtual int noexceptMethod6(int, int, int, int, int, int) noexcept = 0; + virtual int noexceptMethod7(int, int, int, int, int, int, int) noexcept = 0; + virtual int noexceptMethod8(int, int, int, int, int, int, int, int) noexcept = 0; + virtual int noexceptMethod9(int, int, int, int, int, int, int, int, int) noexcept = 0; + virtual int noexceptMethod10(int, int, int, int, int, int, int, int, int, int) noexcept = 0; + virtual int constNoexceptMethod0() const noexcept = 0; + virtual int constNoexceptMethod1(int) const noexcept = 0; + virtual int constNoexceptMethod2(int, int) const noexcept = 0; + virtual int constNoexceptMethod3(int, int, int) const noexcept = 0; + virtual int constNoexceptMethod4(int, int, int, int) const noexcept = 0; + virtual int constNoexceptMethod5(int, int, int, int, int) const noexcept = 0; + virtual int constNoexceptMethod6(int, int, int, int, int, int) const noexcept = 0; + virtual int constNoexceptMethod7(int, int, int, int, int, int, int) const noexcept = 0; + virtual int constNoexceptMethod8(int, int, int, int, int, int, int, int) const noexcept = 0; + virtual int constNoexceptMethod9(int, int, int, int, int, int, int, int, int) const noexcept = 0; + virtual int constNoexceptMethod10(int, int, int, int, int, int, int, int, int, int) const noexcept = 0; +}; + +/** + * @brief Mock for @c SomeInterface. + */ +class SomeMock : public SomeInterface { +public: + MOCK_NOEXCEPT_METHOD0(noexceptMethod0, int()); + MOCK_NOEXCEPT_METHOD1(noexceptMethod1, int(int)); + MOCK_NOEXCEPT_METHOD2(noexceptMethod2, int(int, int)); + MOCK_NOEXCEPT_METHOD3(noexceptMethod3, int(int, int, int)); + MOCK_NOEXCEPT_METHOD4(noexceptMethod4, int(int, int, int, int)); + MOCK_NOEXCEPT_METHOD5(noexceptMethod5, int(int, int, int, int, int)); + MOCK_NOEXCEPT_METHOD6(noexceptMethod6, int(int, int, int, int, int, int)); + MOCK_NOEXCEPT_METHOD7(noexceptMethod7, int(int, int, int, int, int, int, int)); + MOCK_NOEXCEPT_METHOD8(noexceptMethod8, int(int, int, int, int, int, int, int, int)); + MOCK_NOEXCEPT_METHOD9(noexceptMethod9, int(int, int, int, int, int, int, int, int, int)); + MOCK_NOEXCEPT_METHOD10(noexceptMethod10, int(int, int, int, int, int, int, int, int, int, int)); + + MOCK_CONST_NOEXCEPT_METHOD0(constNoexceptMethod0, int()); + MOCK_CONST_NOEXCEPT_METHOD1(constNoexceptMethod1, int(int)); + MOCK_CONST_NOEXCEPT_METHOD2(constNoexceptMethod2, int(int, int)); + MOCK_CONST_NOEXCEPT_METHOD3(constNoexceptMethod3, int(int, int, int)); + MOCK_CONST_NOEXCEPT_METHOD4(constNoexceptMethod4, int(int, int, int, int)); + MOCK_CONST_NOEXCEPT_METHOD5(constNoexceptMethod5, int(int, int, int, int, int)); + MOCK_CONST_NOEXCEPT_METHOD6(constNoexceptMethod6, int(int, int, int, int, int, int)); + MOCK_CONST_NOEXCEPT_METHOD7(constNoexceptMethod7, int(int, int, int, int, int, int, int)); + MOCK_CONST_NOEXCEPT_METHOD8(constNoexceptMethod8, int(int, int, int, int, int, int, int, int)); + MOCK_CONST_NOEXCEPT_METHOD9(constNoexceptMethod9, int(int, int, int, int, int, int, int, int, int)); + MOCK_CONST_NOEXCEPT_METHOD10(constNoexceptMethod10, int(int, int, int, int, int, int, int, int, int, int)); +}; + +/** + * @brief Test fixture for testing GMock extensions. + */ +class GmockExtensionsTest : public ::testing::Test {}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod0) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod0()).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod0()); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod1) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod1(1)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod1(1)); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod2) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod2(1, 2)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod2(1, 2)); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod3) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod3(1, 2, 3)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod3(1, 2, 3)); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod4) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod4(1, 2, 3, 4)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod4(1, 2, 3, 4)); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod5) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod5(1, 2, 3, 4, 5)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod5(1, 2, 3, 4, 5)); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod6) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod6(1, 2, 3, 4, 5, 6)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod6(1, 2, 3, 4, 5, 6)); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod7) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod7(1, 2, 3, 4, 5, 6, 7)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod7(1, 2, 3, 4, 5, 6, 7)); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod8) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod8(1, 2, 3, 4, 5, 6, 7, 8)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod8(1, 2, 3, 4, 5, 6, 7, 8)); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod9) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod9(1, 2, 3, 4, 5, 6, 7, 8, 9)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod9(1, 2, 3, 4, 5, 6, 7, 8, 9)); +}; + +TEST_F(GmockExtensionsTest, test_noexceptMethod10) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod0) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod0()).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod0()); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod1) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod1(1)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod1(1)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod2) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod2(1, 2)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod2(1, 2)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod3) { + SomeMock mock; + EXPECT_CALL(mock, noexceptMethod3(1, 2, 3)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.noexceptMethod3(1, 2, 3)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod4) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod4(1, 2, 3, 4)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod4(1, 2, 3, 4)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod5) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod5(1, 2, 3, 4, 5)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod5(1, 2, 3, 4, 5)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod6) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod6(1, 2, 3, 4, 5, 6)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod6(1, 2, 3, 4, 5, 6)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod7) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod7(1, 2, 3, 4, 5, 6, 7)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod7(1, 2, 3, 4, 5, 6, 7)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod8) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod8(1, 2, 3, 4, 5, 6, 7, 8)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod8(1, 2, 3, 4, 5, 6, 7, 8)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod9) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod9(1, 2, 3, 4, 5, 6, 7, 8, 9)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod9(1, 2, 3, 4, 5, 6, 7, 8, 9)); +}; + +TEST_F(GmockExtensionsTest, test_constNoexceptMethod10) { + SomeMock mock; + EXPECT_CALL(mock, constNoexceptMethod10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).Times(1).WillOnce(Return(-5)); + ASSERT_EQ(-5, mock.constNoexceptMethod10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); +}; + +} // namespace test +} // namespace alexaClientSDK diff --git a/AVSCommon/Utils/test/JSONUtilTest.cpp b/AVSCommon/Utils/test/JSONUtilTest.cpp index 562a527218..3e6268bbd7 100644 --- a/AVSCommon/Utils/test/JSONUtilTest.cpp +++ b/AVSCommon/Utils/test/JSONUtilTest.cpp @@ -293,7 +293,7 @@ TEST_F(JSONUtilTest, test_parseJSONInvalidJSON) { */ TEST_F(JSONUtilTest, test_convertToStringValueWithString) { rapidjson::Value expected; - expected.SetString(STRING_VALUE.c_str(), STRING_VALUE.length()); + expected.SetString(STRING_VALUE.c_str(), static_cast(STRING_VALUE.length())); std::string actual; ASSERT_TRUE(convertToValue(expected, &actual)); ASSERT_EQ(expected.GetString(), actual); @@ -326,7 +326,7 @@ TEST_F(JSONUtilTest, test_convertToStringValueWithInvalidValue) { */ TEST_F(JSONUtilTest, test_convertToStringValueWithNullOutputParam) { rapidjson::Value node; - node.SetString(STRING_VALUE.c_str(), STRING_VALUE.length()); + node.SetString(STRING_VALUE.c_str(), static_cast(STRING_VALUE.length())); std::string* value = nullptr; ASSERT_FALSE(convertToValue(node, value)); } diff --git a/AVSCommon/Utils/test/LibCurlHTTP2ConnectionTest.cpp b/AVSCommon/Utils/test/LibCurlHTTP2ConnectionTest.cpp new file mode 100644 index 0000000000..bbf237d84f --- /dev/null +++ b/AVSCommon/Utils/test/LibCurlHTTP2ConnectionTest.cpp @@ -0,0 +1,68 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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/LibcurlHTTP2Request.h" + +namespace alexaClientSDK { +namespace avsCommon { +namespace utils { +namespace libcurlUtils { +using namespace ::testing; + +/** + * Derived test class for LibCurlHTTP2Connection that is a friend class to the google test class + */ +class LibCurlHTTP2Connection_Test : public LibcurlHTTP2Connection { +public: + LibCurlHTTP2Connection_Test() = default; + friend class GTEST_TEST_CLASS_NAME_(LibCurlHTTP2ConnectionTest, releaseStream_delete_ok); +}; +/** + * Test fixture class for LibCurlHTTP2Connection + */ + +class LibCurlHTTP2ConnectionTest : public ::testing::Test { +protected: + void SetUp() override; + /// The LibCurlHTTP2Connection to test. + std::shared_ptr m_libCurlHTTP2Connection; +}; + +void LibCurlHTTP2ConnectionTest::SetUp() { + m_libCurlHTTP2Connection = std::make_shared(); +} + +TEST_F(LibCurlHTTP2ConnectionTest, releaseStream_delete_ok) { + // Setting IsStopping bool to true because we do not want network loop to process this request + m_libCurlHTTP2Connection->setIsStopping(); + m_libCurlHTTP2Connection->createMultiHandle(); + http2::HTTP2RequestConfig config{http2::HTTP2RequestType::GET, "www.foo.com", "xyz"}; + config.setConnectionTimeout(std::chrono::seconds(60)); + config.setIntermittentTransferExpected(); + auto req = std::make_shared(config, nullptr, config.getId()); + auto handle = req->getCurlHandle(); + m_libCurlHTTP2Connection->m_activeStreams[handle] = req; + ASSERT_TRUE(m_libCurlHTTP2Connection->m_activeStreams.size()); + LibCurlHTTP2Connection_Test::ActiveStreamMap::iterator iter = m_libCurlHTTP2Connection->m_activeStreams.begin(); + m_libCurlHTTP2Connection->releaseStream(iter); + ASSERT_FALSE(m_libCurlHTTP2Connection->m_activeStreams.size()); +} + +} // namespace libcurlUtils +} // namespace utils +} // namespace avsCommon +} // namespace alexaClientSDK \ No newline at end of file diff --git a/AVSCommon/Utils/test/LoggerTest.cpp b/AVSCommon/Utils/test/LoggerTest.cpp index f2442027b0..6460ad9b52 100644 --- a/AVSCommon/Utils/test/LoggerTest.cpp +++ b/AVSCommon/Utils/test/LoggerTest.cpp @@ -191,7 +191,7 @@ void MockLogger::mockEmit( m_lastText = text; } -MockModuleLogger::MockModuleLogger() : ModuleLogger(ACSDK_STRINGIFY(ACSDK_LOG_SINK)) { +MockModuleLogger::MockModuleLogger() : ModuleLogger("ConsoleLogger") { } MockModuleLogger::~MockModuleLogger() { @@ -231,7 +231,7 @@ void LoggerTest::TearDown() { } void LoggerTest::setLevelExpectations(Level level) { - ACSDK_GET_LOGGER_FUNCTION()->setLevel(level); + getLoggerTestLogger()->setLevel(level); switch (level) { case Level::UNKNOWN: @@ -469,16 +469,19 @@ TEST_F(LoggerTest, test_logNoneLevel) { /** * Test observer mechanism in the MockModuleLogger. Expects that when the logLevel changes for the sink, the - * 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. + * callback of the MockModuleLogger is triggered. Also make sure any changes to MockModuleLogger's logLevel will + * override set log level of the MockModuleLogger and the MockModuleLogger will ignore further changes to the logLevel + * to the sink. */ TEST_F(LoggerTest, test_moduleLoggerObserver) { MockModuleLogger mockModuleLogger; - getLoggerTestLogger()->setLevel(Level::WARN); + getLoggerTestLogger()->setLevel(Level::ERROR); + ASSERT_EQ(mockModuleLogger.getLogLevel(), Level::ERROR); + mockModuleLogger.setLevel(Level::WARN); ASSERT_EQ(mockModuleLogger.getLogLevel(), Level::WARN); - mockModuleLogger.setLevel(Level::CRITICAL); - ASSERT_EQ(mockModuleLogger.getLogLevel(), Level::CRITICAL); getLoggerTestLogger()->setLevel(Level::NONE); + ASSERT_EQ(mockModuleLogger.getLogLevel(), Level::WARN); + mockModuleLogger.setLevel(Level::NONE); ASSERT_EQ(mockModuleLogger.getLogLevel(), Level::NONE); } @@ -490,20 +493,20 @@ TEST_F(LoggerTest, test_multipleModuleLoggerObservers) { MockModuleLogger mockModuleLogger2; MockModuleLogger mockModuleLogger3; - getLoggerTestLogger()->setLevel(Level::WARN); - ASSERT_EQ(mockModuleLogger1.getLogLevel(), Level::WARN); - ASSERT_EQ(mockModuleLogger2.getLogLevel(), Level::WARN); - ASSERT_EQ(mockModuleLogger3.getLogLevel(), Level::WARN); + getLoggerTestLogger()->setLevel(Level::NONE); + ASSERT_EQ(mockModuleLogger1.getLogLevel(), Level::NONE); + ASSERT_EQ(mockModuleLogger2.getLogLevel(), Level::NONE); + ASSERT_EQ(mockModuleLogger3.getLogLevel(), Level::NONE); mockModuleLogger1.setLevel(Level::CRITICAL); ASSERT_EQ(mockModuleLogger1.getLogLevel(), Level::CRITICAL); - ASSERT_EQ(mockModuleLogger2.getLogLevel(), Level::WARN); - ASSERT_EQ(mockModuleLogger3.getLogLevel(), Level::WARN); - - getLoggerTestLogger()->setLevel(Level::NONE); - ASSERT_EQ(mockModuleLogger1.getLogLevel(), Level::NONE); ASSERT_EQ(mockModuleLogger2.getLogLevel(), Level::NONE); ASSERT_EQ(mockModuleLogger3.getLogLevel(), Level::NONE); + + getLoggerTestLogger()->setLevel(Level::WARN); + ASSERT_EQ(mockModuleLogger1.getLogLevel(), Level::CRITICAL); + ASSERT_EQ(mockModuleLogger2.getLogLevel(), Level::WARN); + ASSERT_EQ(mockModuleLogger3.getLogLevel(), Level::WARN); } #ifdef ACSDK_LOG_ENABLED diff --git a/AVSCommon/Utils/test/MIMEParserTest.cpp b/AVSCommon/Utils/test/MIMEParserTest.cpp index 6a61f14af6..a60cddca4f 100644 --- a/AVSCommon/Utils/test/MIMEParserTest.cpp +++ b/AVSCommon/Utils/test/MIMEParserTest.cpp @@ -315,7 +315,7 @@ void runTestForCombination(std::vector& partition, const std::string& paylo void generateCombinationsAndRunTest( std::vector& partitions, size_t pos, - size_t remaining, + int remaining, const std::string& words, const std::string& boundary) { if (remaining == 0) { @@ -351,7 +351,7 @@ TEST_F(MIMEParserTest, test_multipleCombinations) { for (size_t numberOfPartitions = 1; numberOfPartitions <= maxNumberOfPartitions; numberOfPartitions += numberOfPartitionsIncrement) { std::vector partitions(numberOfPartitions); - generateCombinationsAndRunTest(partitions, 0, payload.size(), payload, "WWWoooAAA"); + generateCombinationsAndRunTest(partitions, 0, static_cast(payload.size()), payload, "WWWoooAAA"); } } @@ -379,12 +379,12 @@ TEST_F(MIMEParserTest, test_fixedSizeGroups) { decoder.onReceiveResponseCode(HTTPResponseCode::SUCCESS_OK); std::vector groups; - int nGroups = (parts.size() + groupSize - 1) / groupSize; - for (int i = 0; i < nGroups; i++) { + size_t nGroups = (parts.size() + groupSize - 1) / groupSize; + for (size_t i = 0; i < nGroups; i++) { groups.push_back(""); } - int currentGroup = 0; + size_t currentGroup = 0; for (size_t i = 0; i < parts.size(); ++i) { if (i != 0 && i % groupSize == 0) { currentGroup++; @@ -480,7 +480,7 @@ TEST_F(MIMEParserTest, test_decodingRandomBoundaries) { const std::string CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'()+_,-.:=?"; std::random_device random_device; std::mt19937 generator(random_device()); - std::uniform_int_distribution<> distribution(0, CHARACTERS.size() - 1); + std::uniform_int_distribution<> distribution(0, static_cast(CHARACTERS.size() - 1)); for (size_t length = 5; length < 50; length++) { std::string quotedRandomBoundary = QUOTE_CHAR; @@ -523,9 +523,9 @@ TEST_F(MIMEParserTest, test_decodingInvalidBoundaries) { "thisstringhasmorethanseventycharacterssoitsinvalid123123123123123123123", "^invalidchar", "\"^invalidchar\""}; - for (auto boundary : invalid_boundaries) { + for (auto testBoundary : invalid_boundaries) { for (auto header : headerAfterBoundary) { - const std::string boundaryHeader{BOUNDARY_HEADER_PREFIX + boundary + header}; + const std::string boundaryHeader{BOUNDARY_HEADER_PREFIX + testBoundary + header}; auto sink = std::make_shared(); HTTP2MimeResponseDecoder decoder{sink}; ASSERT_FALSE(decoder.onReceiveHeaderLine(boundaryHeader)); @@ -538,9 +538,10 @@ TEST_F(MIMEParserTest, test_decodingInvalidBoundaries) { * Expected Result: pass */ TEST_F(MIMEParserTest, test_decodingBoundaryAfteraNonBoundaryHeader) { - std::string boundary = "myboundary"; - const std::vector headers = {"content-type:nana;myprop:abc\r\n", BOUNDARY_HEADER_PREFIX + boundary}; - generatePayloadAndTest(headers, boundary); + std::string testBoundary = "myboundary"; + const std::vector headers = {"content-type:nana;myprop:abc\r\n", + BOUNDARY_HEADER_PREFIX + testBoundary}; + generatePayloadAndTest(headers, testBoundary); } /* @@ -549,10 +550,10 @@ TEST_F(MIMEParserTest, test_decodingBoundaryAfteraNonBoundaryHeader) { */ TEST_F(MIMEParserTest, test_decodingValidBoundariesWithMoreHeaders) { std::vector headerAfterBoundary = {"", ";otherprop=yes", " somethingelse"}; - std::string boundary = "myboundary"; + std::string testBoundary = "myboundary"; for (auto header : headerAfterBoundary) { - const std::string boundaryHeader{boundary + header}; - generatePayloadAndTest(boundaryHeader, boundary); + const std::string boundaryHeader{testBoundary + header}; + generatePayloadAndTest(boundaryHeader, testBoundary); } } @@ -970,8 +971,8 @@ TEST_F(MIMEParserTest, test_aBORT) { sink->m_abort = true; // setup encoder - std::string boundary = createRandomAlphabetString(10); - HTTP2MimeRequestEncoder encoder{boundary, source}; + std::string testBoundary = createRandomAlphabetString(10); + HTTP2MimeRequestEncoder encoder{testBoundary, source}; char buf[LARGE]; // Ensure repeated calls return ABORT ASSERT_TRUE(encoder.onSendData(buf, SMALL).status == HTTP2SendStatus::ABORT); @@ -1041,8 +1042,8 @@ TEST_F(MIMEParserTest, test_variableChunkSizes) { char buf[XLARGE]; // setup encoder - std::string boundary = createRandomAlphabetString(10); - HTTP2MimeRequestEncoder encoder{boundary, source}; + std::string testBoundary = createRandomAlphabetString(10); + HTTP2MimeRequestEncoder encoder{testBoundary, source}; // setup decoder HTTP2MimeResponseDecoder decoder{sink}; @@ -1075,7 +1076,7 @@ TEST_F(MIMEParserTest, test_variableChunkSizes) { index = 0; pauseCount = 0; HTTP2ReceiveDataStatus status{HTTP2ReceiveDataStatus::SUCCESS}; - const std::string boundaryHeader{BOUNDARY_HEADER_PREFIX + boundary}; + const std::string boundaryHeader{BOUNDARY_HEADER_PREFIX + testBoundary}; bool resp = decoder.onReceiveHeaderLine(boundaryHeader); decoder.onReceiveResponseCode( static_cast::type>(HTTPResponseCode::SUCCESS_OK)); diff --git a/AVSCommon/Utils/test/PooledMediaPlayerFactoryTest.cpp b/AVSCommon/Utils/test/PooledMediaPlayerFactoryTest.cpp index e699e0aca8..2405c9b004 100644 --- a/AVSCommon/Utils/test/PooledMediaPlayerFactoryTest.cpp +++ b/AVSCommon/Utils/test/PooledMediaPlayerFactoryTest.cpp @@ -29,7 +29,7 @@ using namespace avsCommon::utils::mediaPlayer; using namespace avsCommon::utils::mediaPlayer::test; /// String to identify log entries originating from this file. -static const std::string TAG("PooledMediaPlayerFactoryTest"); +#define TAG "PooledMediaPlayerFactoryTest" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/AVSCommon/Utils/test/SafeTimeAccessTest.cpp b/AVSCommon/Utils/test/SafeTimeAccessTest.cpp index c6519f0122..f3dc70055b 100644 --- a/AVSCommon/Utils/test/SafeTimeAccessTest.cpp +++ b/AVSCommon/Utils/test/SafeTimeAccessTest.cpp @@ -142,7 +142,7 @@ static void callSafeCTimeFunction( auto safeCTimeAccess = SafeCTimeAccess::instance(); std::vector> internalResults; for (int i = 0; i < 4; ++i) { - for (time_t t = startingSeed; t < LARGE_TIME_VALUE; t = 1.5 * (t + 1)) { + for (time_t t = startingSeed; t < LARGE_TIME_VALUE; t = static_cast(1.5 * (t + 1))) { std::tm result; switch (type) { case TestType::GMTIME: diff --git a/AVSCommon/Utils/test/SharedDataStreamTest.cpp b/AVSCommon/Utils/test/SharedDataStreamTest.cpp index 791098fcff..cf89d8f6b5 100644 --- a/AVSCommon/Utils/test/SharedDataStreamTest.cpp +++ b/AVSCommon/Utils/test/SharedDataStreamTest.cpp @@ -220,7 +220,7 @@ std::future Source::run( for (size_t word = 0; word < blockSizeWords; ++word) { for (size_t byte = 0; byte < wordSize; ++byte) { size_t byteIndex = word * wordSize + byte; - uint8_t byteValue = m_counter >> (byte % wordSize); + uint8_t byteValue = static_cast(m_counter >> (byte % wordSize)); block[byteIndex] = byteValue; } ++m_counter; @@ -314,7 +314,7 @@ std::future Sink::run( for (ssize_t word = 0; word < nWords; ++word) { for (size_t byte = 0; byte < wordSize; ++byte) { size_t byteIndex = word * wordSize + byte; - uint8_t byteValue = m_counter >> (byte % wordSize); + uint8_t byteValue = static_cast(m_counter >> (byte % wordSize)); ASSERT_EQ(block[byteIndex], byteValue); } ++m_counter; @@ -714,7 +714,7 @@ TEST_F(SharedDataStreamTest, test_readerSeek) { Sds::Index writerPos = 0; uint8_t writeBuf[WORDSIZE * WORDCOUNT]; for (size_t i = 0; i < sizeof(writeBuf); ++i) { - writeBuf[i] = i; + writeBuf[i] = static_cast(i); } size_t writeWords = WORDCOUNT / 2; ASSERT_EQ(writer->write(writeBuf, writeWords), static_cast(writeWords)); diff --git a/AVSCommon/Utils/test/TaskThreadTest.cpp b/AVSCommon/Utils/test/TaskThreadTest.cpp index d87c3973b3..6a277e1074 100644 --- a/AVSCommon/Utils/test/TaskThreadTest.cpp +++ b/AVSCommon/Utils/test/TaskThreadTest.cpp @@ -27,6 +27,10 @@ namespace test { /// Timeout used while waiting for synchronization events. const std::chrono::milliseconds MY_WAIT_TIMEOUT{100}; +/// Default thread moniker to use in tests. +const auto THREAD_MONIKER = "1a1"; +/// Another thread moniker to use in tests. +const auto THREAD_MONIKER2 = "1a2"; using namespace logger; @@ -39,7 +43,7 @@ TEST(TaskThreadTest, test_waitForNothing) { TEST(TaskThreadTest, test_startFailsDueToEmptyFunction) { TaskThread taskThread; std::function emptyFunction; - EXPECT_FALSE(taskThread.start(emptyFunction)); + EXPECT_FALSE(taskThread.start(emptyFunction, THREAD_MONIKER)); } /// Test that start will trigger the provided job and thread will exit once the job is done and return @c false. @@ -54,7 +58,7 @@ TEST(TaskThreadTest, test_simpleJob) { { TaskThread taskThread; - EXPECT_TRUE(taskThread.start(simpleJob)); + EXPECT_TRUE(taskThread.start(simpleJob, THREAD_MONIKER)); EXPECT_TRUE(waitEvent.wait(MY_WAIT_TIMEOUT)); } @@ -78,7 +82,7 @@ TEST(TaskThreadTest, test_sequenceJobs) { { TaskThread taskThread; - EXPECT_TRUE(taskThread.start(jobSequence)); + EXPECT_TRUE(taskThread.start(jobSequence, THREAD_MONIKER)); EXPECT_TRUE(waitEvent.wait(MY_WAIT_TIMEOUT)); } @@ -108,10 +112,10 @@ TEST(TaskThreadTest, test_startNewJob) { }; TaskThread taskThread; - EXPECT_TRUE(taskThread.start(increment)); + EXPECT_TRUE(taskThread.start(increment, THREAD_MONIKER)); EXPECT_TRUE(waitEvent.wait(MY_WAIT_TIMEOUT)); - EXPECT_TRUE(taskThread.start(decrement)); + EXPECT_TRUE(taskThread.start(decrement, THREAD_MONIKER)); EXPECT_TRUE(waitEvent2.wait(MY_WAIT_TIMEOUT)); EXPECT_TRUE(taskCounter == 0); } @@ -126,11 +130,11 @@ TEST(TaskThreadTest, testTimer_startFailDueTooManyThreads) { }; TaskThread taskThread; - EXPECT_TRUE(taskThread.start(simpleJob)); + EXPECT_TRUE(taskThread.start(simpleJob, THREAD_MONIKER)); // Wait until first job has started. - waitStart.wait(MY_WAIT_TIMEOUT); - EXPECT_TRUE(taskThread.start([] { return false; })); + EXPECT_TRUE(waitStart.wait(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(taskThread.start([] { return false; }, THREAD_MONIKER)); // Starting a thread again immediately should fail, unless the system is so fast in starting // the thread on the other core that it starts and runs a few instructions before this can @@ -138,7 +142,7 @@ TEST(TaskThreadTest, testTimer_startFailDueTooManyThreads) { int threadStartCount; for (threadStartCount = 0; threadStartCount < 100; threadStartCount++) { // This should fail since the task thread is starting. - if (!taskThread.start([] { return false; })) { + if (!taskThread.start([] { return false; }, THREAD_MONIKER)) { break; } } @@ -147,7 +151,7 @@ TEST(TaskThreadTest, testTimer_startFailDueTooManyThreads) { waitEnqueue.wakeUp(); } -/// Test that threads related to this task thread will always have the same moniker. +/// Test that threads related to this task thread will always have specified moniker. TEST(TaskThreadTest, DISABLED_test_moniker) { WaitEvent waitGetMoniker, waitValidateMoniker; std::string moniker; @@ -157,18 +161,22 @@ TEST(TaskThreadTest, DISABLED_test_moniker) { return false; }; - auto validateMoniker = [&moniker, &waitValidateMoniker] { - EXPECT_EQ(moniker, ThreadMoniker::getThisThreadMoniker()); + std::string moniker2; + auto validateMoniker = [&moniker2, &waitValidateMoniker] { + moniker2 = ThreadMoniker::getThisThreadMoniker(); waitValidateMoniker.wakeUp(); return false; }; TaskThread taskThread; - EXPECT_TRUE(taskThread.start(getMoniker)); - waitGetMoniker.wait(MY_WAIT_TIMEOUT); + EXPECT_TRUE(taskThread.start(getMoniker, THREAD_MONIKER)); + EXPECT_TRUE(waitGetMoniker.wait(MY_WAIT_TIMEOUT)); - EXPECT_TRUE(taskThread.start(validateMoniker)); - waitValidateMoniker.wait(MY_WAIT_TIMEOUT); + EXPECT_TRUE(taskThread.start(validateMoniker, THREAD_MONIKER2)); + EXPECT_TRUE(waitValidateMoniker.wait(MY_WAIT_TIMEOUT)); + + EXPECT_EQ(THREAD_MONIKER, moniker); + EXPECT_EQ(THREAD_MONIKER2, moniker2); } /// Test that threads from different @c TaskThreads will have different monikers. @@ -183,19 +191,23 @@ TEST(TaskThreadTest, test_monikerDifferentObjects) { return false; }; - auto validateMoniker = [&moniker, &waitValidateMoniker] { - EXPECT_NE(moniker, ThreadMoniker::getThisThreadMoniker()); + std::string moniker2; + auto validateMoniker = [&moniker2, &waitValidateMoniker] { + moniker2 = ThreadMoniker::getThisThreadMoniker(); waitValidateMoniker.wakeUp(); return false; }; TaskThread taskThread1; TaskThread taskThread2; - EXPECT_TRUE(taskThread1.start(getMoniker)); - EXPECT_TRUE(taskThread2.start(validateMoniker)); + EXPECT_TRUE(taskThread1.start(getMoniker, THREAD_MONIKER)); + EXPECT_TRUE(taskThread2.start(validateMoniker, THREAD_MONIKER2)); waitThread2Start.wakeUp(); - waitGetMoniker.wait(MY_WAIT_TIMEOUT); - waitValidateMoniker.wait(MY_WAIT_TIMEOUT); + EXPECT_TRUE(waitGetMoniker.wait(MY_WAIT_TIMEOUT)); + EXPECT_TRUE(waitValidateMoniker.wait(MY_WAIT_TIMEOUT)); + + EXPECT_EQ(THREAD_MONIKER, moniker); + EXPECT_EQ(THREAD_MONIKER2, moniker2); } } // namespace test diff --git a/AVSCommon/Utils/test/TimeUtilsTest.cpp b/AVSCommon/Utils/test/TimeUtilsTest.cpp index b05456dcc5..066b2d5831 100644 --- a/AVSCommon/Utils/test/TimeUtilsTest.cpp +++ b/AVSCommon/Utils/test/TimeUtilsTest.cpp @@ -60,6 +60,32 @@ TEST(TimeTest, test_iso8601StringConversion) { ASSERT_EQ(static_cast(sec.count()), unixTime); } +TEST(TimeTest, test_iso8601StringConversionUTC) { + TimeUtils timeUtils; + std::string iso8601Str{"1986-08-10T21:30:00Z"}; + int64_t unixTime; + auto successUnix = timeUtils.convert8601TimeStringToUnix(iso8601Str, &unixTime); + ASSERT_TRUE(successUnix); + + std::chrono::system_clock::time_point utcTimePoint; + auto successUtcTimePoint = timeUtils.convert8601TimeStringToUtcTimePoint(iso8601Str, &utcTimePoint); + ASSERT_TRUE(successUtcTimePoint); + + time_t dateTimeT = std::chrono::system_clock::to_time_t(utcTimePoint); + auto sec = std::chrono::duration_cast(utcTimePoint.time_since_epoch()); + ASSERT_EQ(static_cast(sec.count()), unixTime); + + std::tm dateTm; + auto safeCTimeAccess = SafeCTimeAccess::instance(); + ASSERT_TRUE(safeCTimeAccess->getGmtime(dateTimeT, &dateTm)); + ASSERT_EQ(dateTm.tm_year, 86); + ASSERT_EQ(dateTm.tm_mon, 7); + ASSERT_EQ(dateTm.tm_mday, 10); + ASSERT_EQ(dateTm.tm_hour, 21); + ASSERT_EQ(dateTm.tm_min, 30); + ASSERT_EQ(dateTm.tm_sec, 0); +} + TEST(TimeTest, test_stringConversionError) { TimeUtils timeUtils; std::string dateStr{"1986-8-10T21:30:00+0000"}; diff --git a/AVSCommon/Utils/test/TimerTest.cpp b/AVSCommon/Utils/test/TimerTest.cpp index 09e30d926f..0bf9966db7 100644 --- a/AVSCommon/Utils/test/TimerTest.cpp +++ b/AVSCommon/Utils/test/TimerTest.cpp @@ -18,7 +18,7 @@ #include #include -#include "AVSCommon/Utils/Timing/Timer.h" +#include namespace alexaClientSDK { namespace avsCommon { @@ -111,7 +111,7 @@ class TimerTest : public ::testing::Test { }; void TimerTest::SetUp() { - m_timer = std::shared_ptr(new Timer); + m_timer = std::make_shared(); } void TimerTest::simpleTask(std::chrono::milliseconds duration) { diff --git a/AVSCommon/Utils/test/UUIDGenerationTest.cpp b/AVSCommon/Utils/test/UUIDGenerationTest.cpp index 9d7371d7be..e22e26cb45 100644 --- a/AVSCommon/Utils/test/UUIDGenerationTest.cpp +++ b/AVSCommon/Utils/test/UUIDGenerationTest.cpp @@ -157,7 +157,7 @@ TEST_F(UUIDGenerationTest, test_multipleConcurrentSaltSettings) { } for (auto& future : uuidRequesters) { - unsigned int prevSizeOfSet = uuidsGenerated.size(); + auto prevSizeOfSet = uuidsGenerated.size(); auto uuid = future.get(); uuidsGenerated.insert(uuid); ASSERT_EQ(UUID_LENGTH, uuid.length()); @@ -186,7 +186,7 @@ TEST_F(UUIDGenerationTest, test_multipleRequests) { std::unordered_set uuidsGenerated; for (unsigned int i = 0; i < MAX_UUIDS_TO_GENERATE; ++i) { - unsigned int prevSizeOfSet = uuidsGenerated.size(); + auto prevSizeOfSet = uuidsGenerated.size(); auto uuid = generateUUID(); uuidsGenerated.insert(uuid); ASSERT_EQ(UUID_LENGTH, uuid.length()); @@ -211,7 +211,7 @@ TEST_F(UUIDGenerationTest, test_multipleConcurrentRequests) { } for (auto& future : uuidRequesters) { - unsigned int prevSizeOfSet = uuidsGenerated.size(); + auto prevSizeOfSet = uuidsGenerated.size(); auto uuid = future.get(); uuidsGenerated.insert(uuid); ASSERT_EQ(UUID_LENGTH, uuid.length()); diff --git a/AVSCommon/Utils/test/WorkerThreadTest.cpp b/AVSCommon/Utils/test/WorkerThreadTest.cpp index b7d595b0c5..0d6dd6a1a3 100644 --- a/AVSCommon/Utils/test/WorkerThreadTest.cpp +++ b/AVSCommon/Utils/test/WorkerThreadTest.cpp @@ -35,7 +35,6 @@ TEST(TaskThreadTest, test_runWorkSeveralTimes) { std::atomic_int count{0}; WaitEvent waitEvent; WorkerThread workerThread; - EXPECT_TRUE(!workerThread.getMoniker().empty()); for (int i = 1; i <= 10; i++) { waitEvent.reset(); diff --git a/AVSCommon/Utils/test/acsdk/Test/GmockExtensions.h b/AVSCommon/Utils/test/acsdk/Test/GmockExtensions.h new file mode 100644 index 0000000000..652eb80cd0 --- /dev/null +++ b/AVSCommon/Utils/test/acsdk/Test/GmockExtensions.h @@ -0,0 +1,361 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_ACSDK_TEST_GMOCKEXTENSIONS_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_ACSDK_TEST_GMOCKEXTENSIONS_H_ + +#include + +/** + * @brief Internal macro for constructing method name. + * + * Macro constructs method name in a manner similar to GMOCK_MOCKER_. + * + * @param arity Parameter count. + * @param constness Optional @a const specifier. + * @param Method Method name. + * + * @return Method name. + */ +#define GMOCK_NOEXCEPT_MOCKER_(arity, constness, Method) \ + GTEST_CONCAT_TOKEN_(gmock##constness##noexcept##arity##_##Method##_, __LINE__) + +/** + * @{ + * @brief Internal macro for declaring and defining mock method. + * + * Macro declares and defines mock method with @a noexcept specifier. Otherwise it works similarly to macros + * GMOCK_METHOD0_ ... GMOCK_METHOD10_. + * + * @param tn Number of arguments. + * @param constness Optional @a const specifier. + * @param Method Method name. + */ +#define GMOCK_NOEXCEPT_METHOD0_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method() constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 0), \ + this_method_does_not_take_0_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(0, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(0, constness, Method).Invoke(); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method() constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(0, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(0, constness, Method).With(); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(0, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD1_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method(GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 1), \ + this_method_does_not_take_1_argument); \ + GMOCK_NOEXCEPT_MOCKER_(1, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(1, constness, Method).Invoke(gmock_a1); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(1, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(1, constness, Method).With(gmock_a1); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(1, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD2_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) \ + ct Method(GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 2), \ + this_method_does_not_take_2_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(2, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(2, constness, Method).Invoke(gmock_a1, gmock_a2); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method( \ + GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(2, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(2, constness, Method).With(gmock_a1, gmock_a2); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(2, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD3_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) \ + ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 3), \ + this_method_does_not_take_3_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(3, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(3, constness, Method).Invoke(gmock_a1, gmock_a2, gmock_a3); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method( \ + GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(3, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(3, constness, Method).With(gmock_a1, gmock_a2, gmock_a3); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(3, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD4_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) \ + ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 4), \ + this_method_does_not_take_4_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(4, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(4, constness, Method).Invoke(gmock_a1, gmock_a2, gmock_a3, gmock_a4); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method( \ + GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(4, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(4, constness, Method).With(gmock_a1, gmock_a2, gmock_a3, gmock_a4); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(4, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD5_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) \ + ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 5), \ + this_method_does_not_take_5_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(5, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(5, constness, Method).Invoke(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method( \ + GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(5, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(5, constness, Method).With(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(5, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD6_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) \ + ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 6), \ + this_method_does_not_take_6_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(6, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(6, constness, Method) \ + .Invoke(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method( \ + GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(6, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(6, constness, Method) \ + .With(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(6, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD7_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) \ + ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 7), \ + this_method_does_not_take_7_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(7, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(7, constness, Method) \ + .Invoke(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method( \ + GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(7, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(7, constness, Method) \ + .With(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(7, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD8_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) \ + ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_ARG_(tn, 8, __VA_ARGS__) gmock_a8) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 8), \ + this_method_does_not_take_8_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(8, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(8, constness, Method) \ + .Invoke(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method( \ + GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_MATCHER_(tn, 8, __VA_ARGS__) gmock_a8) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(8, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(8, constness, Method) \ + .With(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(8, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD9_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) \ + ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_ARG_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_ARG_(tn, 9, __VA_ARGS__) gmock_a9) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 9), \ + this_method_does_not_take_9_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(9, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(9, constness, Method) \ + .Invoke(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method( \ + GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_MATCHER_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_MATCHER_(tn, 9, __VA_ARGS__) gmock_a9) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(9, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(9, constness, Method) \ + .With(gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(9, constness, Method) + +#define GMOCK_NOEXCEPT_METHOD10_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) \ + ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_ARG_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_ARG_(tn, 9, __VA_ARGS__) gmock_a9, \ + GMOCK_ARG_(tn, 10, __VA_ARGS__) gmock_a10) constness noexcept { \ + GTEST_COMPILE_ASSERT_( \ + (::testing::tuple_size::ArgumentTuple>::value == 10), \ + this_method_does_not_take_10_arguments); \ + GMOCK_NOEXCEPT_MOCKER_(10, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_NOEXCEPT_MOCKER_(10, constness, Method) \ + .Invoke( \ + gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9, gmock_a10); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& gmock_##Method( \ + GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_MATCHER_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_MATCHER_(tn, 9, __VA_ARGS__) gmock_a9, \ + GMOCK_MATCHER_(tn, 10, __VA_ARGS__) gmock_a10) constness noexcept { \ + GMOCK_NOEXCEPT_MOCKER_(10, constness, Method).RegisterOwner(this); \ + return GMOCK_NOEXCEPT_MOCKER_(10, constness, Method) \ + .With( \ + gmock_a1, gmock_a2, gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9, gmock_a10); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_NOEXCEPT_MOCKER_(10, constness, Method) +/// @} + +/** + * @{ + * @brief Macro to mock method with @a noexcept specifier. + */ +#define MOCK_NOEXCEPT_METHOD0(m, ...) GMOCK_NOEXCEPT_METHOD0_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD1(m, ...) GMOCK_NOEXCEPT_METHOD1_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD2(m, ...) GMOCK_NOEXCEPT_METHOD2_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD3(m, ...) GMOCK_NOEXCEPT_METHOD3_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD4(m, ...) GMOCK_NOEXCEPT_METHOD4_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD5(m, ...) GMOCK_NOEXCEPT_METHOD5_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD6(m, ...) GMOCK_NOEXCEPT_METHOD6_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD7(m, ...) GMOCK_NOEXCEPT_METHOD7_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD8(m, ...) GMOCK_NOEXCEPT_METHOD8_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD9(m, ...) GMOCK_NOEXCEPT_METHOD9_(, , , m, __VA_ARGS__) +#define MOCK_NOEXCEPT_METHOD10(m, ...) GMOCK_NOEXCEPT_METHOD10_(, , , m, __VA_ARGS__) +/// @} + +/** + * @{ + * @brief Macro to mock method with @a const @a noexcept specifier. + */ +#define MOCK_CONST_NOEXCEPT_METHOD0(m, ...) GMOCK_NOEXCEPT_METHOD0_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD1(m, ...) GMOCK_NOEXCEPT_METHOD1_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD2(m, ...) GMOCK_NOEXCEPT_METHOD2_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD3(m, ...) GMOCK_NOEXCEPT_METHOD3_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD4(m, ...) GMOCK_NOEXCEPT_METHOD4_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD5(m, ...) GMOCK_NOEXCEPT_METHOD5_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD6(m, ...) GMOCK_NOEXCEPT_METHOD6_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD7(m, ...) GMOCK_NOEXCEPT_METHOD7_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD8(m, ...) GMOCK_NOEXCEPT_METHOD8_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD9(m, ...) GMOCK_NOEXCEPT_METHOD9_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_NOEXCEPT_METHOD10(m, ...) GMOCK_NOEXCEPT_METHOD10_(, const, , m, __VA_ARGS__) +/// @} + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_UTILS_TEST_ACSDK_TEST_GMOCKEXTENSIONS_H_ diff --git a/AVSGatewayManager/include/AVSGatewayManager/AVSGatewayManager.h b/AVSGatewayManager/include/AVSGatewayManager/AVSGatewayManager.h index 2c1a60e84d..f895d59676 100644 --- a/AVSGatewayManager/include/AVSGatewayManager/AVSGatewayManager.h +++ b/AVSGatewayManager/include/AVSGatewayManager/AVSGatewayManager.h @@ -57,6 +57,7 @@ class AVSGatewayManager * @param customerDataManager The @c CustomerDataManager object that will track the CustomerDataHandler. * @param configurationRoot The @c ConfigurationNode to get AVS gateway information from the config file. * @param providerRegistrar Object with which to register the new instance as a post connect operation provider. + * @param metricRecorder Optional @c MetricRecorderInterface object for sending metrics. * @return A new instance of the @c AVSGatewayManager. */ static std::shared_ptr createAVSGatewayManagerInterface( @@ -66,7 +67,8 @@ class AVSGatewayManager const std::shared_ptr& configurationRoot, const std::shared_ptr< acsdkPostConnectOperationProviderRegistrarInterfaces::PostConnectOperationProviderRegistrarInterface>& - providerRegistrar); + providerRegistrar, + std::shared_ptr metricRecorder); /** * Creates an instance of the @c AVSGatewayManager. @@ -76,13 +78,15 @@ class AVSGatewayManager * @param customerDataManager The @c CustomerDataManager object that will track the CustomerDataHandler. * @param configurationRoot The @c ConfigurationNode to get AVS gateway information from the config file. * @param authDelegate The @c AuthDelegateInterface to add AuthObservers to take action once Auth state changes + * @param metricRecorder Optional @c MetricRecorderInterface object for sending metrics. * @return A new instance of the @c AVSGatewayManager. */ static std::shared_ptr create( std::shared_ptr avsGatewayManagerStorage, std::shared_ptr customerDataManager, const avsCommon::utils::configuration::ConfigurationNode& configurationRoot, - std::shared_ptr authDelegate = nullptr); + std::shared_ptr authDelegate = nullptr, + std::shared_ptr metricRecorder = nullptr); /// @name AVSGatewayManagerInterface Functions /// @{ @@ -124,12 +128,14 @@ class AVSGatewayManager * * @param avsGatewayManagerStorage The @c AVSGatewayManagerInterface to store avs gateway information. * @param customerDataManager The @c CustomerDataManager object that will track the CustomerDataHandler. + * @param metricRecorder Optional @c MetricRecorderInterface object for sending metrics. * @param defaultGateway The default AVS Gateway URL to use. */ AVSGatewayManager( std::shared_ptr avsGatewayManagerStorage, - const std::shared_ptr& customerDataManager, - const std::shared_ptr& authDelegate, + std::shared_ptr customerDataManager, + std::shared_ptr authDelegate, + std::shared_ptr metricRecorder, const std::string& defaultGateway); /** @@ -153,6 +159,9 @@ class AVSGatewayManager /// The AVS Gateway Assigner. std::shared_ptr m_avsGatewayAssigner; + /// Optional (may be nullptr) interface for recording metrics. + std::shared_ptr m_metricRecorder; + /// The mutex to synchronize access to members. mutable std::mutex m_mutex; diff --git a/AVSGatewayManager/include/AVSGatewayManager/PostConnectVerifyGatewaySender.h b/AVSGatewayManager/include/AVSGatewayManager/PostConnectVerifyGatewaySender.h index ac59d93915..90fa98285a 100644 --- a/AVSGatewayManager/include/AVSGatewayManager/PostConnectVerifyGatewaySender.h +++ b/AVSGatewayManager/include/AVSGatewayManager/PostConnectVerifyGatewaySender.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -39,10 +40,17 @@ class PostConnectVerifyGatewaySender * Creates a new instance of @c PostConnectVerifyGatewaySender. * * @param gatewayVerifiedCallback The callback method that should be called on successful gateway verification. + * @param metricRecorder Optional (may be nullptr) reference to metric recorder. * @return a new instance of the @c PostConnectVerifyGatewaySender. */ static std::shared_ptr create( - std::function&)> gatewayVerifiedCallback); + std::function&)> gatewayVerifiedCallback, + std::shared_ptr metricRecorder = nullptr); + + /** + * Destructor for tracking lifecycle. + */ + ~PostConnectVerifyGatewaySender(); /// @name PostConnectOperationInterface Methods /// @{ @@ -76,9 +84,11 @@ class PostConnectVerifyGatewaySender * Constructor. * * @param gatewayVerifiedCallback The callback method that should be called on successful gateway verification. + * @param metricRecorder Optional (may be nullptr) reference to metric recorder. */ explicit PostConnectVerifyGatewaySender( - std::function&)> gatewayVerifiedCallback); + std::function&)> gatewayVerifiedCallback, + std::shared_ptr metricRecorder); /** * The VerifyGateway operation which sends the @c ApiGateway.VerifyGateway event. @@ -103,6 +113,9 @@ class PostConnectVerifyGatewaySender /// The Callback function that will be called after successful response to @c VerifyGateway event. std::function&)> m_gatewayVerifiedCallback; + /// Optional (may be nullptr) interface for metrics. + std::shared_ptr m_metricRecorder; + /// Mutex to synchronize access to @c WaitableMessageRequest. std::mutex m_mutex; diff --git a/AVSGatewayManager/src/AVSGatewayManager.cpp b/AVSGatewayManager/src/AVSGatewayManager.cpp index 47ae25c535..441e58b4c2 100644 --- a/AVSGatewayManager/src/AVSGatewayManager.cpp +++ b/AVSGatewayManager/src/AVSGatewayManager.cpp @@ -28,7 +28,7 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::configuration; /// String to identify log entries originating from this file. -static const std::string TAG("AVSGatewayManager"); +#define TAG "AVSGatewayManager" /** * Create a LogEntry using the file's TAG and the specified event string. @@ -54,7 +54,8 @@ std::shared_ptr AVSGateway const std::shared_ptr& configurationRoot, const std::shared_ptr< acsdkPostConnectOperationProviderRegistrarInterfaces::PostConnectOperationProviderRegistrarInterface>& - providerRegistrar) { + providerRegistrar, + std::shared_ptr metricRecorder) { if (!configurationRoot) { ACSDK_ERROR(LX("createAVSGatewayManagerInterfaceFailed").d("reason", "nullConfigurationRoot")); return nullptr; @@ -67,8 +68,12 @@ std::shared_ptr AVSGateway ACSDK_ERROR(LX("createAVSGatewayManagerInterfaceFailed").d("reason", "nullAuthDelegater")); return nullptr; } - auto gatewayManager = - create(std::move(avsGatewayManagerStorage), customerDataManager, *configurationRoot, authDelegate); + auto gatewayManager = create( + std::move(avsGatewayManagerStorage), + customerDataManager, + *configurationRoot, + authDelegate, + std::move(metricRecorder)); if (!gatewayManager) { ACSDK_ERROR(LX("createAVSGatewayManagerInterfaceFailed").d("reason", "createFailed")); return nullptr; @@ -84,7 +89,8 @@ std::shared_ptr AVSGatewayManager::create( std::shared_ptr avsGatewayManagerStorage, std::shared_ptr customerDataManager, const ConfigurationNode& configurationRoot, - std::shared_ptr authDelegate) { + std::shared_ptr authDelegate, + std::shared_ptr metricRecorder) { ACSDK_DEBUG5(LX(__func__)); if (!avsGatewayManagerStorage) { ACSDK_ERROR(LX("createFailed").d("reason", "nullAvsGatewayManagerStorage")); @@ -102,8 +108,12 @@ std::shared_ptr AVSGatewayManager::create( avsGateway = DEFAULT_AVS_GATEWAY; } - auto avsGatewayManager = std::shared_ptr( - new AVSGatewayManager(avsGatewayManagerStorage, customerDataManager, authDelegate, avsGateway)); + auto avsGatewayManager = std::shared_ptr(new AVSGatewayManager( + std::move(avsGatewayManagerStorage), + std::move(customerDataManager), + std::move(authDelegate), + std::move(metricRecorder), + avsGateway)); if (avsGatewayManager->init()) { return avsGatewayManager; } else { @@ -115,11 +125,13 @@ std::shared_ptr AVSGatewayManager::create( AVSGatewayManager::AVSGatewayManager( std::shared_ptr avsGatewayManagerStorage, - const std::shared_ptr& customerDataManager, - const std::shared_ptr& authDelegate, + std::shared_ptr customerDataManager, + std::shared_ptr authDelegate, + std::shared_ptr metricRecorder, const std::string& defaultAVSGateway) : CustomerDataHandler{std::move(customerDataManager)}, m_avsGatewayStorage{std::move(avsGatewayManagerStorage)}, + m_metricRecorder{std::move(metricRecorder)}, m_authDelegate{std::move(authDelegate)}, m_currentState{defaultAVSGateway, false} { } @@ -137,7 +149,7 @@ std::shared_ptr AVSGatewayManager::createPostConn if (!m_currentState.isVerified) { auto callback = std::bind(&AVSGatewayManager::onGatewayVerified, this, std::placeholders::_1); std::shared_ptr verifyGatewaySender = - PostConnectVerifyGatewaySender::create(callback); + PostConnectVerifyGatewaySender::create(callback, m_metricRecorder); m_currentVerifyGatewaySender = verifyGatewaySender; if (m_authDelegate) { diff --git a/AVSGatewayManager/src/PostConnectVerifyGatewaySender.cpp b/AVSGatewayManager/src/PostConnectVerifyGatewaySender.cpp index b481b9888a..44dae2190e 100644 --- a/AVSGatewayManager/src/PostConnectVerifyGatewaySender.cpp +++ b/AVSGatewayManager/src/PostConnectVerifyGatewaySender.cpp @@ -19,6 +19,8 @@ #include #include +#include +#include #include namespace alexaClientSDK { @@ -26,9 +28,17 @@ namespace avsGatewayManager { using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils::metrics; /// String to identify log entries originating from this file. -static const std::string TAG("PostConnectVerifyGatewaySender"); +#define TAG "PostConnectVerifyGatewaySender" + +/// Activity name for post-connect metric. +/// This name is dependent on TAG value. +#define POST_CONNECT_ACTIVITY_NAME TAG "-sendVerifyGateway" + +/// Prefix for post-connect data point with status value. +#define POST_CONNECT_STATUS_PREFIX "STATUS-" /** * Create a LogEntry using the file's TAG and the specified event string. @@ -60,12 +70,13 @@ static avsCommon::utils::RetryTimer RETRY_TIMER{RETRY_TABLE}; std::shared_ptr PostConnectVerifyGatewaySender::create( std::function& verifyGatewaySender)> - gatewayVerifiedCallback) { + gatewayVerifiedCallback, + std::shared_ptr metricRecorder) { if (!gatewayVerifiedCallback) { ACSDK_ERROR(LX("createFailed").d("reason", "invalid gatewayVerifiedCallback")); } else { return std::shared_ptr( - new PostConnectVerifyGatewaySender(gatewayVerifiedCallback)); + new PostConnectVerifyGatewaySender(gatewayVerifiedCallback, metricRecorder)); } return nullptr; @@ -73,9 +84,16 @@ std::shared_ptr PostConnectVerifyGatewaySender:: PostConnectVerifyGatewaySender::PostConnectVerifyGatewaySender( std::function& verifyGatewaySender)> - gatewayVerifiedCallback) : - m_gatewayVerifiedCallback{gatewayVerifiedCallback}, + gatewayVerifiedCallback, + std::shared_ptr metricRecorder) : + m_gatewayVerifiedCallback{std::move(gatewayVerifiedCallback)}, + m_metricRecorder{std::move(metricRecorder)}, m_isStopping{false} { + ACSDK_INFO(LX("init").p("this", this)); +} + +PostConnectVerifyGatewaySender::~PostConnectVerifyGatewaySender() { + ACSDK_INFO(LX("destroyed").p("this", this)); } unsigned int PostConnectVerifyGatewaySender::getOperationPriority() { @@ -83,7 +101,7 @@ unsigned int PostConnectVerifyGatewaySender::getOperationPriority() { } bool PostConnectVerifyGatewaySender::performOperation(const std::shared_ptr& messageSender) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_INFO(LX(__func__)); if (!messageSender) { ACSDK_ERROR(LX("performOperationFailed").d("reason", "nullPostConnectSender")); return false; @@ -126,7 +144,7 @@ void PostConnectVerifyGatewaySender::wakeOperation() { } void PostConnectVerifyGatewaySender::abortOperation() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_INFO(LX(__func__)); std::shared_ptr requestCopy; { std::lock_guard lock{m_mutex}; @@ -157,6 +175,21 @@ PostConnectVerifyGatewaySender::VerifyGatewayReturnCode PostConnectVerifyGateway /// Wait for the response. auto status = m_postConnectRequest->waitForCompletion(); +#ifdef ACSDK_ENABLE_METRICS_RECORDING + if (m_metricRecorder) { + std::stringstream eventNameBuilder; + eventNameBuilder << POST_CONNECT_STATUS_PREFIX << status; + auto metricEvent = + MetricEventBuilder{} + .setActivityName(POST_CONNECT_ACTIVITY_NAME) + .addDataPoint(DataPointCounterBuilder{}.setName(eventNameBuilder.str()).increment(1).build()) + .build(); + if (metricEvent) { + m_metricRecorder->recordMetric(std::move(metricEvent)); + } + } +#endif + switch (status) { /// 200 Response with a set gateway directive. case MessageRequestObserverInterface::Status::SUCCESS: diff --git a/AVSGatewayManager/src/Storage/AVSGatewayManagerStorage.cpp b/AVSGatewayManager/src/Storage/AVSGatewayManagerStorage.cpp index e1331e3527..81b7e758b4 100644 --- a/AVSGatewayManager/src/Storage/AVSGatewayManagerStorage.cpp +++ b/AVSGatewayManager/src/Storage/AVSGatewayManagerStorage.cpp @@ -27,7 +27,7 @@ using namespace avsCommon::sdkInterfaces::storage; using namespace avsCommon::utils::json; /// String to identify log entries originating from this file. -static const std::string TAG("AVSGatewayManagerStorage"); +#define TAG "AVSGatewayManagerStorage" /** * Create a LogEntry using the file's TAG and the specified event string. diff --git a/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/PlatformSpecificValues.h b/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/PlatformSpecificValues.h deleted file mode 100644 index 1538ed67bf..0000000000 --- a/ApplicationUtilities/AndroidUtilities/include/AndroidUtilities/PlatformSpecificValues.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_PLATFORMSPECIFICVALUES_H_ -#define ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_PLATFORMSPECIFICVALUES_H_ - -#include - -#include "AndroidUtilities/AndroidSLESEngine.h" - -namespace alexaClientSDK { -namespace applicationUtilities { -namespace androidUtilities { - -struct PlatformSpecificValues { - std::shared_ptr openSlEngine; -}; - -} // namespace androidUtilities -} // namespace applicationUtilities -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_ANDROIDUTILITIES_INCLUDE_ANDROIDUTILITIES_PLATFORMSPECIFICVALUES_H_ diff --git a/ApplicationUtilities/AndroidUtilities/src/AndroidSLESBufferQueue.cpp b/ApplicationUtilities/AndroidUtilities/src/AndroidSLESBufferQueue.cpp index 4c8b4f4448..eef4e02a77 100644 --- a/ApplicationUtilities/AndroidUtilities/src/AndroidSLESBufferQueue.cpp +++ b/ApplicationUtilities/AndroidUtilities/src/AndroidSLESBufferQueue.cpp @@ -17,7 +17,7 @@ #include /// String to identify log entries originating from this file. -static const std::string TAG{"AndroidSLESBufferQueue"}; +#define TAG "AndroidSLESBufferQueue" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ApplicationUtilities/AndroidUtilities/src/AndroidSLESEngine.cpp b/ApplicationUtilities/AndroidUtilities/src/AndroidSLESEngine.cpp index 401e2103fa..778f26472c 100644 --- a/ApplicationUtilities/AndroidUtilities/src/AndroidSLESEngine.cpp +++ b/ApplicationUtilities/AndroidUtilities/src/AndroidSLESEngine.cpp @@ -22,7 +22,7 @@ #include /// String to identify log entries originating from this file. -static const std::string TAG{"AndroidSLESEngine"}; +#define TAG "AndroidSLESEngine" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ApplicationUtilities/AndroidUtilities/src/AndroidSLESMicrophone.cpp b/ApplicationUtilities/AndroidUtilities/src/AndroidSLESMicrophone.cpp index f4a263cd5c..b90d4a7163 100644 --- a/ApplicationUtilities/AndroidUtilities/src/AndroidSLESMicrophone.cpp +++ b/ApplicationUtilities/AndroidUtilities/src/AndroidSLESMicrophone.cpp @@ -19,7 +19,7 @@ #include /// String to identify log entries originating from this file. -static const std::string TAG{"AndroidMicrophone"}; +#define TAG "AndroidMicrophone" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ApplicationUtilities/AndroidUtilities/src/AndroidSLESObject.cpp b/ApplicationUtilities/AndroidUtilities/src/AndroidSLESObject.cpp index eaa73e4b18..962949c586 100644 --- a/ApplicationUtilities/AndroidUtilities/src/AndroidSLESObject.cpp +++ b/ApplicationUtilities/AndroidUtilities/src/AndroidSLESObject.cpp @@ -20,7 +20,7 @@ namespace applicationUtilities { namespace androidUtilities { /// The tag associated with log entries from this class. -static const std::string TAG{"AndroidSLESObject"}; +#define TAG "AndroidSLESObject" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h index 2569e2d653..6d4f8cb365 100644 --- a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h +++ b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClient.h @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -60,8 +61,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -94,16 +94,17 @@ #include #ifdef ENABLE_PCC -#include -#include +#include +#include #endif #ifdef ENABLE_MCC -#include -#include -#include +#include +#include +#include #endif +#include #include #include #include @@ -115,13 +116,11 @@ #include #include #include -#include -#include -#include +#include +#include #include #include #include -#include #ifdef ENABLE_REVOKE_AUTH #include @@ -138,7 +137,9 @@ namespace defaultClient { * This class serves to instantiate each default component with of the SDK with no specializations to provide an * "out-of-box" component that users may utilize for AVS interaction. */ -class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerInterface { +class DefaultClient + : public avsCommon::sdkInterfaces::SpeechInteractionHandlerInterface + , public sdkClient::FeatureClientInterface { public: using DefaultClientSubsetManufactory = acsdkManufactory::Manufactory< std::shared_ptr, @@ -168,8 +169,6 @@ class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerI std::shared_ptr, acsdkManufactory:: Annotated, - acsdkManufactory:: - Annotated, std::shared_ptr, std::shared_ptr, std::shared_ptr, @@ -197,7 +196,7 @@ class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerI std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr, + std::shared_ptr, std::shared_ptr>; using DefaultClientManufactory = acsdkManufactory::Manufactory< @@ -228,8 +227,6 @@ class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerI std::shared_ptr, acsdkManufactory:: Annotated, - acsdkManufactory:: - Annotated, std::shared_ptr, std::shared_ptr, std::shared_ptr, @@ -257,7 +254,7 @@ class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerI std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr, + std::shared_ptr, std::shared_ptr>; /** @@ -293,6 +290,7 @@ class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerI * @param externalCapabilitiesBuilder Optional object used to build capabilities that are not included in the SDK. * @param firstInteractionAudioProvider Optional object used in the first interaction started from * the alexa voice service + * @param sdkClientRegistry Optional object used when the @c SDKClientBuilder is used to construct DefaultClient * @return A @c std::unique_ptr to a DefaultClient if all went well or @c nullptr otherwise. */ static std::unique_ptr create( @@ -304,12 +302,12 @@ class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerI std::shared_ptr> additionalSpeakers, #ifdef ENABLE_PCC std::shared_ptr phoneSpeaker, - std::shared_ptr phoneCaller, + std::shared_ptr phoneCaller, #endif #ifdef ENABLE_MCC std::shared_ptr meetingSpeaker, - std::shared_ptr meetingClient, - std::shared_ptr calendarClient, + std::shared_ptr meetingClient, + std::shared_ptr calendarClient, #endif #ifdef ENABLE_COMMS_AUDIO_PROXY std::shared_ptr commsMediaPlayer, @@ -329,7 +327,8 @@ class DefaultClient : public avsCommon::sdkInterfaces::SpeechInteractionHandlerI std::shared_ptr diagnostics = nullptr, const std::shared_ptr& externalCapabilitiesBuilder = nullptr, capabilityAgents::aip::AudioProvider firstInteractionAudioProvider = - capabilityAgents::aip::AudioProvider::null()); + capabilityAgents::aip::AudioProvider::null(), + const std::shared_ptr& sdkClientRegistry = nullptr); /** * Creates and initializes a default AVS SDK client. To connect the client to AVS, users should make a call to @@ -405,6 +404,8 @@ of the @c ExpectSpeech directive's timeout. If provided, this function must rema AudioInputProcessor. * @param firstInteractionAudioProvider Optional object used in the first interaction started from * the alexa voice service + * @param cryptoFactory Optional Encryption facilities factory. + * @param sdkClientRegistry Optional object used when the @c SDKClientBuilder is used to construct DefaultClient * @return A @c std::unique_ptr to a DefaultClient if all went well or @c nullptr otherwise. */ static std::unique_ptr create( @@ -434,12 +435,12 @@ AudioInputProcessor. std::shared_ptr> additionalSpeakers, #ifdef ENABLE_PCC std::shared_ptr phoneSpeaker, - std::shared_ptr phoneCaller, + std::shared_ptr phoneCaller, #endif #ifdef ENABLE_MCC std::shared_ptr meetingSpeaker, - std::shared_ptr meetingClient, - std::shared_ptr calendarClient, + std::shared_ptr meetingClient, + std::shared_ptr calendarClient, #endif #ifdef ENABLE_COMMS_AUDIO_PROXY std::shared_ptr commsMediaPlayer, @@ -482,14 +483,22 @@ AudioInputProcessor. std::shared_ptr diagnostics = nullptr, const std::shared_ptr& externalCapabilitiesBuilder = nullptr, std::shared_ptr channelVolumeFactory = - std::make_shared(), + speakerManager::createChannelVolumeFactory(), bool startAlertSchedulingOnInitialization = true, std::shared_ptr messageRouterFactory = std::make_shared(), const std::shared_ptr& expectSpeechTimeoutHandler = nullptr, capabilityAgents::aip::AudioProvider firstInteractionAudioProvider = - capabilityAgents::aip::AudioProvider::null()); + capabilityAgents::aip::AudioProvider::null(), + const std::shared_ptr& cryptoFactory = nullptr, + const std::shared_ptr& sdkClientRegistry = nullptr); + + /// @c FeatureClientInterface functions + /// @{ + bool configure(const std::shared_ptr& sdkClientRegistry) override; + void doShutdown() override; + /// @} /** * Connects the client to AVS. After this call, users can observe the state of the connection asynchronously by @@ -633,7 +642,7 @@ AudioInputProcessor. * @param observer The observer to add. */ void addTemplateRuntimeObserver( - std::shared_ptr observer); + std::shared_ptr observer); /** * Removes an observer to be notified when a TemplateRuntime directive is received. @@ -641,12 +650,7 @@ AudioInputProcessor. * @param observer The observer to remove. */ void removeTemplateRuntimeObserver( - std::shared_ptr observer); - - /** - * Notify the TemplateRuntime Capability Agent that the display card is cleared from the screen. - */ - void TemplateRuntimeDisplayCardCleared(); + std::shared_ptr observer); /** * Adds an observer to be notified of IndicatorState changes. @@ -1094,7 +1098,29 @@ AudioInputProcessor. */ std::shared_ptr getBluetoothLocal(); + /** + * Stops any ongoing interaction with the SDK by resetting the state of the @c AudioInputProcessor. + * + * This method is intended for use when a device needs to stop the current user interaction with alexa, for example + * when the audio input state needs to be returned to idle as a result of an event such as a back or exit button + * press. Calling this method has no effect on ongoing Alexa speech, audio playback or visual state. + */ + void stopInteraction(); + + /** + * Get a reference to the audio focus manager + * + * @return shared_ptr to the audio @c FocusManagerInterface, callers should perform a nullptr check as the returned + * pointer may be null + */ + std::shared_ptr getAudioFocusManager(); + private: + /** + * Constructor + */ + DefaultClient(); + /** * Initializes the SDK and "glues" all the components together. * @@ -1114,6 +1140,7 @@ AudioInputProcessor. * @param externalCapabilitiesBuilder Object used to build capabilities that are not included in the SDK. * @param firstInteractionAudioProvider Optional object used in the first interaction started from * the alexa voice service + * @param sdkClientRegistry Optional object used when the @c SDKClientBuilder is used to construct DefaultClient * @return Whether the SDK was initialized properly. */ bool initialize( @@ -1125,12 +1152,12 @@ AudioInputProcessor. std::shared_ptr> additionalSpeakers, #ifdef ENABLE_PCC std::shared_ptr phoneSpeaker, - std::shared_ptr phoneCaller, + std::shared_ptr phoneCaller, #endif #ifdef ENABLE_MCC std::shared_ptr meetingSpeaker, - std::shared_ptr meetingClient, - std::shared_ptr calendarClient, + std::shared_ptr meetingClient, + std::shared_ptr calendarClient, #endif #ifdef ENABLE_COMMS_AUDIO_PROXY std::shared_ptr commsMediaPlayer, @@ -1147,7 +1174,8 @@ AudioInputProcessor. std::shared_ptr softwareInfoSenderObserver, std::shared_ptr diagnostics, const std::shared_ptr& externalCapabilitiesBuilder, - capabilityAgents::aip::AudioProvider firstInteractionAudioProvider); + capabilityAgents::aip::AudioProvider firstInteractionAudioProvider, + const std::shared_ptr& sdkClientRegistry = nullptr); /// The directive sequencer. std::shared_ptr m_directiveSequencer; @@ -1155,12 +1183,10 @@ AudioInputProcessor. /// The focus manager for audio channels. std::shared_ptr m_audioFocusManager; - /// The focus manager for visual channels. - std::shared_ptr m_visualFocusManager; - /// The connection manager. std::shared_ptr m_connectionManager; + /// The internet connection monitor. std::shared_ptr m_internetConnectionMonitor; /// The captions manager. @@ -1207,13 +1233,12 @@ AudioInputProcessor. #ifdef ENABLE_PCC /// The phoneCallController capability agent. - std::shared_ptr m_phoneCallControllerCapabilityAgent; + std::shared_ptr m_phoneCallControllerCapabilityAgent; #endif #ifdef ENABLE_MCC /// The MeetingClientController capability agent. - std::shared_ptr - m_meetingClientControllerCapabilityAgent; + std::shared_ptr m_meetingClientControllerCapabilityAgent; #endif /// The call manager capability agent. @@ -1229,7 +1254,7 @@ AudioInputProcessor. std::shared_ptr m_speakerManager; /// The TemplateRuntime capability agent. - std::shared_ptr m_templateRuntime; + std::shared_ptr m_templateRuntime; /// The Equalizer capability agent. std::shared_ptr m_equalizerCapabilityAgent; diff --git a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientBuilder.h b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientBuilder.h new file mode 100644 index 0000000000..36a7cde41a --- /dev/null +++ b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientBuilder.h @@ -0,0 +1,473 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_DEFAULTCLIENT_INCLUDE_DEFAULTCLIENT_DEFAULTCLIENTBUILDER_H_ +#define ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_DEFAULTCLIENT_INCLUDE_DEFAULTCLIENT_DEFAULTCLIENTBUILDER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_PCC +#include +#include +#endif + +#ifdef ENABLE_MCC +#include +#include +#include +#endif + +#include "DefaultClient/EqualizerRuntimeSetup.h" +#include "DefaultClient/ExternalCapabilitiesBuilderInterface.h" + +#include "DefaultClient.h" + +namespace alexaClientSDK { +namespace defaultClient { +/** + * The DefaultClientBuilder is used to construct DefaultClient when used with the ClientBuilder + */ +class DefaultClientBuilder : public sdkClient::FeatureClientBuilderInterface { +public: + /** + * Creates and initializes a default AVS SDK client. To connect the client to AVS, users should make a call to + * connect() after creation. + * + * @param deviceInfo DeviceInfo which reflects the device setup credentials. + * @param customerDataManager CustomerDataManager instance to be used by RegistrationManager and instances of + * all classes extending CustomDataHandler. + * @param externalMusicProviderMediaPlayers The map of to use to play content from each + * external music provider. + * @param externalMusicProviderSpeakers The map of to use to track volume of each + * external music provider media player. + * @param adapterCreationMap The map of to use when creating the adapters for the + * different music providers supported by ExternalMediaPlayer. + * @param speakMediaPlayer The media player to use to play Alexa speech from. + * @param audioMediaPlayerFactory The media player factory to use to generate players for Alexa audio content. + * @param alertsMediaPlayer The media player to use to play alerts from. + * @param notificationsMediaPlayer The media player to play notification indicators. + * @param bluetoothMediaPlayer The media player to play bluetooth content. + * @param ringtoneMediaPlayer The media player to play Comms ringtones. + * @param systemSoundMediaPlayer The media player to play system sounds. + * @param speakSpeaker The speaker to control volume of Alexa speech. + * @param audioSpeakers A list of speakers to control volume of Alexa audio content. + * @param alertsSpeaker The speaker to control volume of alerts. + * @param notificationsSpeaker The speaker to control volume of notifications. + * @param bluetoothSpeaker The speaker to control volume of bluetooth. + * @param ringtoneSpeaker The speaker to control volume of Comms ringtones. + * @param systemSoundSpeaker The speaker to control volume of system sounds. + * @param additionalSpeakers A map of additional speakers to receive volume changes. +#ifdef ENABLE_COMMS_AUDIO_PROXY + * @param commsMediaPlayer The media player to play Comms calling audio. + * @param commsSpeaker The speaker to control volume of Comms calling audio. + * @param sharedDataStream The stream to use which has the audio from microphone. +#endif + * @param equalizerRuntimeSetup Equalizer component runtime setup + * @param audioFactory The audioFactory is a component that provides unique audio streams. + * @param authDelegate The component that provides the client with valid LWA authorization. + * @param alertStorage The storage interface that will be used to store alerts. + * @param messageStorage The storage interface that will be used to store certified sender messages. + * @param notificationsStorage The storage interface that will be used to store notification indicators. + * @param deviceSettingStorage The storage interface that will be used to store device settings. + * @param bluetoothStorage The storage interface that will be used to store bluetooth data. + * @param miscStorage The storage interface that will be used to store key / value pairs. + * @param alexaDialogStateObservers Observers that can be used to be notified of Alexa dialog related UX state + * changes. + * @param connectionObservers Observers that can be used to be notified of connection status changes. + * @param isGuiSupported Whether the device supports GUI. + * @param capabilitiesDelegate The component that provides the client with the ability to send messages to the + * Capabilities API. + * @param contextManager The @c ContextManager which will provide the context for various components. + * @param transportFactory The object passed in here will be used whenever a new transport object + * for AVS communication is needed. + * @param avsGatewayManager The @c AVSGatewayManager instance used to create the ApiGateway CA. + * @param localeAssetsManager The device locale assets manager. + * @param enabledConnectionRules The set of @c BluetoothDeviceConnectionRuleInterface instances used to + * create the Bluetooth CA. + * @param systemTimezone Optional object used to set the system timezone. + * @param firmwareVersion The firmware version to report to @c AVS or @c INVALID_FIRMWARE_VERSION. + * @param sendSoftwareInfoOnConnected Whether to send SoftwareInfo upon connecting to @c AVS. + * @param softwareInfoSenderObserver Object to receive notifications about sending SoftwareInfo. + * @param bluetoothDeviceManager The @c BluetoothDeviceManager instance used to create the Bluetooth CA. + * @param metricRecorder The metric recorder object used to capture metrics. + * @param powerResourceManager Object to manage power resource. + * @param diagnostics Diagnostics interface which provides suite of APIs for diagnostic insight into SDK. + * @param externalCapabilitiesBuilder Optional object used to build capabilities that are not included in the SDK. + * @param channelVolumeFactory Optional object used to build @c ChannelVolumeInterface in the SDK. + * @param startAlertSchedulingOnInitialization Whether to start scheduling alerts after client initialization. If + * this is set to false, no alert scheduling will occur until onSystemClockSynchronized is called. + * @param messageRouterFactory Object used to instantiate @c MessageRouter in the SDK. + * @param expectSpeechTimeoutHandler An optional object that applications may provide to specify external handling +of the @c ExpectSpeech directive's timeout. If provided, this function must remain valid for the lifetime of the @c +AudioInputProcessor. + * @param firstInteractionAudioProvider Optional object used in the first interaction started from + * the alexa voice service + * @param cryptoFactory Optional Encryption facilities factory. + * @return A @c std::unique_ptr to a DefaultClient if all went well or @c nullptr otherwise. + */ + static std::unique_ptr create( + std::shared_ptr deviceInfo, + std::shared_ptr customerDataManager, + std::unordered_map> + externalMusicProviderMediaPlayers, + std::unordered_map> + externalMusicProviderSpeakers, + acsdkExternalMediaPlayer::ExternalMediaPlayer::AdapterCreationMap adapterCreationMap, + std::shared_ptr speakMediaPlayer, + std::unique_ptr audioMediaPlayerFactory, + std::shared_ptr alertsMediaPlayer, + std::shared_ptr notificationsMediaPlayer, + std::shared_ptr bluetoothMediaPlayer, + std::shared_ptr ringtoneMediaPlayer, + std::shared_ptr systemSoundMediaPlayer, + std::shared_ptr speakSpeaker, + std::vector> audioSpeakers, + std::shared_ptr alertsSpeaker, + std::shared_ptr notificationsSpeaker, + std::shared_ptr bluetoothSpeaker, + std::shared_ptr ringtoneSpeaker, + std::shared_ptr systemSoundSpeaker, + std::multimap< + avsCommon::sdkInterfaces::ChannelVolumeInterface::Type, + std::shared_ptr> additionalSpeakers, +#ifdef ENABLE_PCC + std::shared_ptr phoneSpeaker, + std::shared_ptr phoneCaller, +#endif +#ifdef ENABLE_MCC + std::shared_ptr meetingSpeaker, + std::shared_ptr meetingClient, + std::shared_ptr calendarClient, +#endif +#ifdef ENABLE_COMMS_AUDIO_PROXY + std::shared_ptr commsMediaPlayer, + std::shared_ptr commsSpeaker, + std::shared_ptr sharedDataStream, +#endif + std::shared_ptr equalizerRuntimeSetup, + std::shared_ptr audioFactory, + std::shared_ptr authDelegate, + std::shared_ptr alertStorage, + std::shared_ptr messageStorage, + std::shared_ptr notificationsStorage, + std::shared_ptr deviceSettingStorage, + std::shared_ptr bluetoothStorage, + std::shared_ptr miscStorage, + std::unordered_set> + alexaDialogStateObservers, + std::unordered_set> + connectionObservers, + std::shared_ptr internetConnectionMonitor, + bool isGuiSupported, + std::shared_ptr capabilitiesDelegate, + std::shared_ptr contextManager, + std::shared_ptr transportFactory, + std::shared_ptr avsGatewayManager, + std::shared_ptr localeAssetsManager, + std::unordered_set> + enabledConnectionRules = std::unordered_set< + std::shared_ptr>(), + std::shared_ptr systemTimezone = nullptr, + avsCommon::sdkInterfaces::softwareInfo::FirmwareVersion firmwareVersion = + avsCommon::sdkInterfaces::softwareInfo::INVALID_FIRMWARE_VERSION, + bool sendSoftwareInfoOnConnected = false, + std::shared_ptr softwareInfoSenderObserver = + nullptr, + std::unique_ptr bluetoothDeviceManager = + nullptr, + std::shared_ptr metricRecorder = nullptr, + std::shared_ptr powerResourceManager = nullptr, + std::shared_ptr diagnostics = nullptr, + std::shared_ptr externalCapabilitiesBuilder = nullptr, + std::shared_ptr channelVolumeFactory = + speakerManager::createChannelVolumeFactory(), + bool startAlertSchedulingOnInitialization = true, + std::shared_ptr messageRouterFactory = + std::make_shared(), + std::shared_ptr expectSpeechTimeoutHandler = + nullptr, + capabilityAgents::aip::AudioProvider firstInteractionAudioProvider = + capabilityAgents::aip::AudioProvider::null(), + std::shared_ptr cryptoFactory = nullptr); + + /** + * Function used by SDKClientBuilder to construct an instance of DefaultClient + * @param sdkClientRegistry The @c SDKClientRegistry object + * @return An instance of the @c DefaultClient, or nullptr if creation failed + */ + std::shared_ptr construct(const std::shared_ptr& sdkClientRegistry); + + /// @c FeatureClientBuilderInterface functions + /// @{ + std::string name() override; + /// @} + +private: + /// Constructor + DefaultClientBuilder(); + + /// Flag indicating whether the @c construct method has previously been called on this instance + bool m_constructed; + + /// DeviceInfo which reflects the device setup credentials. + std::shared_ptr m_deviceInfo; + + /// CustomerDataManager instance to be used by RegistrationManager and instances of all classes extending + /// CustomDataHandler. + std::shared_ptr m_customerDataManager; + + /// The map of to use to play content from each external music provider. + std:: + unordered_map> + m_externalMusicProviderMediaPlayers; + + /// The map of to use to track volume of each external music provider media player. + std::unordered_map> + m_externalMusicProviderSpeakers; + + /// The map of to use when creating the adapters for the different music providers + /// supported by ExternalMediaPlayer. + alexaClientSDK::acsdkExternalMediaPlayer::ExternalMediaPlayer::AdapterCreationMap m_adapterCreationMap; + + /// The media player to use to play Alexa speech from. + std::shared_ptr m_speakMediaPlayer; + + /// The media player factory to use to generate players for Alexa audio content. + std::unique_ptr + m_audioMediaPlayerFactory; + + /// The media player to use to play alerts from. + std::shared_ptr m_alertsMediaPlayer; + + /// The media player to play notification indicators. + std::shared_ptr m_notificationsMediaPlayer; + + /// The media player to play bluetooth content. + std::shared_ptr m_bluetoothMediaPlayer; + + /// The media player to play Comms ringtones. + std::shared_ptr m_ringtoneMediaPlayer; + + /// The media player to play system sounds. + std::shared_ptr m_systemSoundMediaPlayer; + + /// The speaker to control volume of Alexa speech. + std::shared_ptr m_speakSpeaker; + + /// A list of speakers to control volume of Alexa audio content. + std::vector> m_audioSpeakers; + + /// The speaker to control volume of alerts. + std::shared_ptr m_alertsSpeaker; + + /// The speaker to control volume of notifications. + std::shared_ptr m_notificationsSpeaker; + + /// The speaker to control volume of bluetooth. + std::shared_ptr m_bluetoothSpeaker; + + /// The speaker to control volume of Comms ringtones. + std::shared_ptr m_ringtoneSpeaker; + + /// The speaker to control volume of system sounds. + std::shared_ptr m_systemSoundSpeaker; + + /// A map of additional speakers to receive volume changes. + std::multimap< + alexaClientSDK::avsCommon::sdkInterfaces::ChannelVolumeInterface::Type, + std::shared_ptr> + m_additionalSpeakers; + + /// Equalizer component runtime setup + std::shared_ptr m_equalizerRuntimeSetup; + + /// The audioFactory is a component that provides unique audio streams. + std::shared_ptr m_audioFactory; + + /// The component that provides the client with valid LWA authorization. + std::shared_ptr m_authDelegate; + + /// The storage interface that will be used to store alerts. + std::shared_ptr m_alertStorage; + + /// The storage interface that will be used to store certified sender messages. + std::shared_ptr m_messageStorage; + + /// The storage interface that will be used to store notification indicators. + std::shared_ptr m_notificationsStorage; + + /// The storage interface that will be used to store device settings. + std::shared_ptr m_deviceSettingStorage; + + /// The storage interface that will be used to store bluetooth data. + std::shared_ptr m_bluetoothStorage; + + /// The storage interface that will be used to store key / value pairs. + std::shared_ptr m_miscStorage; + + /// Observers that can be used to be notified of Alexa dialog related UX state changes. + std::unordered_set> + m_alexaDialogStateObservers; + + /// Observers that can be used to be notified of connection status changes. + std::unordered_set> + m_connectionObservers; + + /// The interface for monitoring and reporting internet connection status + std::shared_ptr + m_internetConnectionMonitor; + + /// Whether the device supports GUI. + bool m_isGuiSupported; + + /// The component that provides the client with the ability to send messages to the Capabilities API. + std::shared_ptr m_capabilitiesDelegate; + + /// The @c ContextManager which will provide the context for various components. + std::shared_ptr m_contextManager; + + /// The object passed in here will be used whenever a new transport object for AVS communication is needed. + std::shared_ptr m_transportFactory; + + /// The @c AVSGatewayManager instance used to create the ApiGateway CA. + std::shared_ptr m_avsGatewayManager; + + /// The device locale assets manager. + std::shared_ptr m_localeAssetsManager; + + /// The set of @c BluetoothDeviceConnectionRuleInterface instances used to create the Bluetooth CA. + std::unordered_set< + std::shared_ptr> + m_enabledConnectionRules; + + /// Optional object used to set the system timezone. + std::shared_ptr m_systemTimezone; + + /// The firmware version to report to @c AVS or @c INVALID_FIRMWARE_VERSION. + alexaClientSDK::avsCommon::sdkInterfaces::softwareInfo::FirmwareVersion m_firmwareVersion; + + /// Whether to send SoftwareInfo upon connecting to @c AVS. + bool m_sendSoftwareInfoOnConnected; + + /// Object to receive notifications about sending SoftwareInfo. + std::shared_ptr + m_softwareInfoSenderObserver; + + /// The @c BluetoothDeviceManager instance used to create the Bluetooth CA. + std::unique_ptr + m_bluetoothDeviceManager; + + /// The metric recorder object used to capture metrics. + std::shared_ptr m_metricRecorder; + + /// Object to manage power resource. + std::shared_ptr m_powerResourceManager; + + /// Diagnostics interface which provides suite of APIs for diagnostic insight into SDK. + std::shared_ptr m_diagnostics; + + /// Optional object used to build capabilities that are not included in the SDK. + std::shared_ptr m_externalCapabilitiesBuilder; + + /// Optional object used to build @c ChannelVolumeInterface in the SDK. + std::shared_ptr m_channelVolumeFactory; + + /// Whether to start scheduling alerts after client initialization. If this is set to false, no alert scheduling + /// will occur until onSystemClockSynchronized is called. + bool m_startAlertSchedulingOnInitialization; + + /// Object used to instantiate @c MessageRouter in the SDK. + std::shared_ptr m_messageRouterFactory; + + /// An optional object that applications may provide to specify external handling of the @c ExpectSpeech directive's + /// timeout. If provided, this function must remain valid for the lifetime of the @c AudioInputProcessor. + std::shared_ptr + m_expectSpeechTimeoutHandler; + + /// Optional object used in the first interaction started from the alexa voice service + alexaClientSDK::capabilityAgents::aip::AudioProvider m_firstInteractionAudioProvider = + alexaClientSDK::capabilityAgents::aip::AudioProvider::null(); + + /// Optional Encryption facilities factory. + std::shared_ptr m_cryptoFactory; + +#ifdef ENABLE_COMMS_AUDIO_PROXY + /// The media player to play Comms calling audio. + std::shared_ptr m_commsMediaPlayer; + + /// The speaker to control volume of Comms calling audio. + std::shared_ptr m_commsSpeaker; + + /// The stream to use which has the audio from microphone. + std::shared_ptr m_sharedDataStream; +#endif + +#ifdef ENABLE_PCC + /// The speaker to control volume of phone audio. + std::shared_ptr m_phoneSpeaker; + + /// The calling functions available on a calling device + std::shared_ptr m_phoneCaller; +#endif + +#ifdef ENABLE_MCC + /// The speaker to control volume of meeting audio. + std::shared_ptr m_meetingSpeaker; + + /// The meeting functions available on a meeting device + std::shared_ptr m_meetingClient; + + /// The calendar functions available on a calendar device + std::shared_ptr m_calendarClient; +#endif +}; +} // namespace defaultClient +} // namespace alexaClientSDK +#endif // ALEXA_CLIENT_SDK_APPLICATIONUTILITIES_DEFAULTCLIENT_INCLUDE_DEFAULTCLIENT_DEFAULTCLIENTBUILDER_H_ diff --git a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientComponent.h b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientComponent.h index 0673c3c6c6..cbf15164d2 100644 --- a/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientComponent.h +++ b/ApplicationUtilities/DefaultClient/include/DefaultClient/DefaultClientComponent.h @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +59,6 @@ #include #include #include -#include #include #include #include @@ -68,7 +69,6 @@ #include #include #include -#include #include "DefaultClient/EqualizerRuntimeSetup.h" #include "DefaultClient/StubApplicationAudioPipelineFactory.h" @@ -108,8 +108,6 @@ using DefaultClientComponent = acsdkManufactory::Component< std::shared_ptr, acsdkManufactory:: Annotated, - acsdkManufactory:: - Annotated, std::shared_ptr, std::shared_ptr, std::shared_ptr, @@ -139,8 +137,9 @@ using DefaultClientComponent = acsdkManufactory::Component< std::shared_ptr, std::shared_ptr, std::shared_ptr, - std::shared_ptr, - std::shared_ptr>; + std::shared_ptr, + std::shared_ptr, + std::shared_ptr>; /** * Get the manufactory @c Component for (legacy) @c DefaultClient initialization. @@ -179,7 +178,8 @@ DefaultClientComponent getComponent( const std::shared_ptr& bluetoothStorage, const std::shared_ptr& bluetoothConnectionRulesProvider, - const std::shared_ptr& notificationsStorage); + const std::shared_ptr& notificationsStorage, + const std::shared_ptr& cryptoFactory); } // namespace defaultClient } // namespace alexaClientSDK diff --git a/ApplicationUtilities/DefaultClient/include/DefaultClient/ExternalCapabilitiesBuilderInterface.h b/ApplicationUtilities/DefaultClient/include/DefaultClient/ExternalCapabilitiesBuilderInterface.h index 615a48c9dd..1eb9a849a6 100644 --- a/ApplicationUtilities/DefaultClient/include/DefaultClient/ExternalCapabilitiesBuilderInterface.h +++ b/ApplicationUtilities/DefaultClient/include/DefaultClient/ExternalCapabilitiesBuilderInterface.h @@ -38,15 +38,15 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include -#include +#include namespace alexaClientSDK { namespace defaultClient { @@ -78,17 +78,6 @@ class ExternalCapabilitiesBuilderInterface { */ virtual ~ExternalCapabilitiesBuilderInterface() = default; - /** - * This method sets the focus manager responsible for visual interactions. - * - * This method will only get called if GUI support has been enabled. - * - * @param visualFocusManager The focus manager object. - * @return A reference to this builder to allow nested function calls. - */ - virtual ExternalCapabilitiesBuilderInterface& withVisualFocusManager( - std::shared_ptr visualFocusManager) = 0; - /** * This method sets the storage using for setting. * @@ -107,7 +96,7 @@ class ExternalCapabilitiesBuilderInterface { * @param templateRuntime The TemplateRuntime object. */ virtual ExternalCapabilitiesBuilderInterface& withTemplateRunTime( - std::shared_ptr templateRuntime) = 0; + std::shared_ptr templateRuntime) = 0; /** * Get the CallManager reference. @@ -124,6 +113,15 @@ class ExternalCapabilitiesBuilderInterface { virtual ExternalCapabilitiesBuilderInterface& withInternetConnectionMonitor( std::shared_ptr internetConnectionMonitor) = 0; + /** + * This method sets the Alexa Interface Message Sender to send Alexa Interface response events. + * + * @param alexaMessageSender The AlexaInterfaceMessageSenderInterface object. + */ + virtual ExternalCapabilitiesBuilderInterface& withAlexaInterfaceMessageSender( + std::shared_ptr + alexaMessageSender) = 0; + /** * This method sets the DialogUXStateAggregator for CallManager. * @@ -161,6 +159,7 @@ class ExternalCapabilitiesBuilderInterface { * @param softwareComponentReporter Object to report adapters' versions. * @param playbackRouter Object to route local playback control command. * @param endpointRegistrationManager Object to manage endpoints. + * @param metricRecorder Object to manage AVS SDK metric reporting. * @return A list with all capabilities as well as objects that require explicit shutdown. Shutdown will be * performed in the reverse order of occurrence. */ @@ -192,7 +191,8 @@ class ExternalCapabilitiesBuilderInterface { std::shared_ptr softwareComponentReporter, std::shared_ptr playbackRouter, std::shared_ptr - endpointRegistrationManager) = 0; + endpointRegistrationManager, + std::shared_ptr metricRecorder) = 0; }; } // namespace defaultClient diff --git a/ApplicationUtilities/DefaultClient/src/CMakeLists.txt b/ApplicationUtilities/DefaultClient/src/CMakeLists.txt index 3ff7530960..27f8d654dd 100644 --- a/ApplicationUtilities/DefaultClient/src/CMakeLists.txt +++ b/ApplicationUtilities/DefaultClient/src/CMakeLists.txt @@ -4,6 +4,7 @@ add_definitions("-DACSDK_LOG_MODULE=defaultClient") add_library(DefaultClient ConnectionRetryTrigger.cpp DefaultClient.cpp + DefaultClientBuilder.cpp DefaultClientComponent.cpp EqualizerRuntimeSetup.cpp StubApplicationAudioPipelineFactory.cpp @@ -15,8 +16,7 @@ target_include_directories(DefaultClient PUBLIC "${Endpoints_SOURCE_DIR}/include") if(BLUETOOTH_BLUEZ) - target_link_libraries(DefaultClient - BluetoothImplementationsBlueZ) + target_link_libraries(DefaultClient BluetoothImplementationsBlueZ) endif() target_link_libraries(DefaultClient @@ -47,31 +47,34 @@ target_link_libraries(DefaultClient RegistrationManager SDKComponent SoftwareComponentReporter - SpeakerManager SpeechSynthesizer SystemSoundPlayer - TemplateRuntime acsdkAlerts acsdkAlertsInterfaces + acsdkAudioEncoderInterfaces acsdkAudioPlayer acsdkBluetooth acsdkBluetoothInterfaces acsdkEqualizer acsdkExternalMediaPlayer acsdkInteractionModel + acsdkSDKClient acsdkStartupManagerInterfaces acsdkSystemClockMonitorInterfaces acsdkDeviceSetup acsdkDeviceSetupInterfaces acsdkAuthorization - acsdkAuthorizationInterfaces) + acsdkAuthorizationInterfaces + acsdkSpeakerManagerComponent + acsdkTemplateRuntime + acsdkTemplateRuntimeInterfaces) if (CAPTIONS) target_link_libraries(DefaultClient CaptionsLib) endif() if (PCC) - target_link_libraries(DefaultClient PhoneCallController) + target_link_libraries(DefaultClient acsdkPhoneCallController) endif() if (MC) @@ -82,7 +85,7 @@ if (MC) endif() if (MCC) - target_link_libraries(DefaultClient MeetingClientController) + target_link_libraries(DefaultClient acsdkMeetingClientController) endif() if (METRICS) @@ -90,9 +93,5 @@ if (METRICS) target_link_libraries(DefaultClient MetricRecorder) endif() -if (OPUS) - target_link_libraries(DefaultClient OpusEncoderContext) -endif() - # install target asdk_install() diff --git a/ApplicationUtilities/DefaultClient/src/ConnectionRetryTrigger.cpp b/ApplicationUtilities/DefaultClient/src/ConnectionRetryTrigger.cpp index 6c2e7fa383..ef9f3852fc 100644 --- a/ApplicationUtilities/DefaultClient/src/ConnectionRetryTrigger.cpp +++ b/ApplicationUtilities/DefaultClient/src/ConnectionRetryTrigger.cpp @@ -23,7 +23,7 @@ namespace defaultClient { using namespace alexaClientSDK::avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("ConnectionRetryTrigger"); +#define TAG "ConnectionRetryTrigger" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp b/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp index e377a10f05..36f3562997 100644 --- a/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp +++ b/ApplicationUtilities/DefaultClient/src/DefaultClient.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -38,17 +40,6 @@ #include #include -#ifdef ENABLE_PCC -#include -#include -#endif - -#ifdef ENABLE_MCC -#include -#include -#include -#endif - #include "DefaultClient/DefaultClient.h" #include "DefaultClient/DefaultClientComponent.h" #include "DefaultClient/StubApplicationAudioPipelineFactory.h" @@ -67,7 +58,7 @@ using namespace alexaClientSDK::avsCommon::sdkInterfaces; using namespace alexaClientSDK::avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("DefaultClient"); +#define TAG "DefaultClient" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -85,12 +76,12 @@ std::unique_ptr DefaultClient::create( std::shared_ptr> additionalSpeakers, #ifdef ENABLE_PCC std::shared_ptr phoneSpeaker, - std::shared_ptr phoneCaller, + std::shared_ptr phoneCaller, #endif #ifdef ENABLE_MCC std::shared_ptr meetingSpeaker, - std::shared_ptr meetingClient, - std::shared_ptr calendarClient, + std::shared_ptr meetingClient, + std::shared_ptr calendarClient, #endif #ifdef ENABLE_COMMS_AUDIO_PROXY std::shared_ptr commsMediaPlayer, @@ -107,7 +98,8 @@ std::unique_ptr DefaultClient::create( std::shared_ptr softwareInfoSenderObserver, std::shared_ptr diagnostics, const std::shared_ptr& externalCapabilitiesBuilder, - capabilityAgents::aip::AudioProvider firstInteractionAudioProvider) { + capabilityAgents::aip::AudioProvider firstInteractionAudioProvider, + const std::shared_ptr& sdkClientRegistry) { std::unique_ptr defaultClient(new DefaultClient()); if (!defaultClient->initialize( manufactory, @@ -136,7 +128,12 @@ std::unique_ptr DefaultClient::create( softwareInfoSenderObserver, diagnostics, externalCapabilitiesBuilder, - firstInteractionAudioProvider)) { + firstInteractionAudioProvider, + sdkClientRegistry)) { + auto shutdownManager = defaultClient->getShutdownManager(); + if (shutdownManager) { + shutdownManager->shutdown(); + } return nullptr; } return defaultClient; @@ -169,12 +166,12 @@ std::unique_ptr DefaultClient::create( std::shared_ptr> additionalSpeakers, #ifdef ENABLE_PCC std::shared_ptr phoneSpeaker, - std::shared_ptr phoneCaller, + std::shared_ptr phoneCaller, #endif #ifdef ENABLE_MCC std::shared_ptr meetingSpeaker, - std::shared_ptr meetingClient, - std::shared_ptr calendarClient, + std::shared_ptr meetingClient, + std::shared_ptr calendarClient, #endif #ifdef ENABLE_COMMS_AUDIO_PROXY std::shared_ptr commsMediaPlayer, @@ -216,7 +213,9 @@ std::unique_ptr DefaultClient::create( bool startAlertSchedulingOnInitialization, std::shared_ptr messageRouterFactory, const std::shared_ptr& expectSpeechTimeoutHandler, - capabilityAgents::aip::AudioProvider firstInteractionAudioProvider) { + capabilityAgents::aip::AudioProvider firstInteractionAudioProvider, + const std::shared_ptr& cryptoFactory, + const std::shared_ptr& sdkClientRegistry) { if (!equalizerRuntimeSetup) { equalizerRuntimeSetup = std::make_shared(false); @@ -304,7 +303,8 @@ std::unique_ptr DefaultClient::create( std::move(bluetoothDeviceManager), std::move(bluetoothStorage), bluetoothConnectionRulesProvider, - std::move(notificationsStorage)); + std::move(notificationsStorage), + cryptoFactory); auto manufactory = DefaultClientManufactory::create(component); auto speakerManager = manufactory->get>(); @@ -350,7 +350,8 @@ std::unique_ptr DefaultClient::create( softwareInfoSenderObserver, diagnostics, externalCapabilitiesBuilder, - firstInteractionAudioProvider); + firstInteractionAudioProvider, + sdkClientRegistry); } bool DefaultClient::initialize( @@ -362,12 +363,12 @@ bool DefaultClient::initialize( std::shared_ptr> additionalSpeakers, #ifdef ENABLE_PCC std::shared_ptr phoneSpeaker, - std::shared_ptr phoneCaller, + std::shared_ptr phoneCaller, #endif #ifdef ENABLE_MCC std::shared_ptr meetingSpeaker, - std::shared_ptr meetingClient, - std::shared_ptr calendarClient, + std::shared_ptr meetingClient, + std::shared_ptr calendarClient, #endif #ifdef ENABLE_COMMS_AUDIO_PROXY std::shared_ptr commsMediaPlayer, @@ -384,7 +385,15 @@ bool DefaultClient::initialize( std::shared_ptr softwareInfoSenderObserver, std::shared_ptr diagnostics, const std::shared_ptr& externalCapabilitiesBuilder, - capabilityAgents::aip::AudioProvider firstInteractionAudioProvider) { + capabilityAgents::aip::AudioProvider firstInteractionAudioProvider, + const std::shared_ptr& sdkClientRegistry) { + + m_shutdownManager = manufactory->get>(); + if (!m_shutdownManager) { + ACSDK_ERROR(LX("initializeFailed").m("Failed to get ShutdownManager!")); + return false; + } + if (!ringtoneMediaPlayer) { ACSDK_ERROR(LX("initializeFailed").d("reason", "nullRingtoneMediaPlayer")); return false; @@ -475,13 +484,13 @@ bool DefaultClient::initialize( m_internetConnectionMonitor = manufactory->get>(); if (!m_internetConnectionMonitor) { - ACSDK_ERROR(LX("initializeFailed").d("reason", "nullConnectionManager")); + ACSDK_ERROR(LX("initializeFailed").d("reason", "nullInternetConnectionMonitor")); return false; } m_connectionManager = manufactory->get>(); if (!m_connectionManager) { - ACSDK_ERROR(LX("initializeFailed").d("reason", "nullDefaultEndpointBuilder")); + ACSDK_ERROR(LX("initializeFailed").d("reason", "nullConnectionManager")); return false; } @@ -522,6 +531,10 @@ bool DefaultClient::initialize( ACSDK_ERROR(LX("initializeFailed").d("reason", "nullAlexaMessageSender")); return false; } + if (externalCapabilitiesBuilder) { + ACSDK_INFO(LX(__FUNCTION__).m("Supply m_alexaMessageSender to externalCapabilitiesBuilder")); + externalCapabilitiesBuilder->withAlexaInterfaceMessageSender(m_alexaMessageSender); + } m_speakerManager = manufactory->get>(); if (!m_speakerManager) { @@ -569,12 +582,6 @@ bool DefaultClient::initialize( return false; } - m_shutdownManager = manufactory->get>(); - if (!m_shutdownManager) { - ACSDK_ERROR(LX("initializeFailed").m("Failed to get ShutdownManager!")); - return false; - } - m_certifiedSender = manufactory->get>(); if (!m_certifiedSender) { ACSDK_ERROR(LX("initializeFailed").d("reason", "nullCertifiedSender")); @@ -740,7 +747,7 @@ bool DefaultClient::initialize( speechConfirmationSetting, capabilityChangeNotifier, wakeWordsSetting, - manufactory->get>(), + manufactory->get>(), firstInteractionAudioProvider, powerResourceManager, metricRecorder, @@ -832,7 +839,7 @@ bool DefaultClient::initialize( * that implements the * PhoneCallController interface of AVS */ - m_phoneCallControllerCapabilityAgent = capabilityAgents::phoneCallController::PhoneCallController::create( + m_phoneCallControllerCapabilityAgent = phoneCallController::PhoneCallController::create( m_contextManager, m_connectionManager, phoneCaller, phoneSpeaker, m_audioFocusManager, m_exceptionSender); if (!m_phoneCallControllerCapabilityAgent) { ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreatePhoneCallControllerCapabilityAgent")); @@ -844,23 +851,20 @@ bool DefaultClient::initialize( * Creating the MeetingClientController - This component is the Capability Agent that implements the * MeetingClientController interface of AVS */ - m_meetingClientControllerCapabilityAgent = - capabilityAgents::meetingClientController::MeetingClientController::create( - m_contextManager, - m_connectionManager, - meetingClient, - calendarClient, - m_speakerManager, - m_audioFocusManager, - m_exceptionSender); + m_meetingClientControllerCapabilityAgent = meetingClientController::MeetingClientController::create( + m_contextManager, + m_connectionManager, + meetingClient, + calendarClient, + m_speakerManager, + m_audioFocusManager, + m_exceptionSender); if (!m_meetingClientControllerCapabilityAgent) { ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateMeetingClientControllerCapabilityAgent")); } #endif if (isGuiSupported) { - m_visualFocusManager = manufactory->get< - acsdkManufactory::Annotated>(); auto renderPlayerInfoCardsProviderRegistrar = manufactory ->get>(); @@ -874,13 +878,14 @@ bool DefaultClient::initialize( * Capability Agent that implements the * TemplateRuntime interface of AVS. */ - m_templateRuntime = capabilityAgents::templateRuntime::TemplateRuntime::createTemplateRuntime( - renderPlayerInfoCardsProviderRegistrar, m_visualFocusManager, m_exceptionSender); - if (!m_templateRuntime) { + auto templateRuntimeData = templateRuntime::TemplateRuntimeFactory::create( + renderPlayerInfoCardsProviderRegistrar, m_exceptionSender, m_defaultEndpointBuilder); + if (!templateRuntimeData.hasValue()) { ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateTemplateRuntimeCapabilityAgent")); return false; } - m_dialogUXStateAggregator->addObserver(m_templateRuntime); + m_templateRuntime = templateRuntimeData.value().templateRuntime; + m_shutdownObjects.push_back(templateRuntimeData.value().requiresShutdown); if (externalCapabilitiesBuilder) { externalCapabilitiesBuilder->withTemplateRunTime(m_templateRuntime); } @@ -1059,10 +1064,6 @@ bool DefaultClient::initialize( } #endif - if (isGuiSupported) { - m_defaultEndpointBuilder->withCapability(m_templateRuntime, m_templateRuntime); - } - if (m_equalizerCapabilityAgent) { m_defaultEndpointBuilder->withCapability(m_equalizerCapabilityAgent, m_equalizerCapabilityAgent); } @@ -1111,7 +1112,8 @@ bool DefaultClient::initialize( powerResourceManager, m_softwareReporterCapabilityAgent, m_playbackRouter, - m_endpointRegistrationManager); + m_endpointRegistrationManager, + metricRecorder); for (auto& capability : externalCapabilities.first) { if (capability.configuration.hasValue()) { m_defaultEndpointBuilder->withCapability(capability.configuration.value(), capability.directiveHandler); @@ -1150,6 +1152,29 @@ bool DefaultClient::initialize( } m_defaultEndpointBuilder->withCapabilityConfiguration(m_softwareReporterCapabilityAgent); + + if (sdkClientRegistry) { + sdkClientRegistry->registerComponent(m_defaultEndpointBuilder); + sdkClientRegistry->registerComponent(m_connectionManager); + sdkClientRegistry->registerComponent(m_exceptionSender); + sdkClientRegistry->registerComponent(m_certifiedSender); + sdkClientRegistry->registerComponent(m_audioFocusManager); + sdkClientRegistry->registerComponent(customerDataManager); + sdkClientRegistry->registerComponent(reportStateHandler); + sdkClientRegistry->registerComponent(m_audioInputProcessor); + sdkClientRegistry->registerComponent(m_speakerManager); + sdkClientRegistry->registerComponent(m_directiveSequencer); + sdkClientRegistry->registerComponent(userInactivityMonitor); + sdkClientRegistry->registerComponent(m_contextManager); + sdkClientRegistry->registerComponent(m_avsGatewayManager); + sdkClientRegistry->registerComponent(audioFactory); + sdkClientRegistry->registerComponent(powerResourceManager); + sdkClientRegistry->registerComponent(m_softwareReporterCapabilityAgent); + sdkClientRegistry->registerComponent(m_playbackRouter); + sdkClientRegistry->registerComponent(m_endpointRegistrationManager); + sdkClientRegistry->registerComponent(metricRecorder); + sdkClientRegistry->registerComponent(m_alexaMessageSender); + } return true; } @@ -1265,7 +1290,7 @@ void DefaultClient::removeAudioPlayerObserver( } void DefaultClient::addTemplateRuntimeObserver( - std::shared_ptr observer) { + std::shared_ptr observer) { if (!m_templateRuntime) { ACSDK_ERROR(LX("addTemplateRuntimeObserverFailed").d("reason", "guiNotSupported")); return; @@ -1274,7 +1299,7 @@ void DefaultClient::addTemplateRuntimeObserver( } void DefaultClient::removeTemplateRuntimeObserver( - std::shared_ptr observer) { + std::shared_ptr observer) { if (!m_templateRuntime) { ACSDK_ERROR(LX("removeTemplateRuntimeObserverFailed").d("reason", "guiNotSupported")); return; @@ -1282,14 +1307,6 @@ void DefaultClient::removeTemplateRuntimeObserver( m_templateRuntime->removeObserver(observer); } -void DefaultClient::TemplateRuntimeDisplayCardCleared() { - if (!m_templateRuntime) { - ACSDK_ERROR(LX("TemplateRuntimeDisplayCardClearedFailed").d("reason", "guiNotSupported")); - return; - } - m_templateRuntime->displayCardCleared(); -} - void DefaultClient::addNotificationsObserver( std::shared_ptr observer) { m_notificationsNotifier->addObserver(observer); @@ -1690,6 +1707,31 @@ std::shared_ptr DefaultClient } DefaultClient::~DefaultClient() { + shutdown(); +} + +bool DefaultClient::setEncodingAudioFormat(AudioFormat::Encoding encoding) { + return m_audioInputProcessor->setEncodingAudioFormat(encoding); +} + +capabilityAgents::aip::AudioInputProcessor::EncodingFormatResponse DefaultClient::requestEncodingAudioFormats( + const capabilityAgents::aip::AudioInputProcessor::EncodingFormatRequest& encodings) { + return m_audioInputProcessor->requestEncodingAudioFormats(encodings); +} + +bool DefaultClient::configure(const std::shared_ptr& sdkClientRegistry) { + return true; +} + +DefaultClient::DefaultClient() : FeatureClientInterface(TAG) { +} + +void DefaultClient::doShutdown() { + if (m_shutdownManager) { + m_shutdownManager->shutdown(); + m_shutdownManager.reset(); + } + while (!m_shutdownObjects.empty()) { if (m_shutdownObjects.back()) { m_shutdownObjects.back()->shutdown(); @@ -1701,10 +1743,6 @@ DefaultClient::~DefaultClient() { ACSDK_DEBUG5(LX("EndpointRegistrationManagerShutdown")); m_endpointRegistrationManager->shutdown(); } - if (m_templateRuntime) { - ACSDK_DEBUG5(LX("TemplateRuntimeShutdown")); - m_templateRuntime->shutdown(); - } if (m_audioInputProcessor) { ACSDK_DEBUG5(LX("AIPShutdown")); removeInternetConnectionObserver(m_audioInputProcessor); @@ -1774,13 +1812,15 @@ DefaultClient::~DefaultClient() { } } -bool DefaultClient::setEncodingAudioFormat(AudioFormat::Encoding encoding) { - return m_audioInputProcessor->setEncodingAudioFormat(encoding); +void DefaultClient::stopInteraction() { + if (m_audioInputProcessor) { + m_audioInputProcessor->resetState(); + } } -capabilityAgents::aip::AudioInputProcessor::EncodingFormatResponse DefaultClient::requestEncodingAudioFormats( - const capabilityAgents::aip::AudioInputProcessor::EncodingFormatRequest& encodings) { - return m_audioInputProcessor->requestEncodingAudioFormats(encodings); +std::shared_ptr DefaultClient::getAudioFocusManager() { + return m_audioFocusManager; } + } // namespace defaultClient } // namespace alexaClientSDK diff --git a/ApplicationUtilities/DefaultClient/src/DefaultClientBuilder.cpp b/ApplicationUtilities/DefaultClient/src/DefaultClientBuilder.cpp new file mode 100644 index 0000000000..ba1e5d7326 --- /dev/null +++ b/ApplicationUtilities/DefaultClient/src/DefaultClientBuilder.cpp @@ -0,0 +1,271 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 "DefaultClient/DefaultClientBuilder.h" + +namespace alexaClientSDK { +namespace defaultClient { +/// String to identify log entries originating from this file. +#define TAG "DefaultClientBuilder" + +/** + * 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) + +// Friendly name for this builder component +static const char DEFAULT_CLIENT_BUILDER_NAME[] = "DefaultClientBuilder"; + +std::unique_ptr DefaultClientBuilder::create( + std::shared_ptr deviceInfo, + std::shared_ptr customerDataManager, + std::unordered_map> + externalMusicProviderMediaPlayers, + std::unordered_map> + externalMusicProviderSpeakers, + acsdkExternalMediaPlayer::ExternalMediaPlayer::AdapterCreationMap adapterCreationMap, + std::shared_ptr speakMediaPlayer, + std::unique_ptr audioMediaPlayerFactory, + std::shared_ptr alertsMediaPlayer, + std::shared_ptr notificationsMediaPlayer, + std::shared_ptr bluetoothMediaPlayer, + std::shared_ptr ringtoneMediaPlayer, + std::shared_ptr systemSoundMediaPlayer, + std::shared_ptr speakSpeaker, + std::vector> audioSpeakers, + std::shared_ptr alertsSpeaker, + std::shared_ptr notificationsSpeaker, + std::shared_ptr bluetoothSpeaker, + std::shared_ptr ringtoneSpeaker, + std::shared_ptr systemSoundSpeaker, + std::multimap< + avsCommon::sdkInterfaces::ChannelVolumeInterface::Type, + std::shared_ptr> additionalSpeakers, +#ifdef ENABLE_PCC + std::shared_ptr phoneSpeaker, + std::shared_ptr phoneCaller, +#endif +#ifdef ENABLE_MCC + std::shared_ptr meetingSpeaker, + std::shared_ptr meetingClient, + std::shared_ptr calendarClient, +#endif +#ifdef ENABLE_COMMS_AUDIO_PROXY + std::shared_ptr commsMediaPlayer, + std::shared_ptr commsSpeaker, + std::shared_ptr sharedDataStream, +#endif + std::shared_ptr equalizerRuntimeSetup, + std::shared_ptr audioFactory, + std::shared_ptr authDelegate, + std::shared_ptr alertStorage, + std::shared_ptr messageStorage, + std::shared_ptr notificationsStorage, + std::shared_ptr deviceSettingStorage, + std::shared_ptr bluetoothStorage, + std::shared_ptr miscStorage, + std::unordered_set> + alexaDialogStateObservers, + std::unordered_set> + connectionObservers, + std::shared_ptr internetConnectionMonitor, + bool isGuiSupported, + std::shared_ptr capabilitiesDelegate, + std::shared_ptr contextManager, + std::shared_ptr transportFactory, + std::shared_ptr avsGatewayManager, + std::shared_ptr localeAssetsManager, + std::unordered_set> + enabledConnectionRules, + std::shared_ptr systemTimezone, + avsCommon::sdkInterfaces::softwareInfo::FirmwareVersion firmwareVersion, + bool sendSoftwareInfoOnConnected, + std::shared_ptr softwareInfoSenderObserver, + std::unique_ptr bluetoothDeviceManager, + std::shared_ptr metricRecorder, + std::shared_ptr powerResourceManager, + std::shared_ptr diagnostics, + std::shared_ptr externalCapabilitiesBuilder, + std::shared_ptr channelVolumeFactory, + bool startAlertSchedulingOnInitialization, + std::shared_ptr messageRouterFactory, + std::shared_ptr expectSpeechTimeoutHandler, + capabilityAgents::aip::AudioProvider firstInteractionAudioProvider, + std::shared_ptr cryptoFactory) { + auto builder = new DefaultClientBuilder(); + + builder->m_deviceInfo = std::move(deviceInfo); + builder->m_customerDataManager = std::move(customerDataManager); + builder->m_externalMusicProviderMediaPlayers = std::move(externalMusicProviderMediaPlayers); + builder->m_externalMusicProviderSpeakers = std::move(externalMusicProviderSpeakers); + builder->m_adapterCreationMap = std::move(adapterCreationMap); + builder->m_speakMediaPlayer = std::move(speakMediaPlayer); + builder->m_audioMediaPlayerFactory = std::move(audioMediaPlayerFactory); + builder->m_alertsMediaPlayer = std::move(alertsMediaPlayer); + builder->m_notificationsMediaPlayer = std::move(notificationsMediaPlayer); + builder->m_bluetoothMediaPlayer = std::move(bluetoothMediaPlayer); + builder->m_ringtoneMediaPlayer = std::move(ringtoneMediaPlayer); + builder->m_systemSoundMediaPlayer = std::move(systemSoundMediaPlayer); + builder->m_speakSpeaker = std::move(speakSpeaker); + builder->m_audioSpeakers = std::move(audioSpeakers); + builder->m_alertsSpeaker = std::move(alertsSpeaker); + builder->m_notificationsSpeaker = std::move(notificationsSpeaker); + builder->m_bluetoothSpeaker = std::move(bluetoothSpeaker); + builder->m_ringtoneSpeaker = std::move(ringtoneSpeaker); + builder->m_systemSoundSpeaker = std::move(systemSoundSpeaker); + builder->m_additionalSpeakers = std::move(additionalSpeakers); +#ifdef ENABLE_PCC + builder->m_phoneSpeaker = std::move(phoneSpeaker); + builder->m_phoneCaller = std::move(phoneCaller); +#endif +#ifdef ENABLE_MCC + builder->m_meetingSpeaker = std::move(meetingSpeaker); + builder->m_meetingClient = std::move(meetingClient); + builder->m_calendarClient = std::move(calendarClient); +#endif +#ifdef ENABLE_COMMS_AUDIO_PROXY + builder->m_commsMediaPlayer = std::move(commsMediaPlayer); + builder->m_commsSpeaker = std::move(commsSpeaker); + builder->m_sharedDataStream = std::move(sharedDataStream); +#endif + builder->m_equalizerRuntimeSetup = std::move(equalizerRuntimeSetup); + builder->m_audioFactory = std::move(audioFactory); + builder->m_authDelegate = std::move(authDelegate); + builder->m_alertStorage = std::move(alertStorage); + builder->m_messageStorage = std::move(messageStorage); + builder->m_notificationsStorage = std::move(notificationsStorage); + builder->m_deviceSettingStorage = std::move(deviceSettingStorage); + builder->m_bluetoothStorage = std::move(bluetoothStorage); + builder->m_miscStorage = std::move(miscStorage); + builder->m_alexaDialogStateObservers = std::move(alexaDialogStateObservers); + builder->m_connectionObservers = std::move(connectionObservers); + builder->m_internetConnectionMonitor = std::move(internetConnectionMonitor); + builder->m_isGuiSupported = isGuiSupported; + builder->m_capabilitiesDelegate = std::move(capabilitiesDelegate); + builder->m_contextManager = std::move(contextManager); + builder->m_transportFactory = std::move(transportFactory); + builder->m_avsGatewayManager = std::move(avsGatewayManager); + builder->m_localeAssetsManager = std::move(localeAssetsManager); + builder->m_enabledConnectionRules = std::move(enabledConnectionRules); + builder->m_systemTimezone = std::move(systemTimezone); + builder->m_firmwareVersion = std::move(firmwareVersion); + builder->m_sendSoftwareInfoOnConnected = sendSoftwareInfoOnConnected; + builder->m_softwareInfoSenderObserver = std::move(softwareInfoSenderObserver); + builder->m_bluetoothDeviceManager = std::move(bluetoothDeviceManager); + builder->m_metricRecorder = std::move(metricRecorder); + builder->m_powerResourceManager = std::move(powerResourceManager); + builder->m_diagnostics = std::move(diagnostics); + builder->m_externalCapabilitiesBuilder = std::move(externalCapabilitiesBuilder); + builder->m_channelVolumeFactory = std::move(channelVolumeFactory); + builder->m_startAlertSchedulingOnInitialization = startAlertSchedulingOnInitialization; + builder->m_messageRouterFactory = std::move(messageRouterFactory); + builder->m_expectSpeechTimeoutHandler = std::move(expectSpeechTimeoutHandler); + builder->m_firstInteractionAudioProvider = std::move(firstInteractionAudioProvider); + builder->m_cryptoFactory = std::move(cryptoFactory); + + return std::unique_ptr(builder); +} + +DefaultClientBuilder::DefaultClientBuilder() : m_constructed(false) { +} + +std::shared_ptr DefaultClientBuilder::construct( + const std::shared_ptr& sdkClientRegistry) { + if (m_constructed) { + ACSDK_ERROR(LX("constructFailed").d("reason", "Repeated call to construct()")); + return nullptr; + } + + m_constructed = true; + + return DefaultClient::create( + std::move(m_deviceInfo), + std::move(m_customerDataManager), + std::move(m_externalMusicProviderMediaPlayers), + std::move(m_externalMusicProviderSpeakers), + std::move(m_adapterCreationMap), + std::move(m_speakMediaPlayer), + std::move(m_audioMediaPlayerFactory), + std::move(m_alertsMediaPlayer), + std::move(m_notificationsMediaPlayer), + std::move(m_bluetoothMediaPlayer), + std::move(m_ringtoneMediaPlayer), + std::move(m_systemSoundMediaPlayer), + std::move(m_speakSpeaker), + std::move(m_audioSpeakers), + std::move(m_alertsSpeaker), + std::move(m_notificationsSpeaker), + std::move(m_bluetoothSpeaker), + std::move(m_ringtoneSpeaker), + std::move(m_systemSoundSpeaker), + std::move(m_additionalSpeakers), +#ifdef ENABLE_PCC + std::move(m_phoneSpeaker), + std::move(m_phoneCaller), +#endif +#ifdef ENABLE_MCC + std::move(m_meetingSpeaker), + std::move(m_meetingClient), + std::move(m_calendarClient), +#endif +#ifdef ENABLE_COMMS_AUDIO_PROXY + std::move(m_commsMediaPlayer), + std::move(m_commsSpeaker), + std::move(m_sharedDataStream), +#endif + std::move(m_equalizerRuntimeSetup), + std::move(m_audioFactory), + std::move(m_authDelegate), + std::move(m_alertStorage), + std::move(m_messageStorage), + std::move(m_notificationsStorage), + std::move(m_deviceSettingStorage), + std::move(m_bluetoothStorage), + std::move(m_miscStorage), + std::move(m_alexaDialogStateObservers), + std::move(m_connectionObservers), + std::move(m_internetConnectionMonitor), + m_isGuiSupported, + std::move(m_capabilitiesDelegate), + std::move(m_contextManager), + std::move(m_transportFactory), + std::move(m_avsGatewayManager), + std::move(m_localeAssetsManager), + std::move(m_enabledConnectionRules), + std::move(m_systemTimezone), + std::move(m_firmwareVersion), + m_sendSoftwareInfoOnConnected, + std::move(m_softwareInfoSenderObserver), + std::move(m_bluetoothDeviceManager), + std::move(m_metricRecorder), + std::move(m_powerResourceManager), + std::move(m_diagnostics), + std::move(m_externalCapabilitiesBuilder), + std::move(m_channelVolumeFactory), + m_startAlertSchedulingOnInitialization, + std::move(m_messageRouterFactory), + std::move(m_expectSpeechTimeoutHandler), + std::move(m_firstInteractionAudioProvider), + std::move(m_cryptoFactory), + sdkClientRegistry); +} + +std::string DefaultClientBuilder::name() { + return DEFAULT_CLIENT_BUILDER_NAME; +} +} // namespace defaultClient +} // namespace alexaClientSDK diff --git a/ApplicationUtilities/DefaultClient/src/DefaultClientComponent.cpp b/ApplicationUtilities/DefaultClient/src/DefaultClientComponent.cpp index 98bab7f423..3191573541 100644 --- a/ApplicationUtilities/DefaultClient/src/DefaultClientComponent.cpp +++ b/ApplicationUtilities/DefaultClient/src/DefaultClientComponent.cpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include #include @@ -51,12 +51,11 @@ #include #include #include -#include -#include +#include #include #include #include -#include +#include #include #include "DefaultClient/DefaultClientComponent.h" @@ -83,7 +82,7 @@ using namespace capabilityAgents::alexa; using namespace capabilityAgents::system; /// String to identify log entries originating from this file. -static const std::string TAG("DefaultClientComponent"); +#define TAG "DefaultClientComponent" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -222,7 +221,8 @@ DefaultClientComponent getComponent( const std::shared_ptr& bluetoothStorage, const std::shared_ptr& bluetoothConnectionRulesProvider, - const std::shared_ptr& notificationsStorage) { + const std::shared_ptr& notificationsStorage, + const std::shared_ptr& cryptoFactory) { std::shared_ptr bluetoothEventBus; if (bluetoothDeviceManager) { bluetoothEventBus = bluetoothDeviceManager->getEventBus(); @@ -259,6 +259,7 @@ DefaultClientComponent getComponent( .addInstance(bluetoothEventBus) .addInstance(bluetoothStorage) .addInstance(notificationsStorage) + .addInstance(cryptoFactory) .addRetainedFactory(getCreateApplicationAudioPipelineFactory(stubAudioPipelineFactory)) .addRetainedFactory(getCreateDeviceSettingStorageInterface(deviceSettingStorage)) @@ -292,7 +293,7 @@ DefaultClientComponent getComponent( createCapabilitiesDelegateStorageInterface) /// Optional, horizontal components. - .addComponent(acsdkSpeechEncoder::getComponent()) + .addComponent(audioEncoderComponent::getComponent()) .addComponent(captions::getComponent()) /// Capability Agents. Some CAs are still instantiated in DefaultClient.cpp. @@ -308,10 +309,9 @@ DefaultClientComponent getComponent( #endif .addComponent(acsdkNotifications::getComponent()) .addComponent(capabilityAgents::playbackController::getComponent()) - .addComponent(capabilityAgents::speakerManager::getComponent()) + .addComponent(speakerManagerComponent::getSpeakerManagerComponent()) .addComponent(capabilityAgents::system::getComponent()) - .addRetainedFactory(capabilityAgents::templateRuntime::RenderPlayerInfoCardsProviderRegistrar:: - createRenderPlayerInfoCardsProviderRegistrarInterface) + .addRetainedFactory(templateRuntime::createRenderPlayerInfoCardsProviderRegistrarInterface) .addComponent(acsdkDeviceSetup::getComponent()); } diff --git a/ApplicationUtilities/DefaultClient/src/EqualizerRuntimeSetup.cpp b/ApplicationUtilities/DefaultClient/src/EqualizerRuntimeSetup.cpp index 310df8c159..50e20c247e 100644 --- a/ApplicationUtilities/DefaultClient/src/EqualizerRuntimeSetup.cpp +++ b/ApplicationUtilities/DefaultClient/src/EqualizerRuntimeSetup.cpp @@ -24,7 +24,7 @@ namespace defaultClient { using namespace alexaClientSDK::acsdkEqualizerInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("EqualizerRuntimeSetup"); +#define TAG "EqualizerRuntimeSetup" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ApplicationUtilities/DefaultClient/src/StubApplicationAudioPipelineFactory.cpp b/ApplicationUtilities/DefaultClient/src/StubApplicationAudioPipelineFactory.cpp index f06f9a927e..4b6fb5ea41 100644 --- a/ApplicationUtilities/DefaultClient/src/StubApplicationAudioPipelineFactory.cpp +++ b/ApplicationUtilities/DefaultClient/src/StubApplicationAudioPipelineFactory.cpp @@ -23,7 +23,7 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::mediaPlayer; /// String to identify log entries originating from this file. -static const std::string TAG("StubApplicationAudioPipelineFactory"); +#define TAG "StubApplicationAudioPipelineFactory" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash b/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash index ad1c51c01c..8eb362c20c 100644 --- a/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash +++ b/ApplicationUtilities/Resources/Audio/include/Audio/Data/create_header.bash @@ -1,5 +1,18 @@ #!/bin/bash +# Copyright Amazon.com, Inc. or its affiliates. All 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. + # This is used to create a header file that contains binary data from a file that can be used in this library. The # files are downloaded from https://developer.amazon.com/docs/alexa/alexa-voice-service/ux-design-overview.html#sounds. diff --git a/ApplicationUtilities/Resources/Audio/src/CMakeLists.txt b/ApplicationUtilities/Resources/Audio/src/CMakeLists.txt index 2b66b0c3c4..01a09a658b 100644 --- a/ApplicationUtilities/Resources/Audio/src/CMakeLists.txt +++ b/ApplicationUtilities/Resources/Audio/src/CMakeLists.txt @@ -8,8 +8,7 @@ add_library(AudioResources SystemSoundAudioFactory.cpp) target_include_directories(AudioResources PUBLIC - "${AudioResources_SOURCE_DIR}/include" - "${AVSCommon_SOURCE_DIR}/Utils/include") + "${AudioResources_SOURCE_DIR}/include") target_link_libraries(AudioResources AVSCommon) diff --git a/ApplicationUtilities/SDKComponent/src/SDKComponent.cpp b/ApplicationUtilities/SDKComponent/src/SDKComponent.cpp index 3cb1e4355d..becaa40f59 100644 --- a/ApplicationUtilities/SDKComponent/src/SDKComponent.cpp +++ b/ApplicationUtilities/SDKComponent/src/SDKComponent.cpp @@ -29,7 +29,7 @@ using namespace avsCommon::utils::logger; static const std::string SDK_COMPONENT_NAME = "com.amazon.alexa.deviceSDK"; /// String to identify log entries originating from this file. -static const std::string TAG("SDKComponent"); +#define TAG "SDKComponent" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/ApplicationUtilities/SystemSoundPlayer/src/SystemSoundPlayer.cpp b/ApplicationUtilities/SystemSoundPlayer/src/SystemSoundPlayer.cpp index 21478a5b0e..bbf19dd492 100644 --- a/ApplicationUtilities/SystemSoundPlayer/src/SystemSoundPlayer.cpp +++ b/ApplicationUtilities/SystemSoundPlayer/src/SystemSoundPlayer.cpp @@ -24,7 +24,7 @@ using namespace avsCommon::utils::logger; using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState; /// String to identify log entries originating from this file. -static const std::string TAG("SystemSoundPlayer"); +#define TAG "SystemSoundPlayer" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/include/BlueZ/BlueZBluetoothDevice.h b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZBluetoothDevice.h index 29f43a53e4..92dac78e21 100644 --- a/BluetoothImplementations/BlueZ/include/BlueZ/BlueZBluetoothDevice.h +++ b/BluetoothImplementations/BlueZ/include/BlueZ/BlueZBluetoothDevice.h @@ -80,6 +80,8 @@ class BlueZBluetoothDevice std::future connect() override; std::future disconnect() override; + bool setPairingPin(const std::string& pin) override; + std::vector> getSupportedServices() override; std::shared_ptr getService( diff --git a/BluetoothImplementations/BlueZ/src/BlueZA2DPSink.cpp b/BluetoothImplementations/BlueZ/src/BlueZA2DPSink.cpp index fc35a085f1..cc10f0a83a 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZA2DPSink.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZA2DPSink.cpp @@ -26,7 +26,7 @@ using namespace avsCommon::utils; using namespace avsCommon::sdkInterfaces::bluetooth::services; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZA2DPSink"}; +#define TAG "BlueZA2DPSink" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/BlueZA2DPSource.cpp b/BluetoothImplementations/BlueZ/src/BlueZA2DPSource.cpp index 28f8393922..528d5f942c 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZA2DPSource.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZA2DPSource.cpp @@ -27,7 +27,7 @@ using namespace avsCommon::utils; using namespace avsCommon::sdkInterfaces::bluetooth::services; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZA2DPSource"}; +#define TAG "BlueZA2DPSource" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/BlueZAVRCPController.cpp b/BluetoothImplementations/BlueZ/src/BlueZAVRCPController.cpp index e4afdc8e75..3d72cf5c67 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZAVRCPController.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZAVRCPController.cpp @@ -24,7 +24,7 @@ using namespace avsCommon::utils; using namespace avsCommon::sdkInterfaces::bluetooth::services; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZAVRCPController"}; +#define TAG "BlueZAVRCPController" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/BlueZAVRCPTarget.cpp b/BluetoothImplementations/BlueZ/src/BlueZAVRCPTarget.cpp index 50351806d5..1b9e0894e1 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZAVRCPTarget.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZAVRCPTarget.cpp @@ -24,7 +24,7 @@ using namespace avsCommon::utils; using namespace avsCommon::sdkInterfaces::bluetooth::services; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZAVRCPTarget"}; +#define TAG "BlueZAVRCPTarget" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/BlueZBluetoothDevice.cpp b/BluetoothImplementations/BlueZ/src/BlueZBluetoothDevice.cpp index 662cb4e189..a1c9f41749 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZBluetoothDevice.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZBluetoothDevice.cpp @@ -37,7 +37,7 @@ using namespace avsCommon::sdkInterfaces::bluetooth; using namespace avsCommon::sdkInterfaces::bluetooth::services; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZBluetoothDevice"}; +#define TAG "BlueZBluetoothDevice" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -503,6 +503,11 @@ bool BlueZBluetoothDevice::executeDisconnect() { return true; } +bool BlueZBluetoothDevice::setPairingPin(const std::string& pin) { + // This feature is not supported currently. + return false; +} + std::vector> BlueZBluetoothDevice::getSupportedServices() { ACSDK_DEBUG5(LX(__func__)); @@ -750,7 +755,7 @@ void BlueZBluetoothDevice::onPropertyChanged(const GVariantMapReader& changesMap initializeServices(uuids); } - m_executor.submit([this, pairedChanged, paired, connectedChanged, connected, aliasChanged, aliasStr] { + m_executor.execute([this, pairedChanged, paired, connectedChanged, connected, aliasChanged, aliasStr] { if (aliasChanged) { ACSDK_DEBUG5(LX("nameChanged").d("oldName", m_friendlyName).d("newName", aliasStr)); m_friendlyName = aliasStr; diff --git a/BluetoothImplementations/BlueZ/src/BlueZBluetoothDeviceManager.cpp b/BluetoothImplementations/BlueZ/src/BlueZBluetoothDeviceManager.cpp index e10d5a7eef..49cc5863d7 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZBluetoothDeviceManager.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZBluetoothDeviceManager.cpp @@ -25,7 +25,7 @@ using namespace avsCommon::sdkInterfaces::bluetooth; using namespace avsCommon::utils::bluetooth; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZBluetoothDeviceManager"}; +#define TAG "BlueZBluetoothDeviceManager" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/BlueZDeviceManager.cpp b/BluetoothImplementations/BlueZ/src/BlueZDeviceManager.cpp index ff225cd6b6..670f9b2339 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZDeviceManager.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZDeviceManager.cpp @@ -79,7 +79,7 @@ static const std::string STATE_IDLE = "idle"; static const std::string STATE_ACTIVE = "active"; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZDeviceManager"}; +#define TAG "BlueZDeviceManager" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/BlueZHFP.cpp b/BluetoothImplementations/BlueZ/src/BlueZHFP.cpp index f3d1fabdb1..45e7d9bfef 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZHFP.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZHFP.cpp @@ -26,7 +26,7 @@ using namespace avsCommon::utils; using namespace avsCommon::sdkInterfaces::bluetooth::services; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZHFP"}; +#define TAG "BlueZHFP" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/BlueZHID.cpp b/BluetoothImplementations/BlueZ/src/BlueZHID.cpp index 27d8d0e385..ab3d08ab13 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZHID.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZHID.cpp @@ -26,7 +26,7 @@ using namespace avsCommon::utils; using namespace avsCommon::sdkInterfaces::bluetooth::services; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZHID"}; +#define TAG "BlueZHID" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/BlueZHostController.cpp b/BluetoothImplementations/BlueZ/src/BlueZHostController.cpp index 2ea1d28502..314cd1b34d 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZHostController.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZHostController.cpp @@ -25,7 +25,7 @@ namespace bluetoothImplementations { namespace blueZ { /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZHostController"}; +#define TAG "BlueZHostController" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/BlueZSPP.cpp b/BluetoothImplementations/BlueZ/src/BlueZSPP.cpp index b1173cf6f9..16686a42bc 100644 --- a/BluetoothImplementations/BlueZ/src/BlueZSPP.cpp +++ b/BluetoothImplementations/BlueZ/src/BlueZSPP.cpp @@ -26,7 +26,7 @@ using namespace avsCommon::utils; using namespace avsCommon::sdkInterfaces::bluetooth::services; /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZSPP"}; +#define TAG "BlueZSPP" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/DBusConnection.cpp b/BluetoothImplementations/BlueZ/src/DBusConnection.cpp index 24f57a6d15..063bb8347a 100644 --- a/BluetoothImplementations/BlueZ/src/DBusConnection.cpp +++ b/BluetoothImplementations/BlueZ/src/DBusConnection.cpp @@ -23,7 +23,7 @@ namespace bluetoothImplementations { namespace blueZ { /// String to identify log entries originating from this file. -static const std::string TAG{"DBusConnection"}; +#define TAG "DBusConnection" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/DBusObjectBase.cpp b/BluetoothImplementations/BlueZ/src/DBusObjectBase.cpp index 496ce7909f..f8ee17343a 100644 --- a/BluetoothImplementations/BlueZ/src/DBusObjectBase.cpp +++ b/BluetoothImplementations/BlueZ/src/DBusObjectBase.cpp @@ -23,7 +23,7 @@ namespace bluetoothImplementations { namespace blueZ { /// String to identify log entries originating from this file. -static const std::string TAG{"DBusObjectBase"}; +#define TAG "DBusObjectBase" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/DBusPropertiesProxy.cpp b/BluetoothImplementations/BlueZ/src/DBusPropertiesProxy.cpp index ed3de477e8..12bb2b3f86 100644 --- a/BluetoothImplementations/BlueZ/src/DBusPropertiesProxy.cpp +++ b/BluetoothImplementations/BlueZ/src/DBusPropertiesProxy.cpp @@ -24,7 +24,7 @@ namespace bluetoothImplementations { namespace blueZ { /// String to identify log entries originating from this file. -static const std::string TAG{"DBusPropertiesProxy"}; +#define TAG "DBusPropertiesProxy" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/DBusProxy.cpp b/BluetoothImplementations/BlueZ/src/DBusProxy.cpp index c813add37c..59d8c2a052 100644 --- a/BluetoothImplementations/BlueZ/src/DBusProxy.cpp +++ b/BluetoothImplementations/BlueZ/src/DBusProxy.cpp @@ -25,7 +25,7 @@ namespace bluetoothImplementations { namespace blueZ { /// String to identify log entries originating from this file. -static const std::string TAG{"BlueZUtils"}; +#define TAG "BlueZUtils" static const int PROXY_DEFAULT_TIMEOUT = -1; diff --git a/BluetoothImplementations/BlueZ/src/GVariantMapReader.cpp b/BluetoothImplementations/BlueZ/src/GVariantMapReader.cpp index 2f141b5000..830e790282 100644 --- a/BluetoothImplementations/BlueZ/src/GVariantMapReader.cpp +++ b/BluetoothImplementations/BlueZ/src/GVariantMapReader.cpp @@ -22,7 +22,7 @@ namespace bluetoothImplementations { namespace blueZ { /// String to identify log entries originating from this file. -static const std::string TAG{"GVariantMapReader"}; +#define TAG "GVariantMapReader" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/GVariantTupleReader.cpp b/BluetoothImplementations/BlueZ/src/GVariantTupleReader.cpp index da5a9a3b3e..66cd7a1bb1 100644 --- a/BluetoothImplementations/BlueZ/src/GVariantTupleReader.cpp +++ b/BluetoothImplementations/BlueZ/src/GVariantTupleReader.cpp @@ -22,7 +22,7 @@ namespace bluetoothImplementations { namespace blueZ { /// String to identify log entries originating from this file. -static const std::string TAG{"GVariantTupleReader"}; +#define TAG "GVariantTupleReader" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/MPRISPlayer.cpp b/BluetoothImplementations/BlueZ/src/MPRISPlayer.cpp index 944fe5290c..0caa549e8d 100644 --- a/BluetoothImplementations/BlueZ/src/MPRISPlayer.cpp +++ b/BluetoothImplementations/BlueZ/src/MPRISPlayer.cpp @@ -30,7 +30,7 @@ using namespace avsCommon::sdkInterfaces::bluetooth::services; using namespace avsCommon::utils::bluetooth; /// String to identify log entries originating from this file. -static const std::string TAG{"MPRISPlayer"}; +#define TAG "MPRISPlayer" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/MediaEndpoint.cpp b/BluetoothImplementations/BlueZ/src/MediaEndpoint.cpp index dff806e84e..bdbf127699 100644 --- a/BluetoothImplementations/BlueZ/src/MediaEndpoint.cpp +++ b/BluetoothImplementations/BlueZ/src/MediaEndpoint.cpp @@ -96,7 +96,7 @@ constexpr size_t MAX_SANE_CODE_SIZE = MAX_SANE_FRAME_LENGTH * 32; constexpr size_t MIN_SANE_CODE_SIZE = 1; // Standard SDK per module logging constants -static const std::string TAG{"MediaEndpoint"}; +#define TAG "MediaEndpoint" #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /** diff --git a/BluetoothImplementations/BlueZ/src/PairingAgent.cpp b/BluetoothImplementations/BlueZ/src/PairingAgent.cpp index 662811b8de..56f1fb6f2d 100644 --- a/BluetoothImplementations/BlueZ/src/PairingAgent.cpp +++ b/BluetoothImplementations/BlueZ/src/PairingAgent.cpp @@ -27,7 +27,7 @@ namespace bluetoothImplementations { namespace blueZ { /// String to identify log entries originating from this file. -static const std::string TAG{"PairingAgent"}; +#define TAG "PairingAgent" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/BluetoothImplementations/BlueZ/src/PulseAudioBluetoothInitializer.cpp b/BluetoothImplementations/BlueZ/src/PulseAudioBluetoothInitializer.cpp index 1812af5e8b..5474ecde60 100644 --- a/BluetoothImplementations/BlueZ/src/PulseAudioBluetoothInitializer.cpp +++ b/BluetoothImplementations/BlueZ/src/PulseAudioBluetoothInitializer.cpp @@ -21,7 +21,7 @@ #include /// String to identify log entries originating from this file. -static const std::string TAG{"PulseAudioBluetoothInitializer"}; +#define TAG "PulseAudioBluetoothInitializer" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -414,7 +414,7 @@ void PulseAudioBluetoothInitializer::onEventFired(const BluetoothEvent& event) { return; } - m_executor.submit([this] { + m_executor.execute([this] { if (!m_paLoopStarted) { m_paLoopStarted = true; run(); diff --git a/CHANGELOG.md b/CHANGELOG.md index 1418530279..a37bf9c745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## ChangeLog +### Version 3.0.0 - September 30 2022 +Feature enhancements, updates, and resolved issues from all releases are available on the [Amazon developer portal](https://developer.amazon.com/docs/alexa/avs-device-sdk/release-notes.html) + +### Version 1.26.0 - November 15 2021 +Feature enhancements, updates, and resolved issues from all releases are available on the [Amazon developer portal](https://developer.amazon.com/en-US/docs/alexa/avs-device-sdk-1-2x/release-notes.html) + ### Version 1.25.0 - August 23 2021 Feature enhancements, updates, and resolved issues from all releases are available on the [Amazon developer portal](https://developer.amazon.com/docs/alexa/avs-device-sdk/release-notes.html) diff --git a/CMakeLists.txt b/CMakeLists.txt index a49a5c5b6a..327a7fa005 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) # Set project information -project(AlexaClientSDK VERSION 1.26.0 LANGUAGES CXX) +project(AlexaClientSDK VERSION 3.0.0 LANGUAGES CXX) set(PROJECT_BRIEF "A cross-platform, modular SDK for interacting with the Alexa Voice Service") # This variable should be used by extension packages to include cmake files from this project. @@ -27,6 +27,9 @@ configure_file ( "${PROJECT_BINARY_DIR}/AVSCommon/Utils/include/AVSCommon/Utils/SDKConfig.h" ) +# Create Integration directory in output +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/Integration) + # Add utils under ThirdParty first so extensions can also utilize them add_subdirectory("ThirdParty") @@ -56,16 +59,12 @@ endif() add_subdirectory("InterruptModel") add_subdirectory("PlaylistParser") add_subdirectory("CapabilityAgents") -if (NOT MSVC) - add_subdirectory("Integration") -endif() -add_subdirectory("SampleApp") add_subdirectory("ApplicationUtilities") add_subdirectory("MediaPlayer") add_subdirectory("shared") -add_subdirectory("SpeechEncoder") add_subdirectory("Storage") add_subdirectory("SynchronizeStateSender") +add_subdirectory("SampleApplications") add_subdirectory("doc") # Create .pc pkg-config file diff --git a/CapabilitiesDelegate/include/CapabilitiesDelegate/CapabilitiesDelegate.h b/CapabilitiesDelegate/include/CapabilitiesDelegate/CapabilitiesDelegate.h index 8a7106f886..dfe0182ec6 100644 --- a/CapabilitiesDelegate/include/CapabilitiesDelegate/CapabilitiesDelegate.h +++ b/CapabilitiesDelegate/include/CapabilitiesDelegate/CapabilitiesDelegate.h @@ -60,6 +60,15 @@ struct InProcessEndpointsToConfigMapStruct { /** * CapabilitiesDelegate provides an implementation of the CapabilitiesDelegateInterface. It allows clients to register * capabilities implemented by agents and publish them so that Alexa is aware of the device's capabilities. + * + * While updating capabilities for an endpoint, the device will also send the cached capabilities of all endpoints that + * share the same registration information. Such endpoints are referred to as deduplicated endpoints. + * + * @note: The following restrictions apply to deduplicated endpoints: + * 1. We can only have one set of deduplicated endpoints, and this set will include the default endpoint. + * 2. All capabilities of the deduplicated endpoints will need to fit into one discovery event. + * 3. Deleting a deduplicated endpoint is not permitted. + * */ class CapabilitiesDelegate : public avsCommon::sdkInterfaces::CapabilitiesDelegateInterface @@ -78,6 +87,7 @@ class CapabilitiesDelegate * @param providerRegistrar Object with which to register the new instance as a post connect operation provider. * @param shutdownNotifier The object to register with to be notified when it is time to shut down. * @param alexaEventProcessedNotifier The object to register with to be notified of AlexaEventProcessed directives. + * @param metricRecorder Optional reference to metric recorder. * @return If successful, returns a new CapabilitiesDelegate, otherwise @c nullptr. */ static std::shared_ptr createCapabilitiesDelegateInterface( @@ -89,7 +99,8 @@ class CapabilitiesDelegate providerRegistrar, const std::shared_ptr& shutdownNotifier, const std::shared_ptr& - alexaEventProcessedNotifier); + alexaEventProcessedNotifier, + const std::shared_ptr& metricRecorder = nullptr); /** * Create an CapabilitiesDelegate. @@ -103,7 +114,8 @@ class CapabilitiesDelegate static std::shared_ptr create( const std::shared_ptr& authDelegate, const std::shared_ptr& storage, - const std::shared_ptr& customerDataManager); + const std::shared_ptr& customerDataManager, + const std::shared_ptr& metricRecorder = nullptr); /// @name CapabilitiesDelegateInterface method overrides. /// @{ @@ -181,7 +193,8 @@ class CapabilitiesDelegate CapabilitiesDelegate( const std::shared_ptr& authDelegate, const std::shared_ptr& storage, - const std::shared_ptr& customerDataManager); + const std::shared_ptr& customerDataManager, + const std::shared_ptr& metricsRecorder); /** * Perform initialization after construction but before returning the @@ -285,6 +298,26 @@ class CapabilitiesDelegate */ void moveInFlightEndpointsToRegisteredEndpoints(); + /** + * Invoke the DiscoveryEventSender to send endpoints. + * @param addOrUpdateEndpointsToSend map of endpoints to be added or updated. + * @param deleteEndpointsToSend map of endpoints to be deleted. + * @return false if the DiscoveryEventSender was not successfully created, true otherwise. + */ + bool executeSendDiscoveryEvents( + const std::unordered_map& addOrUpdateEndpointsToSend, + const std::unordered_map& deleteEndpointsToSend); + + /** + * Determine whether an endpoint is deduplicated. + * @param endpointId Identifier of the endpoint. + * @return true if the endpoint is deduplicated, false otherwise. + */ + bool isEndpointDeduplicated(const avsCommon::sdkInterfaces::endpoints::EndpointIdentifier& endpointId); + + /// Optional (may be nullptr) interface for metrics. + std::shared_ptr m_metricRecorder; + /// Mutex used to serialize access to Capabilities state and Capabilities state observers. std::mutex m_observerMutex; @@ -323,6 +356,13 @@ class CapabilitiesDelegate /// A map of endpointId to configuration for currently registered endpoints. std::unordered_map m_endpoints; + /// A map of endpointId to Registration for currently pending/in-flight/registered endpoints. + /// @note these endpoint(s) are the device maintaining the HTTP/2 connection. + std::unordered_map< + avsCommon::sdkInterfaces::endpoints::EndpointIdentifier, + avsCommon::utils::Optional> + m_endpointRegistrations; + /// The mutex to serialize operations related to @c m_currentDiscoveryEventSender. std::mutex m_currentDiscoveryEventSenderMutex; diff --git a/CapabilitiesDelegate/include/CapabilitiesDelegate/DiscoveryEventSender.h b/CapabilitiesDelegate/include/CapabilitiesDelegate/DiscoveryEventSender.h index d936567f31..4c4a44ac48 100644 --- a/CapabilitiesDelegate/include/CapabilitiesDelegate/DiscoveryEventSender.h +++ b/CapabilitiesDelegate/include/CapabilitiesDelegate/DiscoveryEventSender.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -50,14 +51,18 @@ class DiscoveryEventSender * @param deleteReportEndpoints The map of endpoints for which the @c Discovery.DeleteReport event will be sent. * @param authDelegate The auth delegate instance to request the auth token from to be sent in the @c Discovery * events. - * @param waitForEventProcessed Indicate if sender should wait for the EventProcessed directive. + * @param waitForEventProcessed Indicate if sender should wait for the EventProcessed directive. Default is true. + * @param metricRecorder Optional (may be nullptr) reference to metric recorder. + * @param postConnect Indicate if sender is operating in post-connect operation context. Default is false. * @return a new instance of the @c DiscoveryEventSender. */ static std::shared_ptr create( const std::unordered_map& addOrUpdateReportEndpoints, const std::unordered_map& deleteReportEndpoints, const std::shared_ptr& authDelegate, - const bool waitForEventProcessed = true); + bool waitForEventProcessed = true, + const std::shared_ptr& metricRecorder = nullptr, + bool postConnect = false); /** * Destructor. @@ -92,12 +97,16 @@ class DiscoveryEventSender * @param authDelegate The auth delegate instance to request the auth token from to be sent in the @c Discovery * events. * @param waitForEventProcessed Indicate if sender should wait for the EventProcessed directive. + * @param metricRecorder Optional (may be nullptr) reference to metric recorder. + * @param postConnect Indicate if sender is operating in post-connect operation context. */ DiscoveryEventSender( const std::unordered_map& addOrUpdateReportEndpoints, const std::unordered_map& deleteReportEndpoints, const std::shared_ptr& authDelegate, - const bool waitForEventProcessed); + bool waitForEventProcessed, + const std::shared_ptr& metricRecorder, + bool postConnect); /** * Sends the discovery event while taking into account retries. @@ -193,6 +202,9 @@ class DiscoveryEventSender /// Auth delegate used to get the access token std::shared_ptr m_authDelegate; + /// Optional (may be nullptr) interface for metrics. + std::shared_ptr m_metricRecorder; + /// The authDelegate's auth status. AuthObserverInterface::State m_currentAuthState; @@ -235,6 +247,9 @@ class DiscoveryEventSender /// Flag indicating if the event should wait for the EventProcessed directive. const bool m_waitForEventProcessed; + + /// Flag indicating if the object is used for post-connect operations. + const bool m_postConnect; }; } // namespace capabilitiesDelegate diff --git a/CapabilitiesDelegate/include/CapabilitiesDelegate/Utils/DiscoveryUtils.h b/CapabilitiesDelegate/include/CapabilitiesDelegate/Utils/DiscoveryUtils.h index 86d27fecaf..9409115be3 100644 --- a/CapabilitiesDelegate/include/CapabilitiesDelegate/Utils/DiscoveryUtils.h +++ b/CapabilitiesDelegate/include/CapabilitiesDelegate/Utils/DiscoveryUtils.h @@ -110,6 +110,18 @@ std::string getDeleteReportEventJson( const std::vector& endpointConfigurations, const std::string& authToken); +/** + * Get the maximum number of supported endpoints. + * @return The maximum number of endpoints. + */ +size_t getMaxEndpoints(); + +/** + * Get the maximum number of capabilities per endpoint. + * @return The maximum number of capabilities an endpoint can have. + */ +size_t getMaxCapabilitiesPerEndpoint(); + } // namespace utils } // namespace capabilitiesDelegate } // namespace alexaClientSDK diff --git a/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp b/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp index 57ba3563b2..942102894b 100644 --- a/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp +++ b/CapabilitiesDelegate/src/CapabilitiesDelegate.cpp @@ -14,6 +14,7 @@ */ #include +#include #include #include @@ -31,15 +32,16 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::sdkInterfaces::endpoints; using namespace avsCommon::utils; using namespace avsCommon::utils::configuration; +using namespace avsCommon::utils::metrics; using namespace capabilitiesDelegate::utils; /// String to identify log entries originating from this file. -static const std::string TAG("CapabilitiesDelegate"); +#define TAG "CapabilitiesDelegate" /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -71,7 +73,8 @@ std::shared_ptr CapabilitiesDelegate::createCapab providerRegistrar, const std::shared_ptr& shutdownNotifier, const std::shared_ptr& - alexaEventProcessedNotifier) { + alexaEventProcessedNotifier, + const std::shared_ptr& metricRecorder) { if (!providerRegistrar) { ACSDK_ERROR(LX("createCapabilitiesDelegateInterfaceFailed").d("reason", "nullProviderRegistrar")); return nullptr; @@ -82,7 +85,7 @@ std::shared_ptr CapabilitiesDelegate::createCapab return nullptr; } - auto capabilitiesDelegate = create(authDelegate, std::move(storage), customerDataManager); + auto capabilitiesDelegate = create(authDelegate, std::move(storage), customerDataManager, metricRecorder); if (!capabilitiesDelegate) { ACSDK_ERROR(LX("createCapabilitiesDelegateInterfaceFailed").d("reason", "createFailed")); return nullptr; @@ -100,7 +103,8 @@ std::shared_ptr CapabilitiesDelegate::createCapab std::shared_ptr CapabilitiesDelegate::create( const std::shared_ptr& authDelegate, const std::shared_ptr& capabilitiesDelegateStorage, - const std::shared_ptr& customerDataManager) { + const std::shared_ptr& customerDataManager, + const std::shared_ptr& metricRecorder) { if (!authDelegate) { ACSDK_ERROR(LX("createFailed").d("reason", "nullAuthDelegate")); } else if (!capabilitiesDelegateStorage) { @@ -109,7 +113,7 @@ std::shared_ptr CapabilitiesDelegate::create( ACSDK_ERROR(LX("createFailed").d("reason", "nullCustomerDataManager")); } else { std::shared_ptr instance( - new CapabilitiesDelegate(authDelegate, capabilitiesDelegateStorage, customerDataManager)); + new CapabilitiesDelegate(authDelegate, capabilitiesDelegateStorage, customerDataManager, nullptr)); if (!(instance->init())) { ACSDK_ERROR(LX("createFailed").d("reason", "CapabilitiesDelegateInitFailed")); return nullptr; @@ -124,9 +128,11 @@ std::shared_ptr CapabilitiesDelegate::create( CapabilitiesDelegate::CapabilitiesDelegate( const std::shared_ptr& authDelegate, const std::shared_ptr& capabilitiesDelegateStorage, - const std::shared_ptr& customerDataManager) : + const std::shared_ptr& customerDataManager, + const std::shared_ptr& metricRecorder) : RequiresShutdown{"CapabilitiesDelegate"}, CustomerDataHandler{customerDataManager}, + m_metricRecorder{metricRecorder}, m_capabilitiesState{CapabilitiesDelegateObserverInterface::State::UNINITIALIZED}, m_capabilitiesError{CapabilitiesDelegateObserverInterface::Error::UNINITIALIZED}, m_authDelegate{authDelegate}, @@ -248,6 +254,12 @@ void CapabilitiesDelegate::invalidateCapabilities() { } } +/// Check if the endpoint is present in the map of endpoint registrations. +bool CapabilitiesDelegate::isEndpointDeduplicated(const EndpointIdentifier& endpointId) { + ACSDK_DEBUG5(LX(__func__)); + return m_endpointRegistrations.find(endpointId) != m_endpointRegistrations.end(); +} + bool CapabilitiesDelegate::addOrUpdateEndpoint( const AVSDiscoveryEndpointAttributes& endpointAttributes, const std::vector& capabilities) { @@ -272,7 +284,7 @@ bool CapabilitiesDelegate::addOrUpdateEndpoint( } } - std::string endpointId = endpointAttributes.endpointId; + const EndpointIdentifier& endpointId = endpointAttributes.endpointId; { std::lock_guard lock{m_endpointsMutex}; @@ -285,6 +297,17 @@ bool CapabilitiesDelegate::addOrUpdateEndpoint( return false; } + // Check that this endpoint's registration is not changing. + // If the endpoint is already present in m_endpointRegistrations, confirm that the current registration is not + // empty, and is the same as the previous one. + if (m_endpointRegistrations.find(endpointId) != m_endpointRegistrations.end() && + m_endpointRegistrations[endpointId] != endpointAttributes.registration) { + ACSDK_ERROR(LX("addOrUpdateEndpointFailed") + .d("reason", "Endpoint registration changed") + .sensitive("endpointId", endpointId)); + return false; + } + // Add endpoint to pending list. it = m_addOrUpdateEndpoints.pending.find(endpointId); if (m_addOrUpdateEndpoints.pending.end() != it) { @@ -294,19 +317,121 @@ bool CapabilitiesDelegate::addOrUpdateEndpoint( return false; } + if (capabilities.size() > getMaxCapabilitiesPerEndpoint()) { + ACSDK_ERROR(LX("addOrUpdateEndpointFailed") + .d("reason", "Maximum number of capabilities reached") + .d("size", capabilities.size())); + return false; + } + std::string endpointConfigJson = getEndpointConfigJson(endpointAttributes, capabilities); // Check for endpoint configuration. if (endpointConfigJson.size() > MAX_ENDPOINTS_SIZE_IN_PAYLOAD) { - ACSDK_ERROR(LX("addOrUpdateEndpointFailed").d("reason", "endpointConfiguration too big")); + ACSDK_ERROR(LX("addOrUpdateEndpointFailed") + .d("reason", "endpointConfiguration too big") + .d("size", endpointConfigJson.size())); return false; } + if (endpointAttributes.registration.hasValue()) { + if (!m_endpointRegistrations.empty()) { + // check that the registration of endpointId matches the others by comparing it to the first one. + // Note that if endpointId is already present in m_endpointRegistrations, it has already been confirmed + // earlier that its registration is unchanged. + auto registration = m_endpointRegistrations.begin()->second; + if (endpointAttributes.registration != registration) { + ACSDK_ERROR(LX("addOrUpdateEndpointFailed") + .d("reason", "Endpoint registration does not match existing deduplicated endpoints") + .sensitive("endpointId", endpointId)); + return false; + } + } + // If this endpoint needs to be added to m_endpointRegistrations check whether doing so will + // exceed the maximum number of endpoints. + if (m_endpointRegistrations.find(endpointId) == m_endpointRegistrations.end() && + m_endpointRegistrations.size() + 1 > getMaxEndpoints()) { + ACSDK_ERROR(LX("addOrUpdateEndpointFailed") + .d("reason", "Max number of deduplicated endpoints reached") + .sensitive("endpointId", endpointId)); + return false; + } + + m_endpointRegistrations[endpointId] = endpointAttributes.registration; + + // Check if any of the deduplicated endpoints are pending deletion or in-flight for deletion. If they are, + // fail this addOrUpdate. This should never happen. + for (auto& iter : m_endpointRegistrations) { + if ((m_deleteEndpoints.pending.find(iter.first) != m_deleteEndpoints.pending.end()) || + (m_deleteEndpoints.inFlight.find(iter.first) != m_deleteEndpoints.inFlight.end())) { + ACSDK_ERROR(LX("addOrUpdateEndpointFailed") + .d("reason", "Deduplicated endpoint already pending delete") + .sensitive("endpointId", endpointId)); + return false; + } + } + + // Check that all de-duplicated endpoints can fit into one discovery message by adding the sizes of their + // configurations. + // First add the size of the current endpoint configuration. + size_t totalConfigurationSize = endpointConfigJson.size(); + for (auto& iter : m_endpointRegistrations) { + if (iter.first == endpointId) { + // skip the current endpoint, since we have already added its configuration size. + continue; + } + // Check pending before in-flight to ensure that the most recent configuration is used, wherever + // possible. + if (m_addOrUpdateEndpoints.pending.find(iter.first) != m_addOrUpdateEndpoints.pending.end()) { + totalConfigurationSize += m_addOrUpdateEndpoints.pending[iter.first].size(); + } else if (m_addOrUpdateEndpoints.inFlight.find(iter.first) != m_addOrUpdateEndpoints.inFlight.end()) { + totalConfigurationSize += m_addOrUpdateEndpoints.inFlight[iter.first].size(); + } else { + totalConfigurationSize += m_endpoints[iter.first].size(); + } + } + + if (totalConfigurationSize > MAX_ENDPOINTS_SIZE_IN_PAYLOAD) { + ACSDK_ERROR( + LX("addOrUpdateEndpointFailed") + .d("reason", "Deduplicated endpoint configurations too big to fit into one discovery event")); + return false; + } + + // All deduplicated endpoints can fit into one discovery message. + // Add the de-duplicated endpoints to the list of pending addOrUpdate endpoints, if they are not already + // present. If any of them are in flight, use that configuration as it is the most recent. + for (auto& iter : m_endpointRegistrations) { + auto dedupedEndpointId = iter.first; + if (dedupedEndpointId == endpointId) { + // skip adding the endpoint here, as it will be added after. + continue; + } + if (m_addOrUpdateEndpoints.pending.find(dedupedEndpointId) != m_addOrUpdateEndpoints.pending.end()) { + // already pending update. Nothing to do. + } else if ( + m_addOrUpdateEndpoints.inFlight.find(dedupedEndpointId) != m_addOrUpdateEndpoints.inFlight.end()) { + // update is in flight. + m_addOrUpdateEndpoints.pending.insert( + std::make_pair(dedupedEndpointId, m_addOrUpdateEndpoints.inFlight[dedupedEndpointId])); + ACSDK_DEBUG0(LX("addOrUpdateEndpoint") + .m("Adding inflight deduplicated Endpoint") + .sensitive("endpointId", dedupedEndpointId)); + } else { + // add de-duped endpoint. + ACSDK_DEBUG0(LX("addOrUpdateEndpoint") + .m("Adding registered deduplicated Endpoint") + .sensitive("endpointId", dedupedEndpointId)); + m_addOrUpdateEndpoints.pending.insert( + std::make_pair(dedupedEndpointId, m_endpoints[dedupedEndpointId])); + } + } + } m_addOrUpdateEndpoints.pending.insert(std::make_pair(endpointId, endpointConfigJson)); } if (!m_currentDiscoveryEventSender) { - m_executor.submit([this] { executeSendPendingEndpoints(); }); + m_executor.execute([this] { executeSendPendingEndpoints(); }); } return true; @@ -336,7 +461,7 @@ bool CapabilitiesDelegate::deleteEndpoint( } } - std::string endpointId = endpointAttributes.endpointId; + const EndpointIdentifier& endpointId = endpointAttributes.endpointId; { std::lock_guard lock{m_endpointsMutex}; @@ -358,6 +483,23 @@ bool CapabilitiesDelegate::deleteEndpoint( return false; } + // Check that this endpoint's registration is not changing + if (m_endpointRegistrations.find(endpointId) != m_endpointRegistrations.end() && + m_endpointRegistrations[endpointId] != endpointAttributes.registration) { + ACSDK_ERROR(LX("deleteEndpointFailed") + .d("reason", "Endpoint registration changed") + .sensitive("endpointId", endpointId)); + return false; + } + + // Disable deleting de-duped endpoints. + if (isEndpointDeduplicated(endpointId)) { + ACSDK_ERROR(LX("deleteEndpointFailed") + .d("reason", "Cannot delete a de-duplicated endpoint") + .sensitive("endpointId", endpointId)); + return false; + } + // Add endpoint to pending list. it = m_deleteEndpoints.pending.find(endpointId); if (m_deleteEndpoints.pending.end() != it) { @@ -371,7 +513,7 @@ bool CapabilitiesDelegate::deleteEndpoint( } if (!m_currentDiscoveryEventSender) { - m_executor.submit([this] { executeSendPendingEndpoints(); }); + m_executor.execute([this] { executeSendPendingEndpoints(); }); } return true; @@ -395,46 +537,100 @@ void CapabilitiesDelegate::executeSendPendingEndpoints() { return; } - if (m_addOrUpdateEndpoints.pending.empty() && m_deleteEndpoints.pending.empty()) { - ACSDK_DEBUG5(LX(__func__).d("Skipped", "No endpoints to register or delete")); - return; - } - - std::unordered_map addOrUpdateEndpointsToSend; + std::vector> addOrUpdateEndpointsToSendVector; std::unordered_map deleteEndpointsToSend; { std::lock_guard lock{m_endpointsMutex}; - // Move pending endpoints to in-flight, since we are going to send them. - m_addOrUpdateEndpoints.inFlight = m_addOrUpdateEndpoints.pending; - addOrUpdateEndpointsToSend = m_addOrUpdateEndpoints.inFlight; - m_addOrUpdateEndpoints.pending.clear(); + if (m_addOrUpdateEndpoints.pending.empty() && m_deleteEndpoints.pending.empty()) { + ACSDK_DEBUG5(LX(__func__).d("Skipped", "No endpoints to register or delete")); + return; + } - m_deleteEndpoints.inFlight = m_deleteEndpoints.pending; - deleteEndpointsToSend = m_deleteEndpoints.inFlight; - m_deleteEndpoints.pending.clear(); + // check if the addOrUpdate Discovery event will need to be split. + size_t totalAddOrUpdateConfigSize = 0; + for (auto& iter : m_addOrUpdateEndpoints.pending) { + totalAddOrUpdateConfigSize += iter.second.size(); + } + + if (totalAddOrUpdateConfigSize <= MAX_ENDPOINTS_SIZE_IN_PAYLOAD) { + ACSDK_DEBUG5(LX("ExecuteSendPendingEndpoints").d("reason", "No need to split discovery message")); + // No split necessary. + m_addOrUpdateEndpoints.inFlight = m_addOrUpdateEndpoints.pending; + addOrUpdateEndpointsToSendVector.push_back(m_addOrUpdateEndpoints.inFlight); + m_addOrUpdateEndpoints.pending.clear(); + + m_deleteEndpoints.inFlight = m_deleteEndpoints.pending; + deleteEndpointsToSend = m_deleteEndpoints.inFlight; + m_deleteEndpoints.pending.clear(); + } else { + // Discovery event will need to be split. + ACSDK_DEBUG5(LX("ExecuteSendPendingEndpoints").d("reason", "Need to split discovery message")); + // first send all endpoints that do not have registration information, i.e. are not de-duplicated. Send the + // endpoints to be deleted as well + std::unordered_map addOrUpdateEndpointsToSend; + for (auto iter = m_addOrUpdateEndpoints.pending.begin(); iter != m_addOrUpdateEndpoints.pending.end();) { + if (!isEndpointDeduplicated(iter->first)) { + addOrUpdateEndpointsToSend.insert(*iter); + m_addOrUpdateEndpoints.inFlight.insert(*iter); + iter = m_addOrUpdateEndpoints.pending.erase(iter); + } else { + ++iter; + } + } + // No non-deduplicated endpoints are pending add or update. + if (!addOrUpdateEndpointsToSend.empty()) { + addOrUpdateEndpointsToSendVector.push_back(addOrUpdateEndpointsToSend); + } + + m_deleteEndpoints.inFlight = m_deleteEndpoints.pending; + deleteEndpointsToSend = m_deleteEndpoints.inFlight; + m_deleteEndpoints.pending.clear(); + + addOrUpdateEndpointsToSend.clear(); + deleteEndpointsToSend.clear(); + + // Send remaining deduplicated endpoints in a separate discovery event, if any. + if (!m_addOrUpdateEndpoints.pending.empty()) { + m_addOrUpdateEndpoints.inFlight = m_addOrUpdateEndpoints.pending; + addOrUpdateEndpointsToSend = m_addOrUpdateEndpoints.inFlight; + m_addOrUpdateEndpoints.pending.clear(); + } + addOrUpdateEndpointsToSendVector.push_back(addOrUpdateEndpointsToSend); + } + } + + for (auto& addOrUpdateEndpointsToSend : addOrUpdateEndpointsToSendVector) { + if (!executeSendDiscoveryEvents(addOrUpdateEndpointsToSend, deleteEndpointsToSend)) { + return; + } + deleteEndpointsToSend.clear(); } +} +bool CapabilitiesDelegate::executeSendDiscoveryEvents( + const std::unordered_map& addOrUpdateEndpointsToSend, + const std::unordered_map& deleteEndpointsToSend) { ACSDK_DEBUG5(LX(__func__) .d("num endpoints to add", addOrUpdateEndpointsToSend.size()) .d("num endpoints to delete", deleteEndpointsToSend.size())); - auto discoveryEventSender = - DiscoveryEventSender::create(addOrUpdateEndpointsToSend, deleteEndpointsToSend, m_authDelegate); + auto discoveryEventSender = DiscoveryEventSender::create( + addOrUpdateEndpointsToSend, deleteEndpointsToSend, m_authDelegate, true, m_metricRecorder); if (!discoveryEventSender) { - ACSDK_ERROR(LX("failedExecuteSendPendingEndpoints").d("reason", "failed to create DiscoveryEventSender")); + ACSDK_ERROR(LX("failedExecuteSendDiscoveryEvents").d("reason", "failed to create DiscoveryEventSender")); moveInFlightEndpointsToPending(); setCapabilitiesState( CapabilitiesDelegateObserverInterface::State::FATAL_ERROR, CapabilitiesDelegateObserverInterface::Error::UNKNOWN_ERROR, getEndpointIdentifiers(addOrUpdateEndpointsToSend), getEndpointIdentifiers(deleteEndpointsToSend)); - return; + return false; } - setDiscoveryEventSender(discoveryEventSender); discoveryEventSender->sendDiscoveryEvents(m_messageSender); + return true; } void CapabilitiesDelegate::onAlexaEventProcessedReceived(const std::string& eventCorrelationToken) { @@ -520,11 +716,18 @@ std::shared_ptr CapabilitiesDelegate::createPostC /// even though no Discovery event was sent. This logic is not required for pending delete endpoints, since /// calls to CapabilitiesDelegate.deleteEndpoint fail when the endpoint is not already cached with /// CapabilitiesDelegate. - if (addOrUpdateEndpointsToSend.empty() && !originalPendingAddOrUpdateEndpoints.empty()) { + auto unchangedPendingAddOrUpdateEndpoints = originalPendingAddOrUpdateEndpoints; + for (const auto& endpoint : addOrUpdateEndpointsToSend) { + auto endpointIt = unchangedPendingAddOrUpdateEndpoints.find(endpoint.first); + if (endpointIt != unchangedPendingAddOrUpdateEndpoints.end()) { + unchangedPendingAddOrUpdateEndpoints.erase(endpointIt); + } + } + if (!unchangedPendingAddOrUpdateEndpoints.empty()) { setCapabilitiesState( CapabilitiesDelegateObserverInterface::State::SUCCESS, CapabilitiesDelegateObserverInterface::Error::SUCCESS, - getEndpointIdentifiers(originalPendingAddOrUpdateEndpoints), + getEndpointIdentifiers(unchangedPendingAddOrUpdateEndpoints), std::vector{}); } @@ -538,8 +741,8 @@ std::shared_ptr CapabilitiesDelegate::createPostC .d("num endpoints to add", addOrUpdateEndpointsToSend.size()) .d("num endpoints to delete", deleteEndpointsToSend.size())); - std::shared_ptr newEventSender = - DiscoveryEventSender::create(addOrUpdateEndpointsToSend, deleteEndpointsToSend, m_authDelegate); + std::shared_ptr newEventSender = DiscoveryEventSender::create( + addOrUpdateEndpointsToSend, deleteEndpointsToSend, m_authDelegate, true, m_metricRecorder, true); if (!newEventSender) { ACSDK_ERROR(LX("createPostConnectOperationFailed").m("Could not create DiscoveryEventSender.")); return nullptr; @@ -562,9 +765,13 @@ void CapabilitiesDelegate::onDiscoveryCompleted( const std::unordered_map& deleteReportEndpoints) { ACSDK_DEBUG5(LX(__func__)); - if (m_addOrUpdateEndpoints.inFlight != addOrUpdateReportEndpoints || - m_deleteEndpoints.inFlight != deleteReportEndpoints) { - ACSDK_WARN(LX(__func__).m("Cached in-flight endpoints do not match endpoints registered to AVS")); + { + std::lock_guard lock{m_endpointsMutex}; + + if (m_addOrUpdateEndpoints.inFlight != addOrUpdateReportEndpoints || + m_deleteEndpoints.inFlight != deleteReportEndpoints) { + ACSDK_WARN(LX(__func__).m("Cached in-flight endpoints do not match endpoints registered to AVS")); + } } auto addOrUpdateReportEndpointIdentifiers = getEndpointIdentifiers(addOrUpdateReportEndpoints); @@ -589,7 +796,7 @@ void CapabilitiesDelegate::onDiscoveryCompleted( addOrUpdateReportEndpointIdentifiers, deleteReportEndpointIdentifiers); - m_executor.submit([this] { executeSendPendingEndpoints(); }); + m_executor.execute([this] { executeSendPendingEndpoints(); }); } void CapabilitiesDelegate::onDiscoveryFailure(MessageRequestObserverInterface::Status status) { @@ -601,13 +808,20 @@ void CapabilitiesDelegate::onDiscoveryFailure(MessageRequestObserverInterface::S } ACSDK_ERROR(LX(__func__).d("reason", status)); - auto addOrUpdateReportEndpointIdentifiers = getEndpointIdentifiers(m_addOrUpdateEndpoints.inFlight); - auto deleteReportEndpointIdentifiers = getEndpointIdentifiers(m_deleteEndpoints.inFlight); - - switch (status) { - case MessageRequestObserverInterface::Status::INVALID_AUTH: + std::vector addOrUpdateReportEndpointIdentifiers; + std::vector deleteReportEndpointIdentifiers; + { + std::lock_guard lock{m_endpointsMutex}; + addOrUpdateReportEndpointIdentifiers = getEndpointIdentifiers(m_addOrUpdateEndpoints.inFlight); + deleteReportEndpointIdentifiers = getEndpointIdentifiers(m_deleteEndpoints.inFlight); + if (status == MessageRequestObserverInterface::Status::INVALID_AUTH || + status == MessageRequestObserverInterface::Status::BAD_REQUEST) { m_addOrUpdateEndpoints.inFlight.clear(); m_deleteEndpoints.inFlight.clear(); + } + } + switch (status) { + case MessageRequestObserverInterface::Status::INVALID_AUTH: resetCurrentDiscoveryEventSender(); setCapabilitiesState( @@ -617,8 +831,6 @@ void CapabilitiesDelegate::onDiscoveryFailure(MessageRequestObserverInterface::S deleteReportEndpointIdentifiers); break; case MessageRequestObserverInterface::Status::BAD_REQUEST: - m_addOrUpdateEndpoints.inFlight.clear(); - m_deleteEndpoints.inFlight.clear(); resetCurrentDiscoveryEventSender(); setCapabilitiesState( @@ -735,7 +947,7 @@ void CapabilitiesDelegate::onConnectionStatusChanged( if (ConnectionStatusObserverInterface::Status::CONNECTED == status) { /// If newly connected, send Discovery events for any endpoints that may have been added or deleted /// during the post-connect stage. - m_executor.submit([this] { executeSendPendingEndpoints(); }); + m_executor.execute([this] { executeSendPendingEndpoints(); }); } } @@ -759,10 +971,16 @@ void CapabilitiesDelegate::addStaleEndpointsToPendingDeleteLocked( ACSDK_ERROR(LX("findEndpointsToDeleteLockedFailed").d("reason", "invalidStoredEndpointConfig")); return; } - + // Do not allow a deduplicated endpoint to be deleted. for (auto& it : *storedEndpointConfig) { if (m_endpoints.end() == m_endpoints.find(it.first) && m_addOrUpdateEndpoints.pending.end() == m_addOrUpdateEndpoints.pending.find(it.first)) { + if (isEndpointDeduplicated(it.first)) { + ACSDK_ERROR(LX(__func__) + .d("reason", "deduplicated endpoint included in deleteReport") + .sensitive("endpointId", it.first)); + return; + } ACSDK_DEBUG9(LX(__func__).d("step", "endpoint included in deleteReport").sensitive("endpointId", it.first)); m_deleteEndpoints.pending.insert({it.first, getDeleteReportEndpointConfigJson(it.first)}); } diff --git a/CapabilitiesDelegate/src/DiscoveryEventSender.cpp b/CapabilitiesDelegate/src/DiscoveryEventSender.cpp index 11106eaef1..3d46f161ed 100644 --- a/CapabilitiesDelegate/src/DiscoveryEventSender.cpp +++ b/CapabilitiesDelegate/src/DiscoveryEventSender.cpp @@ -17,6 +17,8 @@ #include "CapabilitiesDelegate/Utils/DiscoveryUtils.h" #include +#include +#include #include namespace alexaClientSDK { @@ -24,10 +26,20 @@ namespace capabilitiesDelegate { using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::utils::metrics; using namespace capabilitiesDelegate::utils; /// String to identify log entries originating from this file. -static const std::string TAG("DiscoveryEventSender"); +#define TAG "DiscoveryEventSender" + +/// Activity name for post-connect metrics after sending a report. +#define POST_CONNECT_ADD_OR_UPDATE_REPORT_ACTIVITY "PostConnect" TAG "-sendAddOrUpdateReport" + +/// Activity name for post-connect metrics after sending a report. +#define POST_CONNECT_DELETE_REPORT_ACTIVITY "PostConnect" TAG "-sendDeleteReport" + +/// Prefix for post-connect data point with status value. +#define POST_CONNECT_STATUS_PREFIX "STATUS-" /** * Create a LogEntry using the file's TAG and the specified event string. @@ -61,14 +73,21 @@ std::shared_ptr DiscoveryEventSender::create( const std::unordered_map& addOrUpdateReportEndpoints, const std::unordered_map& deleteReportEndpoints, const std::shared_ptr& authDelegate, - const bool waitForEventProcessed) { + bool waitForEventProcessed, + const std::shared_ptr& metricRecorder, + bool postConnect) { if (addOrUpdateReportEndpoints.empty() && deleteReportEndpoints.empty()) { ACSDK_ERROR(LX("createFailed").d("reason", "endpoint map empty")); } else if (!authDelegate) { ACSDK_ERROR(LX("createFailed").d("reason", "invalid auth delegate")); } else { auto instance = std::shared_ptr(new DiscoveryEventSender( - addOrUpdateReportEndpoints, deleteReportEndpoints, authDelegate, waitForEventProcessed)); + addOrUpdateReportEndpoints, + deleteReportEndpoints, + authDelegate, + waitForEventProcessed, + metricRecorder, + postConnect)); return instance; } @@ -79,14 +98,18 @@ DiscoveryEventSender::DiscoveryEventSender( const std::unordered_map& addOrUpdateReportEndpoints, const std::unordered_map& deleteReportEndpoints, const std::shared_ptr& authDelegate, - const bool waitForEventProcessed) : + bool waitForEventProcessed, + const std::shared_ptr& metricRecorder, + bool postConnect) : m_addOrUpdateReportEndpoints{addOrUpdateReportEndpoints}, m_deleteReportEndpoints{deleteReportEndpoints}, m_authDelegate{authDelegate}, + m_metricRecorder{metricRecorder}, m_currentAuthState{AuthObserverInterface::State::UNINITIALIZED}, m_isStopping{false}, m_isSendDiscoveryEventsInvoked{false}, - m_waitForEventProcessed{waitForEventProcessed} { + m_waitForEventProcessed{waitForEventProcessed}, + m_postConnect{postConnect} { } DiscoveryEventSender::~DiscoveryEventSender() { @@ -188,6 +211,7 @@ MessageRequestObserverInterface::Status DiscoveryEventSender::sendDiscoveryEvent const std::shared_ptr& messageSender, const std::string& eventString, bool waitForEventProcessed) { + ACSDK_INFO(LX(__func__)); ACSDK_DEBUG5(LX(__func__).sensitive("discoveryEvent", eventString)); std::unique_lock lock{m_mutex}; m_eventProcessedWaitEvent.reset(); @@ -237,6 +261,27 @@ bool DiscoveryEventSender::sendDiscoveryEventWithRetries( } auto status = sendDiscoveryEvent(messageSender, eventString, isAddOrUpdateReportEvent); + +#ifdef ACSDK_ENABLE_METRICS_RECORDING + if (m_postConnect && m_metricRecorder) { + const auto activityName = isAddOrUpdateReportEvent ? POST_CONNECT_ADD_OR_UPDATE_REPORT_ACTIVITY + : POST_CONNECT_DELETE_REPORT_ACTIVITY; + std::stringstream eventNameBuilder; + eventNameBuilder << POST_CONNECT_STATUS_PREFIX << status; + auto metricEvent = + MetricEventBuilder{} + .setActivityName(activityName) + .addDataPoint(DataPointCounterBuilder{}.setName(eventNameBuilder.str()).increment(1).build()) + .build(); + if (metricEvent) { + m_metricRecorder->recordMetric(std::move(metricEvent)); + } + } +#else + (void)m_postConnect; + (void)m_metricRecorder; +#endif + switch (status) { case MessageRequestObserverInterface::Status::SUCCESS_ACCEPTED: /// Successful response, proceed to send next event if available. @@ -265,12 +310,12 @@ bool DiscoveryEventSender::sendDiscoveryEvents( const std::vector& endpointConfigurations, const std::shared_ptr& messageSender, bool isAddOrUpdateReportEvent) { - int currentEventSize = 0; + size_t currentEventSize = 0; std::vector currentEndpointConfigurationsBuffer; auto currentEndpointConfigIt = endpointConfigurations.begin(); while (currentEndpointConfigIt != endpointConfigurations.end()) { - int currentEndpointConfigSize = currentEndpointConfigIt->size(); + size_t currentEndpointConfigSize = currentEndpointConfigIt->size(); bool sendEvent = false; @@ -309,7 +354,7 @@ bool DiscoveryEventSender::sendDiscoveryEvents( bool DiscoveryEventSender::sendAddOrUpdateReportEvents( const std::shared_ptr& messageSender) { - ACSDK_DEBUG5(LX(__func__).d("num endpoints", m_addOrUpdateReportEndpoints.size())); + ACSDK_INFO(LX(__func__).d("num endpoints", m_addOrUpdateReportEndpoints.size())); if (m_addOrUpdateReportEndpoints.empty()) { ACSDK_DEBUG5(LX(__func__).m("endpoints list empty")); @@ -380,7 +425,7 @@ void DiscoveryEventSender::removeDiscoveryStatusObserver( } void DiscoveryEventSender::reportDiscoveryStatus(MessageRequestObserverInterface::Status status) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_INFO(LX(__func__).d("status", status)); std::shared_ptr observer; { diff --git a/CapabilitiesDelegate/src/PostConnectCapabilitiesPublisher.cpp b/CapabilitiesDelegate/src/PostConnectCapabilitiesPublisher.cpp index 85eeb62e58..e86ebb76d4 100644 --- a/CapabilitiesDelegate/src/PostConnectCapabilitiesPublisher.cpp +++ b/CapabilitiesDelegate/src/PostConnectCapabilitiesPublisher.cpp @@ -30,7 +30,7 @@ using namespace avsCommon::sdkInterfaces; using namespace capabilitiesDelegate::utils; /// String to identify log entries originating from this file. -static const std::string TAG("PostConnectCapabilitiesPublisher"); +#define TAG "PostConnectCapabilitiesPublisher" /** * Create a LogEntry using the file's TAG and the specified event string. @@ -56,11 +56,13 @@ PostConnectCapabilitiesPublisher::PostConnectCapabilitiesPublisher( std::shared_ptr discoveryEventSender) : m_isPerformOperationInvoked{false}, m_discoveryEventSender{std::move(discoveryEventSender)} { + ACSDK_INFO(LX("init").p("this", this)); } PostConnectCapabilitiesPublisher::~PostConnectCapabilitiesPublisher() { ACSDK_DEBUG5(LX(__func__)); m_discoveryEventSender->stop(); + ACSDK_INFO(LX("destroyed").p("this", this)); } unsigned int PostConnectCapabilitiesPublisher::getOperationPriority() { @@ -68,7 +70,7 @@ unsigned int PostConnectCapabilitiesPublisher::getOperationPriority() { } bool PostConnectCapabilitiesPublisher::performOperation(const std::shared_ptr& messageSender) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_INFO(LX(__func__)); if (!messageSender) { ACSDK_ERROR(LX("performOperationFailed").d("reason", "nullPostConnectSender")); return false; @@ -92,7 +94,7 @@ bool PostConnectCapabilitiesPublisher::performOperation(const std::shared_ptrstop(); } diff --git a/CapabilitiesDelegate/src/Storage/SQLiteCapabilitiesDelegateStorage.cpp b/CapabilitiesDelegate/src/Storage/SQLiteCapabilitiesDelegateStorage.cpp index 0fb734095d..9b18a94afc 100644 --- a/CapabilitiesDelegate/src/Storage/SQLiteCapabilitiesDelegateStorage.cpp +++ b/CapabilitiesDelegate/src/Storage/SQLiteCapabilitiesDelegateStorage.cpp @@ -24,7 +24,7 @@ namespace storage { using namespace avsCommon::utils::configuration; /// String to identify log entries originating from this file. -static const std::string TAG("SQLiteCapabilitiesDelegateStorage"); +#define TAG "SQLiteCapabilitiesDelegateStorage" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilitiesDelegate/src/Utils/DiscoveryUtils.cpp b/CapabilitiesDelegate/src/Utils/DiscoveryUtils.cpp index 62c7dcdb3b..6fa4a4a786 100644 --- a/CapabilitiesDelegate/src/Utils/DiscoveryUtils.cpp +++ b/CapabilitiesDelegate/src/Utils/DiscoveryUtils.cpp @@ -25,7 +25,7 @@ #include /// String to identify log entries originating from this file. -static const std::string TAG("DiscoveryUtils"); +#define TAG "DiscoveryUtils" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -113,6 +113,14 @@ static const std::string SCOPE_TYPE_BEARER_TOKEN = "BearerToken"; /// Scope Token key static const std::string SCOPE_TOKEN_KEY = "token"; +/// Maximum number of endpoints for a user. +/// See here for more information: https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery.html#limits +static constexpr size_t MAX_ENDPOINTS = 300; + +/// Maximum capabilities per endpoint. +/// See here for more information: https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery.html#limits +static constexpr size_t MAX_CAPABILITIES_PER_ENDPOINT = 100; + /** * Helper struct to build json objects */ @@ -414,6 +422,14 @@ std::string getDeleteReportEventJson( return buildJsonEventString(header, Optional(), payloadGenerator.toString()); } +size_t getMaxEndpoints() { + return MAX_ENDPOINTS; +} + +size_t getMaxCapabilitiesPerEndpoint() { + return MAX_CAPABILITIES_PER_ENDPOINT; +} + } // namespace utils } // namespace capabilitiesDelegate } // namespace alexaClientSDK diff --git a/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp b/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp index f170dba2fd..d8abc4caf9 100644 --- a/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp +++ b/CapabilitiesDelegate/test/CapabilitiesDelegateTest.cpp @@ -57,6 +57,22 @@ static const std::string EVENT_CORRELATION_TOKEN_KEY = "eventCorrelationToken"; /// Constant representing the timeout for test events. /// @note Use a large enough value that should not fail even in slower systems. static const std::chrono::seconds MY_WAIT_TIMEOUT{5}; +/// A Test client Id. +static const std::string TEST_CLIENT_ID = "TEST_CLIENT_ID"; +/// A Test product Id. +static const std::string TEST_PRODUCT_ID = "TEST_PRODUCT_ID"; +/// A Test serial number. +static const std::string TEST_SERIAL_NUMBER = "TEST_SERIAL_NUMBER"; +/// A Test product Id. +static const std::string TEST_REGISTRATION_KEY = "TEST_REGISTRATION_KEY"; +/// A Test product Id Key. +static const std::string TEST_PRODUCT_ID_KEY = "TEST_PRODUCT_ID_KEY"; +/// A Test manufacturer name. +static const std::string TEST_MANUFACTURER_NAME = "TEST_MANUFACTURER_NAME"; +/// A Test description. +static const std::string TEST_DESCRIPTION = "TEST_DESCRIPTION"; +/// A Test display category. +static const std::string TEST_DISPLAY_CATEGORY = "TEST_DISPLAY_CATEGORY"; /** * Structure to store event data from a Discovery event JSON. @@ -79,13 +95,29 @@ AVSDiscoveryEndpointAttributes createEndpointAttributes(const std::string endpoi AVSDiscoveryEndpointAttributes attributes; attributes.endpointId = endpointId; - attributes.description = "TEST_DESCRIPTION"; - attributes.manufacturerName = "TEST_MANUFACTURER_NAME"; - attributes.displayCategories = {"TEST_DISPLAY_CATEGORY"}; + attributes.description = TEST_DESCRIPTION; + attributes.manufacturerName = TEST_MANUFACTURER_NAME; + attributes.displayCategories = {TEST_DISPLAY_CATEGORY}; return attributes; } +/** + * Create a test @c AVSDiscoveryEndpointAttributes::Registration object. + * @param productId Optional product Id to use in these attributes. + * @param serialNumber Optional device serial number to use in these attributes. + * @param registrationKey Optional registration key to use in these attributes. + * @param productIdKey Optional product Id Key to use in these attributes. + * @return a @c AVSDiscoveryEndpointAttributes::Registration structure. + */ +AVSDiscoveryEndpointAttributes::Registration createEndpointRegistration( + const std::string productID = TEST_PRODUCT_ID, + const std::string serialNumber = TEST_SERIAL_NUMBER, + const std::string registrationKey = TEST_REGISTRATION_KEY, + const std::string productIdKey = TEST_PRODUCT_ID_KEY) { + return AVSDiscoveryEndpointAttributes::Registration(productID, serialNumber, registrationKey, productIdKey); +} + /** * Creates a test @c CapabilityConfiguration. * @return a @c CapabilityConfiguration structure. @@ -124,6 +156,12 @@ class CapabilitiesDelegateTest : public Test { /// Helper that validates dynamically adding an endpoint. Used for testing dynamic delete. void addEndpoint(AVSDiscoveryEndpointAttributes attributes, CapabilityConfiguration configuration); + /// Helper that validates dynamically adding an endpoint. Used for testing dynamic delete. + void addEndpointToCapabilitiesDelegate( + std::shared_ptr, + AVSDiscoveryEndpointAttributes attributes, + CapabilityConfiguration configuration); + /* * Gets the event correlation token string. * @param request The message request to parse. @@ -162,6 +200,7 @@ void CapabilitiesDelegateTest::SetUp() { /// Expect calls to storage. EXPECT_CALL(*m_mockCapabilitiesStorage, open()).WillOnce(Return(true)); m_capabilitiesDelegate = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); + ASSERT_NE(m_capabilitiesDelegate, nullptr); /// Add a new observer and it receives notifications of the current capabilities state. @@ -281,6 +320,7 @@ TEST_F(CapabilitiesDelegateTest, test_init) { EXPECT_CALL(*m_mockCapabilitiesStorage, createDatabase()).WillOnce(Return(true)); instance = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); + ASSERT_NE(instance, nullptr); instance->shutdown(); } @@ -656,8 +696,8 @@ TEST_F(CapabilitiesDelegateTest, test_createPostConnectOperationWithDifferentEnd })); auto instance = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); - instance->addOrUpdateEndpoint(endpointAttributes, {capabilityConfig}); + instance->addOrUpdateEndpoint(endpointAttributes, {capabilityConfig}); /// Endpoint config is different from the endpoint config created with the test endpoint attributes so a /// post connect operation is created. auto publisher = instance->createPostConnectOperation(); @@ -1032,6 +1072,69 @@ TEST_F( instance->shutdown(); } +/** + * Tests if the createPostConnectOperation() creates a new @c PostConnectCapabilitiesPublisher when registered + * endpoint configurations are same as the ones in storage, but there is one additional stored endpoint that is + * not registered (and needs to be deleted). + * Tests CapabilitiesObservers are notified of added endpoints even though they were not published in an event. + * Tests if CapabilitiesDelegate returns a non-null post connect operation, since there is a stale endpoint to delete. + */ +TEST_F( + CapabilitiesDelegateTest, + test_createPostConnectOperationWithNewEndpointAndPendingEndpointsWithSameEndpointConfigs) { + auto unchangedEndpointAttributes = createEndpointAttributes("endpointId"); + auto unchangedEndpointConfiguration = createCapabilityConfiguration(); + std::vector unchangedCapabilityConfigs = {unchangedEndpointConfiguration}; + + auto newEndpointAttributes = createEndpointAttributes("newEndpointId"); + auto newEndpointConfiguration = createCapabilityConfiguration(); + std::vector newCapabilityConfigs = {newEndpointConfiguration}; + + std::string unchangedEndpointConfig = + utils::getEndpointConfigJson(unchangedEndpointAttributes, unchangedCapabilityConfigs); + std::string newEndpointConfig = utils::getEndpointConfigJson(newEndpointAttributes, newCapabilityConfigs); + EXPECT_CALL(*m_mockCapabilitiesStorage, open()).Times(1).WillOnce(Return(true)); + EXPECT_CALL(*m_mockCapabilitiesStorage, load(_)) + .Times(1) + .WillOnce( + Invoke([unchangedEndpointAttributes, unchangedEndpointConfig, newEndpointAttributes, newEndpointConfig]( + std::unordered_map* storedEndpoints) { + storedEndpoints->insert({unchangedEndpointAttributes.endpointId, unchangedEndpointConfig}); + return true; + })); + EXPECT_CALL(*m_mockCapabilitiesDelegateObserver, onCapabilitiesStateChange(_, _, _, _)) + .WillOnce(Invoke([](CapabilitiesDelegateObserverInterface::State newState, + CapabilitiesDelegateObserverInterface::Error newError, + std::vector addOrUpdateReportEndpointIdentifiers, + std::vector deleteReportEndpointIdentifiers) { + EXPECT_EQ(newState, CapabilitiesDelegateObserverInterface::State::UNINITIALIZED); + EXPECT_EQ(newError, CapabilitiesDelegateObserverInterface::Error::UNINITIALIZED); + EXPECT_EQ(addOrUpdateReportEndpointIdentifiers, std::vector{}); + EXPECT_EQ(deleteReportEndpointIdentifiers, std::vector{}); + })); + + auto instance = CapabilitiesDelegate::create(m_mockAuthDelegate, m_mockCapabilitiesStorage, m_dataManager); + instance->addCapabilitiesObserver(m_mockCapabilitiesDelegateObserver); + instance->addOrUpdateEndpoint(unchangedEndpointAttributes, unchangedCapabilityConfigs); + instance->addOrUpdateEndpoint(newEndpointAttributes, newCapabilityConfigs); + + /// Observer callback should only contain the pending endpoint to add (since that is already registered), + /// but not the stale endpoint to delete (since that still needs to be sent to AVS). + EXPECT_CALL( + *m_mockCapabilitiesDelegateObserver, + onCapabilitiesStateChange( + CapabilitiesDelegateObserverInterface::State::SUCCESS, + CapabilitiesDelegateObserverInterface::Error::SUCCESS, + std::vector{unchangedEndpointAttributes.endpointId}, + std::vector{})); + + auto publisher = instance->createPostConnectOperation(); + ASSERT_NE(publisher, nullptr); + + // Clean-up. + instance->shutdown(); +} + /** * Tests if before the stale endpoint is deleted and the stale endpoint is added, that the first * createPostConnectOperation will create a deleteReport for the stale endpoint, but the second @@ -1534,6 +1637,537 @@ TEST_F(CapabilitiesDelegateTest, test_duplicateEndpointInPendingAddOrUpdateAndDe ASSERT_FALSE(m_capabilitiesDelegate->addOrUpdateEndpoint(deleteEndpointAttributes, {capabilityConfig})); } +/** + * Test endpoint registrations. + * Confirm that changing the registration of an endpoint results in a failure. + * Also confirm that adding an endpoint with a different registration results in a failure. + */ +TEST_F(CapabilitiesDelegateTest, test_registration) { + const std::string endpointID1{"TEST_ENDPOINT_ID_1"}; + const std::string endpointID2{"TEST_ENDPOINT_ID_2"}; + const std::string endpointID3{"TEST_ENDPOINT_ID_3"}; + + /// Configure endpointId1 attributes with a non-empty registration. + auto endpointAttributes1 = createEndpointAttributes(endpointID1); + endpointAttributes1.registration = createEndpointRegistration(); + + auto capabilityConfig = createCapabilityConfiguration(); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes1, {capabilityConfig})); + + /// Try endpointId1 with empty registration. + auto updatedEndpointAttributes1 = createEndpointAttributes(endpointID1); + ASSERT_FALSE(m_capabilitiesDelegate->addOrUpdateEndpoint(updatedEndpointAttributes1, {capabilityConfig})); + + /// Configure endpointId2 attributes with a non-empty registration. + auto endpointAttributes2 = createEndpointAttributes(endpointID2); + endpointAttributes2.registration = createEndpointRegistration(); + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes2, {capabilityConfig})); + + /// Try endpointId2 with non-empty registration. + auto updatedEndpointAttributes2 = createEndpointAttributes(endpointID2); + updatedEndpointAttributes2.registration = createEndpointRegistration("UPDATED_PRODUCT_ID"); + ASSERT_FALSE(m_capabilitiesDelegate->addOrUpdateEndpoint(updatedEndpointAttributes2, {capabilityConfig})); + + /// Try endpointId3 with different and non-empty registration. + auto updatedEndpointAttributes3 = createEndpointAttributes(endpointID3); + updatedEndpointAttributes3.registration = createEndpointRegistration("UPDATED_PRODUCT_ID"); + ASSERT_FALSE(m_capabilitiesDelegate->addOrUpdateEndpoint(updatedEndpointAttributes3, {capabilityConfig})); +} + +/** + * Test adding 3 endpoints that share registration information (i.e. they are de-duplicated in the cloud). + * Verify that other endpoints are sent in the discovery message whenever an endpoint is added. + * Finally, update the first endpoint that was sent and confirm that all endpoints are sent in the discovery message. + */ +TEST_F(CapabilitiesDelegateTest, test_addAndUpdateOfDeduplicatedEndpoints) { + m_capabilitiesDelegate->onConnectionStatusChanged( + ConnectionStatusObserverInterface::Status::CONNECTED, + ConnectionStatusObserverInterface::ChangedReason::SUCCESS); + + WaitEvent e; + validateAuthDelegate(); + + const std::string endpointID1{"TEST_ENDPOINT_ID_1"}; + const std::string endpointID2{"TEST_ENDPOINT_ID_2"}; + const std::string endpointID3{"TEST_ENDPOINT_ID_3"}; + + /// Set-up. + auto endpointAttributes1 = createEndpointAttributes(endpointID1); + endpointAttributes1.registration = createEndpointRegistration(); + + auto endpointAttributes2 = createEndpointAttributes(endpointID2); + endpointAttributes2.registration = createEndpointRegistration(); + + auto endpointAttributes3 = createEndpointAttributes(endpointID3); + endpointAttributes3.registration = createEndpointRegistration(); + + auto capabilityConfig = createCapabilityConfiguration(); + + /// endpointId1 is being registered first. The Discovery message should only contain it. + /// Expect calls to MessageSender. + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)) + .Times(Exactly(1)) + .WillOnce(Invoke([this](std::shared_ptr request) { + std::string eventCorrelationTokenString; + getEventCorrelationTokenString(request, eventCorrelationTokenString); + + request->sendCompleted(MessageRequestObserverInterface::Status::SUCCESS_ACCEPTED); + m_capabilitiesDelegate->onAlexaEventProcessedReceived(eventCorrelationTokenString); + })); + /// Expect call to storage. + EXPECT_CALL(*m_mockCapabilitiesStorage, store(_)).WillOnce(Return(true)); + EXPECT_CALL(*m_mockCapabilitiesStorage, erase((std::unordered_map{}))) + .WillOnce(Return(true)); + + /// Expect callback to CapabilitiesObserver. + EXPECT_CALL(*m_mockCapabilitiesDelegateObserver, onCapabilitiesStateChange(_, _, _, _)) + .WillOnce(Invoke([&](CapabilitiesDelegateObserverInterface::State state, + CapabilitiesDelegateObserverInterface::Error error, + std::vector addedEndpoints, + std::vector deletedEndpoints) { + EXPECT_EQ(state, CapabilitiesDelegateObserverInterface::State::SUCCESS); + EXPECT_EQ(error, CapabilitiesDelegateObserverInterface::Error::SUCCESS); + EXPECT_EQ(addedEndpoints, std::vector{endpointID1}); + EXPECT_EQ(deletedEndpoints, (std::vector{})); + + e.wakeUp(); + })); + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes1, {capabilityConfig})); + ASSERT_TRUE(e.wait(MY_WAIT_TIMEOUT)); + e.reset(); + + /// endpointId1 has already been registered. Confirm that it is added when endpointId2 is added. + /// Expect calls to MessageSender. + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)) + .Times(Exactly(1)) + .WillOnce(Invoke([this](std::shared_ptr request) { + std::string eventCorrelationTokenString; + getEventCorrelationTokenString(request, eventCorrelationTokenString); + + request->sendCompleted(MessageRequestObserverInterface::Status::SUCCESS_ACCEPTED); + m_capabilitiesDelegate->onAlexaEventProcessedReceived(eventCorrelationTokenString); + })); + /// Expect call to storage. + EXPECT_CALL(*m_mockCapabilitiesStorage, store(_)).WillOnce(Return(true)); + EXPECT_CALL(*m_mockCapabilitiesStorage, erase((std::unordered_map{}))) + .WillOnce(Return(true)); + + /// Expect callback to CapabilitiesObserver. + EXPECT_CALL(*m_mockCapabilitiesDelegateObserver, onCapabilitiesStateChange(_, _, _, _)) + .WillOnce(Invoke([&](CapabilitiesDelegateObserverInterface::State state, + CapabilitiesDelegateObserverInterface::Error error, + std::vector addedEndpoints, + std::vector deletedEndpoints) { + std::vector expectedAddedEndpoints{endpointID1, endpointID2}; + std::sort(expectedAddedEndpoints.begin(), expectedAddedEndpoints.end()); + std::sort(addedEndpoints.begin(), addedEndpoints.end()); + EXPECT_EQ(state, CapabilitiesDelegateObserverInterface::State::SUCCESS); + EXPECT_EQ(error, CapabilitiesDelegateObserverInterface::Error::SUCCESS); + EXPECT_EQ(addedEndpoints, expectedAddedEndpoints); + EXPECT_EQ(deletedEndpoints, (std::vector{})); + + e.wakeUp(); + })); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes2, {capabilityConfig})); + ASSERT_TRUE(e.wait(MY_WAIT_TIMEOUT)); + e.reset(); + + /// endpointId1 and endpointId2 have already been registered. Confirm that they are added when endpointId3 is added. + /// Expect calls to MessageSender. + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)) + .Times(Exactly(1)) + .WillOnce(Invoke([this](std::shared_ptr request) { + std::string eventCorrelationTokenString; + getEventCorrelationTokenString(request, eventCorrelationTokenString); + + request->sendCompleted(MessageRequestObserverInterface::Status::SUCCESS_ACCEPTED); + m_capabilitiesDelegate->onAlexaEventProcessedReceived(eventCorrelationTokenString); + })); + /// Expect call to storage. + EXPECT_CALL(*m_mockCapabilitiesStorage, store(_)).WillOnce(Return(true)); + EXPECT_CALL(*m_mockCapabilitiesStorage, erase((std::unordered_map{}))) + .WillOnce(Return(true)); + + /// Expect callback to CapabilitiesObserver. + EXPECT_CALL(*m_mockCapabilitiesDelegateObserver, onCapabilitiesStateChange(_, _, _, _)) + .WillOnce(Invoke([&](CapabilitiesDelegateObserverInterface::State state, + CapabilitiesDelegateObserverInterface::Error error, + std::vector addedEndpoints, + std::vector deletedEndpoints) { + std::vector expectedAddedEndpoints{endpointID1, endpointID2, endpointID3}; + std::sort(expectedAddedEndpoints.begin(), expectedAddedEndpoints.end()); + std::sort(addedEndpoints.begin(), addedEndpoints.end()); + EXPECT_EQ(state, CapabilitiesDelegateObserverInterface::State::SUCCESS); + EXPECT_EQ(error, CapabilitiesDelegateObserverInterface::Error::SUCCESS); + EXPECT_EQ(addedEndpoints, expectedAddedEndpoints); + EXPECT_EQ(deletedEndpoints, (std::vector{})); + + e.wakeUp(); + })); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes3, {capabilityConfig})); + ASSERT_TRUE(e.wait(MY_WAIT_TIMEOUT)); + e.reset(); + + /// Update the configuration + std::string additionalAttribute(256 * 10, 'X'); + std::map additionalAttributes; + additionalAttributes["test"] = "{\"test\":\"" + additionalAttribute + "\"}"; + auto updatedCapabilityConfig = createCapabilityConfiguration(additionalAttributes); + + auto updatedEndpointAttributes1 = createEndpointAttributes(endpointID1); + updatedEndpointAttributes1.registration = createEndpointRegistration(); + + /// endpointId1, endpointId2 and endpointId3 have already been registered. Confirm that they are added when + /// endpointId1 is updated. + /// Expect calls to MessageSender. + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)) + .Times(Exactly(1)) + .WillOnce(Invoke([this](std::shared_ptr request) { + std::string eventCorrelationTokenString; + getEventCorrelationTokenString(request, eventCorrelationTokenString); + + request->sendCompleted(MessageRequestObserverInterface::Status::SUCCESS_ACCEPTED); + m_capabilitiesDelegate->onAlexaEventProcessedReceived(eventCorrelationTokenString); + })); + /// Expect call to storage. + EXPECT_CALL(*m_mockCapabilitiesStorage, store(_)).WillOnce(Return(true)); + EXPECT_CALL(*m_mockCapabilitiesStorage, erase((std::unordered_map{}))) + .WillOnce(Return(true)); + + /// Expect callback to CapabilitiesObserver. + EXPECT_CALL(*m_mockCapabilitiesDelegateObserver, onCapabilitiesStateChange(_, _, _, _)) + .WillOnce(Invoke([&](CapabilitiesDelegateObserverInterface::State state, + CapabilitiesDelegateObserverInterface::Error error, + std::vector addedEndpoints, + std::vector deletedEndpoints) { + std::vector expectedAddedEndpoints{endpointID1, endpointID2, endpointID3}; + std::sort(expectedAddedEndpoints.begin(), expectedAddedEndpoints.end()); + std::sort(addedEndpoints.begin(), addedEndpoints.end()); + EXPECT_EQ(state, CapabilitiesDelegateObserverInterface::State::SUCCESS); + EXPECT_EQ(error, CapabilitiesDelegateObserverInterface::Error::SUCCESS); + EXPECT_EQ(addedEndpoints, expectedAddedEndpoints); + EXPECT_EQ(deletedEndpoints, (std::vector{})); + + e.wakeUp(); + })); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(updatedEndpointAttributes1, {updatedCapabilityConfig})); + ASSERT_TRUE(e.wait(MY_WAIT_TIMEOUT)); +} + +/** + * Test that deleting a de-duplicated endpoint fails. + */ +TEST_F(CapabilitiesDelegateTest, test_deduplictedDeletionFailure) { + m_capabilitiesDelegate->onConnectionStatusChanged( + ConnectionStatusObserverInterface::Status::CONNECTED, + ConnectionStatusObserverInterface::ChangedReason::SUCCESS); + + WaitEvent e; + validateAuthDelegate(); + + const std::string endpointID1{"TEST_ENDPOINT_ID_1"}; + const std::string endpointID2{"TEST_ENDPOINT_ID_2"}; + + auto endpointAttributes1 = createEndpointAttributes(endpointID1); + endpointAttributes1.registration = createEndpointRegistration(); + + auto capabilityConfig = createCapabilityConfiguration(); + auto capabilityConfigJson = utils::getEndpointConfigJson(endpointAttributes1, {capabilityConfig}); + + /// endpointId1 is being registered first. The Discovery message should only contain it. + /// Expect calls to MessageSender. + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)) + .Times(Exactly(1)) + .WillOnce(Invoke([this](std::shared_ptr request) { + std::string eventCorrelationTokenString; + getEventCorrelationTokenString(request, eventCorrelationTokenString); + + request->sendCompleted(MessageRequestObserverInterface::Status::SUCCESS_ACCEPTED); + m_capabilitiesDelegate->onAlexaEventProcessedReceived(eventCorrelationTokenString); + })); + /// Expect call to storage. + EXPECT_CALL(*m_mockCapabilitiesStorage, store(_)).WillOnce(Return(true)); + EXPECT_CALL(*m_mockCapabilitiesStorage, erase((std::unordered_map{}))) + .WillOnce(Return(true)); + + /// Expect callback to CapabilitiesObserver. + EXPECT_CALL(*m_mockCapabilitiesDelegateObserver, onCapabilitiesStateChange(_, _, _, _)) + .WillOnce(Invoke([&](CapabilitiesDelegateObserverInterface::State state, + CapabilitiesDelegateObserverInterface::Error error, + std::vector addedEndpoints, + std::vector deletedEndpoints) { + EXPECT_EQ(state, CapabilitiesDelegateObserverInterface::State::SUCCESS); + EXPECT_EQ(error, CapabilitiesDelegateObserverInterface::Error::SUCCESS); + EXPECT_EQ(addedEndpoints, std::vector{endpointID1}); + EXPECT_EQ(deletedEndpoints, (std::vector{})); + + e.wakeUp(); + })); + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes1, {capabilityConfig})); + ASSERT_TRUE(e.wait(MY_WAIT_TIMEOUT)); + e.reset(); + + /// Deleting endpoint 1 should fail. + ASSERT_FALSE(m_capabilitiesDelegate->deleteEndpoint(endpointAttributes1, {capabilityConfig})); +} + +/** + * Test adding two pairs of 3 endpoints that share registration information (i.e. they are de-duplicated in the cloud). + * For this test, endpointid3 and endpointId6 have large configurations, preventing them from being sent in the same + * message. + * Verify that the endpoints are split into two messages, each containing 3 endpoints. + */ +TEST_F(CapabilitiesDelegateTest, test_SplitMessagePendingDeduplicatedEndpoints) { + m_capabilitiesDelegate->onConnectionStatusChanged( + ConnectionStatusObserverInterface::Status::DISCONNECTED, + ConnectionStatusObserverInterface::ChangedReason::SERVER_SIDE_DISCONNECT); + + WaitEvent e; + validateAuthDelegate(); + + const std::string endpointID1{"TEST_ENDPOINT_ID_1"}; + const std::string endpointID2{"TEST_ENDPOINT_ID_2"}; + const std::string endpointID3{"TEST_ENDPOINT_ID_3"}; + + /// Set-up the first set of deduplicated endpoints. + auto endpointAttributes1 = createEndpointAttributes(endpointID1); + endpointAttributes1.registration = createEndpointRegistration(); + + auto endpointAttributes2 = createEndpointAttributes(endpointID2); + endpointAttributes2.registration = createEndpointRegistration(); + + auto endpointAttributes3 = createEndpointAttributes(endpointID3); + endpointAttributes3.registration = createEndpointRegistration(); + + const std::string endpointID4{"TEST_ENDPOINT_ID_4"}; + const std::string endpointID5{"TEST_ENDPOINT_ID_5"}; + const std::string endpointID6{"TEST_ENDPOINT_ID_6"}; + + /// Set-up the second set of endpoints. + auto endpointAttributes4 = createEndpointAttributes(endpointID4); + + auto endpointAttributes5 = createEndpointAttributes(endpointID5); + + auto endpointAttributes6 = createEndpointAttributes(endpointID6); + + /// Create a large capability configuration for endpoint3 and endpoint6. + std::string additionalAttribute(240 * 1024, 'X'); + std::map additionalAttributes; + additionalAttributes["test"] = "{\"test\":\"" + additionalAttribute + "\"}"; + auto largeCapabilityConfig = createCapabilityConfiguration(additionalAttributes); + + /// Create a default capability configuration for the other endpoints. + auto capabilityConfig = createCapabilityConfiguration(); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes1, {capabilityConfig})); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes2, {capabilityConfig})); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes3, {largeCapabilityConfig})); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes4, {capabilityConfig})); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes5, {capabilityConfig})); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes6, {largeCapabilityConfig})); + + /// Upon connect, expect two messages to be sent. One with endpoints 1,2 and 3, and the other with endpoints 4, 5 + /// and 6. Expect calls to MessageSender. + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)) + .Times(Exactly(2)) + .WillRepeatedly(Invoke([this](std::shared_ptr request) { + std::string eventCorrelationTokenString; + getEventCorrelationTokenString(request, eventCorrelationTokenString); + + request->sendCompleted(MessageRequestObserverInterface::Status::SUCCESS_ACCEPTED); + m_capabilitiesDelegate->onAlexaEventProcessedReceived(eventCorrelationTokenString); + })); + /// Expect call to storage. + EXPECT_CALL(*m_mockCapabilitiesStorage, store(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*m_mockCapabilitiesStorage, erase((std::unordered_map{}))) + .WillRepeatedly(Return(true)); + + /// Expect callback to CapabilitiesObserver. + std::vector> expectedAddedEndpoints{{endpointID1, endpointID2, endpointID3}, + {endpointID4, endpointID5, endpointID6}}; + int other{0}; + EXPECT_CALL(*m_mockCapabilitiesDelegateObserver, onCapabilitiesStateChange(_, _, _, _)) + .Times(Exactly(2)) + .WillOnce(Invoke([&](CapabilitiesDelegateObserverInterface::State state, + CapabilitiesDelegateObserverInterface::Error error, + std::vector addedEndpoints, + std::vector deletedEndpoints) { + std::sort(addedEndpoints.begin(), addedEndpoints.end()); + EXPECT_EQ(state, CapabilitiesDelegateObserverInterface::State::SUCCESS); + EXPECT_EQ(error, CapabilitiesDelegateObserverInterface::Error::SUCCESS); + if (addedEndpoints[0] == endpointID1) { + other = 1; + EXPECT_EQ(addedEndpoints, expectedAddedEndpoints[0]); + } else { + EXPECT_EQ(addedEndpoints, expectedAddedEndpoints[1]); + } + EXPECT_EQ(deletedEndpoints, (std::vector{})); + })) + .WillOnce(Invoke([&](CapabilitiesDelegateObserverInterface::State state, + CapabilitiesDelegateObserverInterface::Error error, + std::vector addedEndpoints, + std::vector deletedEndpoints) { + std::sort(addedEndpoints.begin(), addedEndpoints.end()); + EXPECT_EQ(state, CapabilitiesDelegateObserverInterface::State::SUCCESS); + EXPECT_EQ(error, CapabilitiesDelegateObserverInterface::Error::SUCCESS); + EXPECT_EQ(addedEndpoints, expectedAddedEndpoints[other]); + EXPECT_EQ(deletedEndpoints, (std::vector{})); + e.wakeUp(); + })); + + m_capabilitiesDelegate->onConnectionStatusChanged( + ConnectionStatusObserverInterface::Status::CONNECTED, + ConnectionStatusObserverInterface::ChangedReason::SUCCESS); + + ASSERT_TRUE(e.wait(MY_WAIT_TIMEOUT)); +} + +/** + * Test limits of the Alexa.Discovery interface. + * Specifically test : + * 1. Adding more than 300 deduplicated endpoints triggers a failure. + * 2. Having more than 100 capabilities per deduplicated endpoint triggers a failure. + * This is because a single discovery message cannot contain more than 300 endpoints, per the limits imposed here: + * https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery.html#limits + */ +TEST_F(CapabilitiesDelegateTest, test_endpointLimits) { + // Create deduplicated endpoints + for (size_t i = 0; i < getMaxEndpoints(); ++i) { + const std::string testEndpointID = "TEST_ENDPOINT_ID_" + std::to_string(i); + + auto endpointAttributes = createEndpointAttributes(testEndpointID); + endpointAttributes.registration = createEndpointRegistration(); + auto capabilityConfig = createCapabilityConfiguration(); + + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes, {capabilityConfig})); + } + const std::string lastEndpointID = "TEST_ENDPOINT_ID_LAST"; + auto lastEndpointAttributes = createEndpointAttributes(lastEndpointID); + lastEndpointAttributes.registration = createEndpointRegistration(); + auto lastCapabilityConfig = createCapabilityConfiguration(); + + ASSERT_FALSE(m_capabilitiesDelegate->addOrUpdateEndpoint(lastEndpointAttributes, {lastCapabilityConfig})); + + const std::string endpointID = "TEST_ENDPOINT_ID"; + + /// Add a deduplicated endpoint with more than MAX_CAPABILITIES_PER_ENDPOINT capabilities. + auto endpointAttributes = createEndpointAttributes(endpointID); + endpointAttributes.registration = createEndpointRegistration(); + std::vector largeConfig; + for (size_t i = 0; i <= getMaxCapabilitiesPerEndpoint(); ++i) { + largeConfig.push_back(createCapabilityConfiguration()); + } + + ASSERT_FALSE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes, largeConfig)); +} + +/** + * Test updating a deduplicated endpoint when it is in flight. + */ +TEST_F(CapabilitiesDelegateTest, test_updateDeduplicatedEndpointWhenInflight) { + m_capabilitiesDelegate->onConnectionStatusChanged( + ConnectionStatusObserverInterface::Status::CONNECTED, + ConnectionStatusObserverInterface::ChangedReason::SUCCESS); + WaitEvent e; + validateAuthDelegate(); + + const std::string endpointID{"TEST_ENDPOINT_ID"}; + + /// Set-up. + auto endpointAttributes = createEndpointAttributes(endpointID); + endpointAttributes.registration = createEndpointRegistration(); + + auto capabilityConfig = createCapabilityConfiguration(); + + addEndpoint(endpointAttributes, {capabilityConfig}); + + /// Add a DiscoveryEventSender to simulate Discovery event in-flight. + auto discoveryEventSender = std::make_shared>(); + EXPECT_CALL(*discoveryEventSender, addDiscoveryStatusObserver(_)) + .WillOnce(Invoke([this](const std::shared_ptr& observer) { + EXPECT_EQ(observer, m_capabilitiesDelegate); + })); + EXPECT_CALL(*discoveryEventSender, removeDiscoveryStatusObserver(_)) + .WillOnce(Invoke([this](const std::shared_ptr& observer) { + EXPECT_EQ(observer, m_capabilitiesDelegate); + })); + EXPECT_CALL(*discoveryEventSender, stop()); + m_capabilitiesDelegate->setDiscoveryEventSender(discoveryEventSender); + + /// Create an updated capability configuration for the update to endpoint1. + std::string additionalAttribute(2 * 1024, 'X'); + std::map additionalAttributes; + additionalAttributes["test"] = "{\"test\":\"" + additionalAttribute + "\"}"; + auto updatedCapabilityConfig = createCapabilityConfiguration(additionalAttributes); + + /// Expect no callback to CapabilitiesObserver, since the update remains in pending. + EXPECT_CALL( + *m_mockCapabilitiesDelegateObserver, + onCapabilitiesStateChange(_, _, std::vector{endpointID}, std::vector{})) + .Times(0); + + /// endpointId1 is inflight. Should be able to add it again to the pending update set. + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes, {updatedCapabilityConfig})); +} + +/** + * Test adding a deduplicated endpoint when another one is in flight. + * Verify that the first endpoint is also sent in the discovery message when the second endpoint is added. + */ +TEST_F(CapabilitiesDelegateTest, test_addDeduplicatedEndpointWhenOtherInflight) { + m_capabilitiesDelegate->onConnectionStatusChanged( + ConnectionStatusObserverInterface::Status::CONNECTED, + ConnectionStatusObserverInterface::ChangedReason::SUCCESS); + + WaitEvent e; + validateAuthDelegate(); + + const std::string endpointID1{"TEST_ENDPOINT_ID_1"}; + const std::string endpointID2{"TEST_ENDPOINT_ID_2"}; + + /// Set-up. + auto endpointAttributes1 = createEndpointAttributes(endpointID1); + endpointAttributes1.registration = createEndpointRegistration(); + + auto endpointAttributes2 = createEndpointAttributes(endpointID2); + endpointAttributes2.registration = createEndpointRegistration(); + + auto capabilityConfig = createCapabilityConfiguration(); + + addEndpoint(endpointAttributes1, {capabilityConfig}); + + /// Add a DiscoveryEventSender to simulate Discovery event in-flight. + auto discoveryEventSender = std::make_shared>(); + EXPECT_CALL(*discoveryEventSender, addDiscoveryStatusObserver(_)) + .WillOnce(Invoke([this](const std::shared_ptr& observer) { + EXPECT_EQ(observer, m_capabilitiesDelegate); + })); + EXPECT_CALL(*discoveryEventSender, removeDiscoveryStatusObserver(_)) + .WillOnce(Invoke([this](const std::shared_ptr& observer) { + EXPECT_EQ(observer, m_capabilitiesDelegate); + })); + EXPECT_CALL(*discoveryEventSender, stop()); + m_capabilitiesDelegate->setDiscoveryEventSender(discoveryEventSender); + + /// Expect no callback to CapabilitiesObserver, since the add of endpoint2 remains in pending. + EXPECT_CALL( + *m_mockCapabilitiesDelegateObserver, + onCapabilitiesStateChange(_, _, std::vector{endpointID1, endpointID2}, std::vector{})) + .Times(0); + + /// Endpoint1 is in flight. Confirm that Endpoint2 is added to the pending endpoints. + ASSERT_TRUE(m_capabilitiesDelegate->addOrUpdateEndpoint(endpointAttributes2, {capabilityConfig})); +} + } // namespace test } // namespace capabilitiesDelegate } // namespace alexaClientSDK diff --git a/CapabilityAgents/AIP/include/AIP/ASRProfile.h b/CapabilityAgents/AIP/include/AIP/ASRProfile.h index 1e4bf8774c..e69fcace61 100644 --- a/CapabilityAgents/AIP/include/AIP/ASRProfile.h +++ b/CapabilityAgents/AIP/include/AIP/ASRProfile.h @@ -34,7 +34,9 @@ enum class ASRProfile { /// Cloud determines end of user speech (0 to 5 ft). NEAR_FIELD, /// Cloud determines end of user speech (0 to 20+ ft). - FAR_FIELD + FAR_FIELD, + /// Indicates that ASR profile is undefined. + UNDEFINED }; /** @@ -55,6 +57,9 @@ inline std::ostream& operator<<(std::ostream& stream, ASRProfile profile) { case ASRProfile::FAR_FIELD: stream << "FAR_FIELD"; break; + case ASRProfile::UNDEFINED: + stream << ""; + break; } return stream; } @@ -73,10 +78,31 @@ inline std::string asrProfileToString(ASRProfile profile) { return "NEAR_FIELD"; case ASRProfile::FAR_FIELD: return "FAR_FIELD"; + case ASRProfile::UNDEFINED: + return ""; } return ""; } +/** + * This function reverse maps the provided string to corresponding ASRProfile Implementation as specified by + * asrProfileToString + * @param input string to convert to corresponding ASRProfile + * @return @c ASRProfile that corresponds to the input string. In case of error + * the API returns ASRProfile::UNDEFINED + */ +inline ASRProfile getASRProfile(const std::string& input) { + ASRProfile profile = ASRProfile::UNDEFINED; + if (asrProfileToString(ASRProfile::CLOSE_TALK) == input) { + profile = ASRProfile::CLOSE_TALK; + } else if (asrProfileToString(ASRProfile::NEAR_FIELD) == input) { + profile = ASRProfile::NEAR_FIELD; + } else if (asrProfileToString(ASRProfile::FAR_FIELD) == input) { + profile = ASRProfile::FAR_FIELD; + } + return profile; +} + } // namespace aip } // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h b/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h index e01cda8854..e4bf67566b 100644 --- a/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h +++ b/CapabilityAgents/AIP/include/AIP/AudioInputProcessor.h @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -50,13 +51,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include "AudioProvider.h" #include "Initiator.h" @@ -149,7 +150,7 @@ class AudioInputProcessor * configurations change. * @param wakeWordsSetting The setting that represents the enabled wake words. This parameter is required if this * device supports wake words. - * @param speechEncoder The Speech Encoder used to encode audio inputs. This parameter is optional and + * @param audioEncoder The Audio Encoder used to encode audio inputs. This parameter is optional and * defaults to nullptr, which disable the encoding feature. * @param defaultAudioProvider A default @c avsCommon::AudioProvider to use for ExpectSpeech if the previous * provider is not readable (@c avsCommon::AudioProvider::alwaysReadable). This parameter is optional and @@ -175,7 +176,7 @@ class AudioInputProcessor std::shared_ptr speechConfirmation, const std::shared_ptr& capabilityChangeNotifier, std::shared_ptr wakeWordsSetting = nullptr, - std::shared_ptr speechEncoder = nullptr, + std::shared_ptr audioEncoder = nullptr, AudioProvider defaultAudioProvider = AudioProvider::null(), std::shared_ptr powerResourceManager = nullptr, std::shared_ptr metricRecorder = nullptr, @@ -396,7 +397,7 @@ class AudioInputProcessor * @param exceptionEncounteredSender The object to use for sending ExceptionEncountered messages. * @param userInactivityMonitor The object to use for resetting user inactivity. * @param systemSoundPlayer The instance of the system sound player. - * @param speechEncoder The Speech Encoder used to encode audio inputs. This parameter is optional and + * @param audioEncoder The Audio Encoder used to encode audio inputs. This parameter is optional and * will disable the encoding feature if set to nullptr. * @param defaultAudioProvider A default @c avsCommon::AudioProvider to use for ExpectSpeech if the previous * provider is not readable (@c AudioProvider::alwaysReadable). This parameter is optional, and ignored if set @@ -427,7 +428,7 @@ class AudioInputProcessor std::shared_ptr userInactivityMonitor, std::shared_ptr systemSoundPlayer, const std::shared_ptr& assetsManager, - std::shared_ptr speechEncoder, + std::shared_ptr audioEncoder, AudioProvider defaultAudioProvider, std::shared_ptr wakeWordConfirmation, std::shared_ptr speechConfirmation, @@ -766,7 +767,7 @@ class AudioInputProcessor avsCommon::utils::timing::Timer m_expectingSpeechTimer; /// The Speech Encoder to encode input stream. - const std::shared_ptr m_encoder; + const std::shared_ptr m_encoder; /** * @name Executor Thread Variables @@ -957,6 +958,11 @@ class AudioInputProcessor */ std::string m_uploadMetricName; + /** + * The Duration Builder used to calculate the duration of the fetch context call. + */ + avsCommon::utils::metrics::DataPointDurationBuilder m_fetchContextTimeMetricData; + /// A @c PowerResourceId used for wakelock logic. std::shared_ptr m_powerResourceId; diff --git a/CapabilityAgents/AIP/src/AudioInputProcessor.cpp b/CapabilityAgents/AIP/src/AudioInputProcessor.cpp index d0944a00c8..c3003b58ca 100644 --- a/CapabilityAgents/AIP/src/AudioInputProcessor.cpp +++ b/CapabilityAgents/AIP/src/AudioInputProcessor.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include "AIP/AudioInputProcessor.h" @@ -84,7 +83,7 @@ static const std::string CAPABILITY_INTERFACE_VALUES_KEY = "values"; static const std::string CAPABILITY_INTERFACE_DEFAULT_LOCALE = "DEFAULT"; /// String to identify log entries originating from this file. -static const std::string TAG("AudioInputProcessor"); +#define TAG "AudioInputProcessor" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -199,6 +198,8 @@ static const std::string WAKEWORD_DETECTION_SEGMENT_UPLOADED_PCM = "AIP_WAKEWORD /// Recognize EVENT is built for AIP metric source static const std::string RECOGNIZE_START_SEND_MESSAGE = "RECOGNIZE_EVENT_IS_BUILT"; +static const std::string RECOGNIZE_START_SEND_MESSAGE_ACTIVITY_NAME = + METRIC_ACTIVITY_NAME_PREFIX_AIP + RECOGNIZE_START_SEND_MESSAGE; /// Wakeword Activity Name for AIP metric source static const std::string START_OF_STREAM_TIMESTAMP = "START_OF_STREAM_TIMESTAMP"; @@ -217,6 +218,11 @@ static const std::string ACQUIRE_POWER_RESOURCE_ACTIVITY = METRIC_ACTIVITY_NAME_ static const std::string RELEASE_POWER_RESOURCE = "RELEASE_POWER_RESOURCE"; static const std::string RELEASE_POWER_RESOURCE_ACTIVITY = METRIC_ACTIVITY_NAME_PREFIX_AIP + RELEASE_POWER_RESOURCE; +/// The timestamp for the start of execute on context available +static const std::string FETCH_CONTEXT_DURATION = "FETCH_CONTEXT_DURATION"; +static const std::string FETCH_CONTEXT_DURATION_ACTIVITY_NAME = + METRIC_ACTIVITY_NAME_PREFIX_AIP + FETCH_CONTEXT_DURATION; + /// End of Speech Offset Received Activity Name for AIP metric source static const std::string END_OF_SPEECH_OFFSET_RECEIVED = "END_OF_SPEECH_OFFSET"; static const std::string END_OF_SPEECH_OFFSET_RECEIVED_ACTIVITY_NAME = @@ -260,6 +266,9 @@ static const int WAKEWORD_DETECTION_SEGMENT_SIZE_BYTES_OPUS = 5209; /// Threshold number of bytes for PCM Encoded Wakeword detection static const int WAKEWORD_DETECTION_SEGMENT_SIZE_BYTES_PCM = 40480; +/// Cloud resolve key constant. Used to determine the encoding sent to the cloud. +static const std::string CLOUD_RESOLVE_KEY = "CLOUD"; + /** * Helper function to get string values of encoding audio format, which are used in Recognize event. * @param encoding Target encoding format @@ -410,7 +419,7 @@ std::shared_ptr AudioInputProcessor::create( std::shared_ptr speechConfirmation, const std::shared_ptr& capabilityChangeNotifier, std::shared_ptr wakeWordsSetting, - std::shared_ptr speechEncoder, + std::shared_ptr audioEncoder, AudioProvider defaultAudioProvider, std::shared_ptr powerResourceManager, std::shared_ptr metricRecorder, @@ -470,7 +479,7 @@ std::shared_ptr AudioInputProcessor::create( userInactivityMonitor, systemSoundPlayer, assetsManager, - speechEncoder, + audioEncoder, defaultAudioProvider, wakeWordConfirmation, speechConfirmation, @@ -508,7 +517,7 @@ void AudioInputProcessor::addObserver(std::shared_ptr observe ACSDK_ERROR(LX("addObserverFailed").d("reason", "nullObserver")); return; } - m_executor.submit([this, observer]() { m_observers.insert(observer); }); + m_executor.execute([this, observer]() { m_observers.insert(observer); }); } void AudioInputProcessor::removeObserver(std::shared_ptr observer) { @@ -579,11 +588,13 @@ std::future AudioInputProcessor::resetState() { } void AudioInputProcessor::onContextAvailable(const std::string& jsonContext) { - m_executor.submit([this, jsonContext]() { executeOnContextAvailable(jsonContext); }); + ACSDK_DEBUG0(LX(__func__)); + m_executor.execute([this, jsonContext]() { executeOnContextAvailable(jsonContext); }); } void AudioInputProcessor::onContextFailure(const ContextRequestError error) { - m_executor.submit([this, error]() { executeOnContextFailure(error); }); + ACSDK_DEBUG0(LX(__func__)); + m_executor.execute([this, error]() { executeOnContextFailure(error); }); } void AudioInputProcessor::handleDirectiveImmediately(std::shared_ptr directive) { @@ -650,11 +661,11 @@ void AudioInputProcessor::onDeregistered() { void AudioInputProcessor::onFocusChanged(avsCommon::avs::FocusState newFocus, avsCommon::avs::MixingBehavior behavior) { ACSDK_DEBUG9(LX("onFocusChanged").d("newFocus", newFocus).d("MixingBehavior", behavior)); - m_executor.submit([this, newFocus]() { executeOnFocusChanged(newFocus); }); + m_executor.execute([this, newFocus]() { executeOnFocusChanged(newFocus); }); } void AudioInputProcessor::onDialogUXStateChanged(DialogUXStateObserverInterface::DialogUXState newState) { - m_executor.submit([this, newState]() { executeOnDialogUXStateChanged(newState); }); + m_executor.execute([this, newState]() { executeOnDialogUXStateChanged(newState); }); } AudioInputProcessor::AudioInputProcessor( @@ -666,7 +677,7 @@ AudioInputProcessor::AudioInputProcessor( std::shared_ptr userInactivityMonitor, std::shared_ptr systemSoundPlayer, const std::shared_ptr& assetsManager, - std::shared_ptr speechEncoder, + std::shared_ptr audioEncoder, AudioProvider defaultAudioProvider, std::shared_ptr wakeWordConfirmation, std::shared_ptr speechConfirmation, @@ -684,7 +695,7 @@ AudioInputProcessor::AudioInputProcessor( m_contextManager{contextManager}, m_focusManager{focusManager}, m_userInactivityMonitor{userInactivityMonitor}, - m_encoder{speechEncoder}, + m_encoder{audioEncoder}, m_defaultAudioProvider{defaultAudioProvider}, m_lastAudioProvider{AudioProvider::null()}, m_state{ObserverInterface::State::IDLE}, @@ -709,6 +720,7 @@ AudioInputProcessor::AudioInputProcessor( m_messageRequestResolver{nullptr}, m_encodingAudioFormats{{DEFAULT_RESOLVE_KEY, AudioFormat::Encoding::LPCM}} { m_capabilityConfigurations.insert(capabilitiesConfiguration); + m_fetchContextTimeMetricData.setName(FETCH_CONTEXT_DURATION); if (m_powerResourceManager) { m_powerResourceId = m_powerResourceManager->create( @@ -819,7 +831,7 @@ bool resolveMessageRequest( rapidjson::Value formatValue; auto encodingFormat = encodingFormats.at(resolveKey); auto formatString = encodingFormatToString(encodingFormat); - formatValue.SetString(formatString.c_str(), formatString.length()); + formatValue.SetString(formatString.c_str(), static_cast(formatString.length())); if (payload->value.FindMember(FORMAT_KEY) != payload->value.MemberEnd()) { ACSDK_WARN(LX("Format already exists in Json payload. Replace it with").d("format", formatString)); @@ -868,7 +880,7 @@ std::future AudioInputProcessor::expectSpeechTimedOut() { void AudioInputProcessor::handleStopCaptureDirective(std::shared_ptr info) { m_stopCaptureReceivedTime = steady_clock::now(); - m_executor.submit([this, info]() { + m_executor.execute([this, info]() { bool stopImmediately = true; executeStopCapture(stopImmediately, info); }); @@ -895,7 +907,7 @@ void AudioInputProcessor::handleExpectSpeechDirective(std::shared_ptr info) { @@ -1105,7 +1117,7 @@ bool AudioInputProcessor::executeRecognize( } if (settings::WakeWordConfirmationSettingType::TONE == m_wakeWordConfirmation->get()) { - m_executor.submit( + m_executor.execute( [this]() { m_systemSoundPlayer->playTone(SystemSoundPlayerInterface::Tone::WAKEWORD_NOTIFICATION); }); } @@ -1141,10 +1153,12 @@ bool AudioInputProcessor::executeRecognize( avsCommon::avs::AudioInputStream::Index encodingOffset = 0; AudioInputStream::Reader::Reference encodingReference = AudioInputStream::Reader::Reference::ABSOLUTE; - // Set up the speech encoder + // Set up the audio encoder + std::shared_ptr encodedStream; if (m_usingEncoder) { ACSDK_DEBUG(LX("encodingAudio").d("format", avsEncodingFormat)); - if (!m_encoder->startEncoding(provider.stream, provider.format, offset, reference)) { + encodedStream = m_encoder->startEncoding(provider.stream, provider.format, offset, reference); + if (!encodedStream) { ACSDK_ERROR(LX("executeRecognizeFailed").d("reason", "Failed to start encoder")); return false; } @@ -1179,7 +1193,7 @@ bool AudioInputProcessor::executeRecognize( std::shared_ptr audioReader = attachment::DefaultAttachmentReader::create( sds::ReaderPolicy::NONBLOCKING, - isLPCMEncodingAudioFormat ? provider.stream : m_encoder->getEncodedStream(), + isLPCMEncodingAudioFormat ? provider.stream : encodedStream, isLPCMEncodingAudioFormat ? offset : encodingOffset, isLPCMEncodingAudioFormat ? reference : encodingReference); if (!audioReader) { @@ -1193,14 +1207,24 @@ bool AudioInputProcessor::executeRecognize( ACSDK_INFO(LX("Create audio attachment reader success") .d("resolveKey", resolveKey) .d("format", encodingFormatToString(it.second))); + + if (resolveKey == CLOUD_RESOLVE_KEY) { + if (isLPCMEncodingAudioFormat) { + m_audioBytesForMetricThreshold = WAKEWORD_DETECTION_SEGMENT_SIZE_BYTES_PCM; + m_uploadMetricName = WAKEWORD_DETECTION_SEGMENT_UPLOADED_PCM; + } else { + m_audioBytesForMetricThreshold = WAKEWORD_DETECTION_SEGMENT_SIZE_BYTES_OPUS; + m_uploadMetricName = WAKEWORD_DETECTION_SEGMENT_UPLOADED_OPUS; + } + } } if (!multiStreamsRequestedLocked()) { m_messageRequestResolver = nullptr; // Set up format for single audio stream request - if (m_usingEncoder && m_encoder->getContext()) { - avsEncodingFormat = m_encoder->getContext()->getAVSFormatName(); + if (m_usingEncoder) { + avsEncodingFormat = m_encoder->getAVSFormatName(); } payloadGenerator.addMember(FORMAT_KEY, avsEncodingFormat); @@ -1243,6 +1267,11 @@ bool AudioInputProcessor::executeRecognize( setState(ObserverInterface::State::RECOGNIZING); + // Notify observers of current ASRProfile + for (auto observer : m_observers) { + observer->onASRProfileChanged(asrProfileToString(provider.profile)); + } + // Note that we're preparing to send a Recognize event. m_preparingToSend = true; @@ -1251,6 +1280,7 @@ bool AudioInputProcessor::executeRecognize( m_streamIsClosedInRecognizingState = false; // Start assembling the context; we'll service the callback after assembling our Recognize event. + m_fetchContextTimeMetricData.startDurationTimer(); m_contextManager->getContextWithoutReportableStateProperties(shared_from_this()); // Stop the ExpectSpeech timer so we don't get a timeout. @@ -1266,6 +1296,9 @@ bool AudioInputProcessor::executeRecognize( m_recognizeRequest.reset(); // Handle metrics for this event. + if (m_usingEncoder) { + avsEncodingFormat = m_encoder->getAVSFormatName(); + } submitMetric( m_metricRecorder, MetricEventBuilder{} @@ -1282,6 +1315,7 @@ bool AudioInputProcessor::executeRecognize( .setName("RESOURCE_TYPE_ID") .setValue(std::to_string(m_resourceFlags.to_ulong())) .build()) + .addDataPoint(DataPointStringBuilder{}.setName("ENCODING_FORMAT").setValue(avsEncodingFormat).build()) .addDataPoint(DataPointCounterBuilder{}.setName(START_OF_UTTERANCE).increment(1).build()), m_preCachedDialogRequestId); @@ -1312,9 +1346,16 @@ bool AudioInputProcessor::executeRecognize( .addDataPoint( DataPointDurationBuilder{duration_cast(startOfStreamTimestamp.time_since_epoch())} .setName(START_OF_STREAM_TIMESTAMP) - .build()) + .build()), + m_preCachedDialogRequestId); + + submitMetric( + m_metricRecorder, + MetricEventBuilder{} + .setActivityName(RECOGNIZE_START_SEND_MESSAGE_ACTIVITY_NAME) .addDataPoint(DataPointCounterBuilder{}.setName(RECOGNIZE_START_SEND_MESSAGE).increment(1).build()), m_preCachedDialogRequestId); + ACSDK_DEBUG(LX(__func__).d("WW_DURATION(ms)", duration.count())); } @@ -1330,6 +1371,13 @@ bool AudioInputProcessor::executeRecognize( void AudioInputProcessor::executeOnContextAvailable(const std::string& jsonContext) { ACSDK_DEBUG(LX("executeOnContextAvailable").sensitive("jsonContext", jsonContext)); + /// Submit execute context start metric + submitMetric( + m_metricRecorder, + MetricEventBuilder{} + .setActivityName(FETCH_CONTEXT_DURATION_ACTIVITY_NAME) + .addDataPoint(m_fetchContextTimeMetricData.stopDurationTimer().build()), + m_preCachedDialogRequestId); // Should already be RECOGNIZING if we get here. if (m_state != ObserverInterface::State::RECOGNIZING) { ACSDK_ERROR( @@ -1470,7 +1518,7 @@ bool AudioInputProcessor::executeStopCapture(bool stopImmediately, std::shared_p std::function stopCapture = [=] { ACSDK_DEBUG(LX("stopCapture").d("stopImmediately", stopImmediately)); if (m_usingEncoder) { - // If SpeechEncoder is enabled, let it finish it so the stream will be closed automatically. + // If Audio Encoder is enabled, let it finish it so the stream will be closed automatically. m_encoder->stopEncoding(stopImmediately); m_usingEncoder = false; } @@ -1631,7 +1679,7 @@ void AudioInputProcessor::setState(ObserverInterface::State state) { // Reset the user inactivity if transitioning to or from `RECOGNIZING` state. if (ObserverInterface::State::RECOGNIZING == m_state || ObserverInterface::State::RECOGNIZING == state) { - m_executor.submit([this]() { m_userInactivityMonitor->onUserActive(); }); + m_executor.execute([this]() { m_userInactivityMonitor->onUserActive(); }); } auto currentDialogRequestId = @@ -1699,7 +1747,7 @@ void AudioInputProcessor::onExceptionReceived(const std::string& exceptionMessag void AudioInputProcessor::onSendCompleted(MessageRequestObserverInterface::Status status) { ACSDK_DEBUG(LX("onSendCompleted").d("status", status)); - m_executor.submit([this, status]() { + m_executor.execute([this, status]() { if (MessageRequestObserverInterface::Status::SUCCESS == status && ObserverInterface::State::RECOGNIZING == m_state) { // This is to take care of the edge case where the event stream is closed before a stop capture is received. @@ -1715,7 +1763,7 @@ std::unordered_set> Aud } void AudioInputProcessor::onLocaleAssetsChanged() { - m_executor.submit([this]() { executeOnLocaleAssetsChanged(); }); + m_executor.execute([this]() { executeOnLocaleAssetsChanged(); }); } void AudioInputProcessor::executeOnLocaleAssetsChanged() { @@ -1787,7 +1835,7 @@ bool AudioInputProcessor::handleSetWakeWordConfirmation(std::shared_ptrsetAvsChange(value); }; - m_executor.submit(executeChange); + m_executor.execute(executeChange); if (info->result) { info->result->setCompleted(); @@ -1810,7 +1858,7 @@ bool AudioInputProcessor::handleSetSpeechConfirmation(std::shared_ptr> value; @@ -1823,7 +1871,7 @@ bool AudioInputProcessor::handleSetSpeechConfirmation(std::shared_ptrsetAvsChange(value); }; - m_executor.submit(executeChange); + m_executor.execute(executeChange); if (info->result) { info->result->setCompleted(); @@ -1852,7 +1900,7 @@ bool AudioInputProcessor::handleSetWakeWords(std::shared_ptr info return false; } - m_executor.submit([this, wakeWords, info]() { m_wakeWordsSetting->setAvsChange(wakeWords); }); + m_executor.execute([this, wakeWords, info]() { m_wakeWordsSetting->setAvsChange(wakeWords); }); if (info->result) { info->result->setCompleted(); @@ -1902,7 +1950,7 @@ void AudioInputProcessor::managePowerResource(ObserverInterface::State newState) void AudioInputProcessor::onConnectionStatusChanged(bool connected) { if (!connected) { - m_executor.submit([this]() { return executeDisconnected(); }); + m_executor.execute([this]() { return executeDisconnected(); }); } } @@ -1914,8 +1962,7 @@ void AudioInputProcessor::executeDisconnected() { } bool AudioInputProcessor::setEncodingAudioFormat(AudioFormat::Encoding encoding) { - if (encoding == AudioFormat::Encoding::LPCM || - (m_encoder && m_encoder->getContext() && encoding == m_encoder->getContext()->getAudioFormat().encoding)) { + if (encoding == AudioFormat::Encoding::LPCM || (m_encoder && encoding == m_encoder->getEncoding())) { std::lock_guard lock(m_encodingFormatMutex); m_encodingAudioFormats.clear(); // Only one format is configured, and AIP will send resolved RequestMessage, and this resolveKey is simply a @@ -1927,10 +1974,10 @@ bool AudioInputProcessor::setEncodingAudioFormat(AudioFormat::Encoding encoding) } bool AudioInputProcessor::initialize() { - if (m_encoder && m_encoder->getContext()) { + if (m_encoder) { std::lock_guard lock(m_encodingFormatMutex); m_encodingAudioFormats.clear(); - m_encodingAudioFormats.emplace(DEFAULT_RESOLVE_KEY, m_encoder->getContext()->getAudioFormat().encoding); + m_encodingAudioFormats.emplace(DEFAULT_RESOLVE_KEY, m_encoder->getEncoding()); } m_assetsManager->addLocaleAssetsObserver(shared_from_this()); return true; @@ -1986,21 +2033,17 @@ void AudioInputProcessor::closeAttachmentReaders(attachment::AttachmentReader::C } bool AudioInputProcessor::isEncodingFormatSupported(avsCommon::utils::AudioFormat::Encoding encodingFormat) const { - if (encodingFormat == AudioFormat::Encoding::LPCM || - (m_encoder && m_encoder->getContext() && - encodingFormat == m_encoder->getContext()->getAudioFormat().encoding)) { + if (encodingFormat == AudioFormat::Encoding::LPCM || (m_encoder && encodingFormat == m_encoder->getEncoding())) { return true; } return false; } bool AudioInputProcessor::isUsingEncoderLocked() const { - for (auto it = m_encodingAudioFormats.begin(); it != m_encodingAudioFormats.end(); it++) { - if (it->second != AudioFormat::Encoding::LPCM) { - return true; - } - } - return false; + return std::any_of( + m_encodingAudioFormats.begin(), + m_encodingAudioFormats.end(), + [](EncodingFormatResponse::const_reference entry) { return entry.second != AudioFormat::Encoding::LPCM; }); } bool AudioInputProcessor::multiStreamsRequestedLocked() const { diff --git a/CapabilityAgents/AIP/src/CMakeLists.txt b/CapabilityAgents/AIP/src/CMakeLists.txt index 2c2eed5328..244db6c5fb 100644 --- a/CapabilityAgents/AIP/src/CMakeLists.txt +++ b/CapabilityAgents/AIP/src/CMakeLists.txt @@ -8,7 +8,6 @@ target_include_directories(AIP PUBLIC "${AFML_SOURCE_DIR}/include" "${AVSCommon_INCLUDE_DIRS}" "${DeviceSettings_INCLUDE_DIRS}" - "${SpeechEncoder_SOURCE_DIR}/include" "${SystemSoundPlayer_INCLUDE_DIRS}") target_link_libraries(AIP @@ -16,7 +15,7 @@ target_link_libraries(AIP ADSL AFML DeviceSettings - SpeechEncoder + acsdkAudioEncoderInterfaces SystemSoundPlayer acsdkNotifier) diff --git a/CapabilityAgents/AIP/test/AudioInputProcessorTest.cpp b/CapabilityAgents/AIP/test/AudioInputProcessorTest.cpp index c1b553b7b5..a588977906 100644 --- a/CapabilityAgents/AIP/test/AudioInputProcessorTest.cpp +++ b/CapabilityAgents/AIP/test/AudioInputProcessorTest.cpp @@ -27,6 +27,8 @@ #include #include +#include +#include #include #include #include @@ -111,6 +113,9 @@ static const avsCommon::avs::NamespaceAndName DIRECTIVES[] = {STOP_CAPTURE, /// The SpeechRecognizer context state signature. static const avsCommon::avs::NamespaceAndName RECOGNIZER_STATE{NAMESPACE, "RecognizerState"}; +/// Byte array for audio sample data passing. +using Bytes = audioEncoderInterfaces::BlockAudioEncoderInterface::Bytes; + /// Sample rate for audio input stream. static const unsigned int SAMPLE_RATE_HZ = 16000; @@ -632,7 +637,7 @@ void RecognizeEvent::verifyMessage( auto bytesRead = namedReader->reader->read( samples.data() + samplesRead, (samples.size() - samplesRead) * SDS_WORDSIZE, &status); if (avsCommon::avs::attachment::AttachmentReader::ReadStatus::OK_WOULDBLOCK == status) { - std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } EXPECT_EQ(status, avsCommon::avs::attachment::AttachmentReader::ReadStatus::OK); @@ -683,8 +688,8 @@ class MockExpectSpeechTimeoutHandler : public avsCommon::sdkInterfaces::ExpectSp bool(std::chrono::milliseconds timeout, const std::function()>& expectSpeechTimedOut)); }; -/// Mock class that implements the SpeechEncoderContext. -class MockEncoderContext : public speechencoder::EncoderContext { +/// Mock class that implements the @c BlockAudioEncoderInterface. +class MockBlockAudioEncoder : public audioEncoderInterfaces::BlockAudioEncoderInterface { public: MOCK_METHOD1(init, bool(alexaClientSDK::avsCommon::utils::AudioFormat inputFormat)); MOCK_METHOD0(getInputFrameSize, size_t()); @@ -692,8 +697,9 @@ class MockEncoderContext : public speechencoder::EncoderContext { MOCK_METHOD0(requiresFullyRead, bool()); MOCK_METHOD0(getAudioFormat, alexaClientSDK::avsCommon::utils::AudioFormat()); MOCK_METHOD0(getAVSFormatName, std::string()); - MOCK_METHOD0(start, bool()); - MOCK_METHOD3(processSamples, ssize_t(void* samples, size_t numberOfWords, uint8_t* buffer)); + MOCK_METHOD1(start, bool(Bytes&)); + MOCK_METHOD3(processSamples, bool(Bytes::const_iterator, Bytes::const_iterator, Bytes&)); + MOCK_METHOD1(flush, bool(Bytes&)); MOCK_METHOD0(close, void()); }; @@ -1018,10 +1024,10 @@ class AudioInputProcessorTest : public ::testing::Test { std::string m_dialogRequestId; /// The audio encoder object - std::shared_ptr m_speechEncoder; + std::shared_ptr m_audioEncoder; - /// The mock @c EncoderContext - std::shared_ptr m_mockEncoderContext; + /// The mock @c BlockAudioEncoderInterface + std::shared_ptr m_mockBlockAudioEncoder; /// Message ID in directive std::string m_messageId; @@ -1123,30 +1129,30 @@ void AudioInputProcessorTest::SetUp() { m_pattern.resize(PATTERN_WORDS); std::iota(m_pattern.begin(), m_pattern.end(), 0); - m_mockEncoderContext = std::make_shared(); - m_speechEncoder = std::make_shared(m_mockEncoderContext); - EXPECT_CALL(*m_mockEncoderContext, init(_)).Times(AtLeast(0)).WillRepeatedly(Return(true)); - EXPECT_CALL(*m_mockEncoderContext, requiresFullyRead()).Times(AtLeast(0)).WillRepeatedly(Return(true)); - EXPECT_CALL(*m_mockEncoderContext, getAVSFormatName()).WillRepeatedly(Return(std::string(AUDIO_FORMAT_OPUS))); - EXPECT_CALL(*m_mockEncoderContext, getInputFrameSize()).WillRepeatedly(Return(1)); - EXPECT_CALL(*m_mockEncoderContext, getOutputFrameSize()).WillRepeatedly(Return(2)); - EXPECT_CALL(*m_mockEncoderContext, processSamples(_, _, _)) - .WillRepeatedly(Invoke([](void* samples, size_t numberOfWords, uint8_t* buffer) { - uint8_t* src = static_cast(samples); - buffer[0] = src[0]; - buffer[1] = src[1]; - return 2; + m_mockBlockAudioEncoder = std::make_shared(); + m_audioEncoder = audioEncoder::createAudioEncoder(m_mockBlockAudioEncoder); + EXPECT_CALL(*m_mockBlockAudioEncoder, init(_)).Times(AtLeast(0)).WillRepeatedly(Return(true)); + EXPECT_CALL(*m_mockBlockAudioEncoder, requiresFullyRead()).Times(AtLeast(0)).WillRepeatedly(Return(true)); + EXPECT_CALL(*m_mockBlockAudioEncoder, getAVSFormatName()).WillRepeatedly(Return(std::string(AUDIO_FORMAT_OPUS))); + EXPECT_CALL(*m_mockBlockAudioEncoder, getInputFrameSize()).WillRepeatedly(Return(1)); + EXPECT_CALL(*m_mockBlockAudioEncoder, getOutputFrameSize()).WillRepeatedly(Return(2)); + EXPECT_CALL(*m_mockBlockAudioEncoder, processSamples(_, _, _)) + .WillRepeatedly(Invoke([](Bytes::const_iterator begin, Bytes::const_iterator end, Bytes& buffer) { + const size_t bufferOffset = buffer.size(); + buffer.resize(bufferOffset + 2); + std::memcpy(buffer.data() + bufferOffset, &*begin, 2); + return true; })); - EXPECT_CALL(*m_mockEncoderContext, getAudioFormat()) - .WillRepeatedly( - Return(avsCommon::utils::AudioFormat{.encoding = avsCommon::utils::AudioFormat::Encoding::OPUS, - .endianness = avsCommon::utils::AudioFormat::Endianness::LITTLE, - .sampleRateHz = 16000, - .sampleSizeInBits = 16, - .numChannels = 1, - .dataSigned = false, - .layout = avsCommon::utils::AudioFormat::Layout::NON_INTERLEAVED})); - EXPECT_CALL(*m_mockEncoderContext, start()).WillRepeatedly(Return(true)); + EXPECT_CALL(*m_mockBlockAudioEncoder, getAudioFormat()) + .WillRepeatedly(Return(avsCommon::utils::AudioFormat{avsCommon::utils::AudioFormat::Encoding::OPUS, + avsCommon::utils::AudioFormat::Endianness::LITTLE, + 16000, + 16, + 1, + false, + avsCommon::utils::AudioFormat::Layout::NON_INTERLEAVED})); + EXPECT_CALL(*m_mockBlockAudioEncoder, start(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*m_mockBlockAudioEncoder, flush(_)).WillRepeatedly(Return(true)); } void AudioInputProcessorTest::setupEncoderTest() { @@ -1165,7 +1171,7 @@ void AudioInputProcessorTest::setupEncoderTest() { m_mockSpeechConfirmation, m_capabilityChangeNotifier, m_mockWakeWordSetting, - m_speechEncoder, + m_audioEncoder, *m_audioProvider, m_mockPowerResourceManager, m_metricRecorder); @@ -1261,7 +1267,6 @@ bool AudioInputProcessorTest::testRecognizeSucceeds( } if (!bargeIn) { - EXPECT_CALL(*m_mockUserInactivityMonitor, onUserActive()).Times(2); EXPECT_CALL(*m_mockObserver, onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING)); EXPECT_CALL(*m_mockFocusManager, acquireChannel(CHANNEL_NAME, _)).WillOnce(InvokeWithoutArgs([this, stopPoint] { m_audioInputProcessor->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND, MixingBehavior::PRIMARY); @@ -1324,6 +1329,7 @@ bool AudioInputProcessorTest::testRecognizeSucceeds( EXPECT_CALL(*m_mockPowerResourceManager, release(IsSamePowerResource(COMPONENT_NAME))).Times(AtLeast(1)); } + EXPECT_CALL(*m_mockObserver, onASRProfileChanged(asrProfileToString(audioProvider.profile))).Times(AtLeast(1)); auto sentFuture = m_recognizeEvent->send(m_audioInputProcessor); // If a valid begin index was not provided, load the SDS buffer with the test pattern after recognize() is sent. @@ -1384,7 +1390,6 @@ bool AudioInputProcessorTest::testContextFailure(avsCommon::sdkInterfaces::Conte return CONTEXT_REQUEST_TOKEN; })); EXPECT_CALL(*m_mockObserver, onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING)); - EXPECT_CALL(*m_mockUserInactivityMonitor, onUserActive()).Times(2); EXPECT_CALL(*m_mockObserver, onStateChanged(AudioInputProcessorObserverInterface::State::IDLE)) .WillOnce(InvokeWithoutArgs([&] { std::lock_guard lock(mutex); @@ -1394,6 +1399,8 @@ bool AudioInputProcessorTest::testContextFailure(avsCommon::sdkInterfaces::Conte EXPECT_CALL(*m_mockPowerResourceManager, acquire(IsSamePowerResource(COMPONENT_NAME), _)).Times(AtLeast(1)); EXPECT_CALL(*m_mockPowerResourceManager, release(IsSamePowerResource(COMPONENT_NAME))).Times(AtLeast(1)); + EXPECT_CALL(*m_mockObserver, onASRProfileChanged(asrProfileToString(m_audioProvider->profile))).Times((AtLeast(1))); + if (recognize.send(m_audioInputProcessor).get()) { std::unique_lock lock(mutex); return conditionVariable.wait_for(lock, TEST_TIMEOUT, [&done] { return done; }); @@ -1470,7 +1477,6 @@ bool AudioInputProcessorTest::testExpectSpeechSucceeds(bool withDialogRequestId) EXPECT_CALL(*m_mockObserver, onStateChanged(AudioInputProcessorObserverInterface::State::EXPECTING_SPEECH)); EXPECT_CALL(*m_mockObserver, onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING)); - EXPECT_CALL(*m_mockUserInactivityMonitor, onUserActive()).Times(2); EXPECT_CALL(*m_mockPowerResourceManager, acquire(IsSamePowerResource(COMPONENT_NAME), _)).Times(AtLeast(1)); if (withDialogRequestId) { EXPECT_CALL(*result, setCompleted()); @@ -1482,7 +1488,7 @@ bool AudioInputProcessorTest::testExpectSpeechSucceeds(bool withDialogRequestId) conditionVariable.notify_one(); return CONTEXT_REQUEST_TOKEN; })); - + EXPECT_CALL(*m_mockObserver, onASRProfileChanged(asrProfileToString(m_audioProvider->profile))).Times(AtLeast(1)); if (!withDialogRequestId) { directiveHandler->handleDirectiveImmediately(avsDirective); } else { @@ -1655,11 +1661,10 @@ bool AudioInputProcessorTest::testRecognizeWithExpectSpeechInitiator(bool withIn EXPECT_CALL(*result, setCompleted()); EXPECT_CALL(*m_mockObserver, onStateChanged(AudioInputProcessorObserverInterface::State::EXPECTING_SPEECH)); EXPECT_CALL(*m_mockObserver, onStateChanged(AudioInputProcessorObserverInterface::State::RECOGNIZING)); - EXPECT_CALL(*m_mockUserInactivityMonitor, onUserActive()).Times(2); EXPECT_CALL(*m_mockContextManager, getContextWithoutReportableStateProperties(_, _, _)) .WillOnce(Return(CONTEXT_REQUEST_TOKEN)); EXPECT_CALL(*m_mockPowerResourceManager, acquire(IsSamePowerResource(COMPONENT_NAME), _)).Times(AtLeast(1)); - + EXPECT_CALL(*m_mockObserver, onASRProfileChanged(asrProfileToString(m_audioProvider->profile))).Times(AtLeast(1)); // Set AIP to a sane state. directiveHandler->preHandleDirective(avsDirective, std::move(result)); EXPECT_TRUE(directiveHandler->handleDirective(avsDirective->getMessageId())); @@ -3636,16 +3641,15 @@ TEST_F(AudioInputProcessorTest, test_requestEncodingAudioFormatsSuccess) { */ TEST_F(AudioInputProcessorTest, test_requestEncodingAudioFormatsWithFallbackAndUnsupportedFormats) { setupEncoderTest(); - EXPECT_CALL(*m_mockEncoderContext, getAVSFormatName()).WillRepeatedly(Return(std::string(AUDIO_FORMAT_LPCM))); - EXPECT_CALL(*m_mockEncoderContext, getAudioFormat()) - .WillRepeatedly( - Return(avsCommon::utils::AudioFormat{.encoding = avsCommon::utils::AudioFormat::Encoding::LPCM, - .endianness = avsCommon::utils::AudioFormat::Endianness::LITTLE, - .sampleRateHz = 16000, - .sampleSizeInBits = 16, - .numChannels = 1, - .dataSigned = false, - .layout = avsCommon::utils::AudioFormat::Layout::NON_INTERLEAVED})); + EXPECT_CALL(*m_mockBlockAudioEncoder, getAVSFormatName()).WillRepeatedly(Return(std::string(AUDIO_FORMAT_LPCM))); + EXPECT_CALL(*m_mockBlockAudioEncoder, getAudioFormat()) + .WillRepeatedly(Return(avsCommon::utils::AudioFormat{avsCommon::utils::AudioFormat::Encoding::LPCM, + avsCommon::utils::AudioFormat::Endianness::LITTLE, + 16000, + 16, + 1, + false, + avsCommon::utils::AudioFormat::Layout::NON_INTERLEAVED})); AudioInputProcessor::EncodingFormatRequest encodingReq = { {"CLOUD", {avsCommon::utils::AudioFormat::Encoding::OPUS, avsCommon::utils::AudioFormat::Encoding::OPUS}}, @@ -3822,7 +3826,7 @@ TEST_F(AudioInputProcessorTest, test_recognizeWakewordWithGoodBeginAndEndForMult } /// This function verifies that recognize() works for multiple audio streams in State::RECOGNIZING when the previous * -/// recognize used the CLOSE_TALK profile. Disabled for potential race condition in SpeechEncoder. +/// recognize used the CLOSE_TALK profile. Disabled for potential race condition in audio encoder. TEST_F(AudioInputProcessorTest, test_recognizeBargeInWhileRecognizingCloseTalkForMultiStreams) { auto audioProvider = *m_audioProvider; audioProvider.profile = ASRProfile::CLOSE_TALK; diff --git a/CapabilityAgents/AIP/test/CMakeLists.txt b/CapabilityAgents/AIP/test/CMakeLists.txt index 640e5e0e8b..1b8c692d8e 100644 --- a/CapabilityAgents/AIP/test/CMakeLists.txt +++ b/CapabilityAgents/AIP/test/CMakeLists.txt @@ -5,4 +5,4 @@ set(INCLUDE_PATH "${AVSCommon_SOURCE_DIR}/AVS/test" "${DeviceSettings_SOURCE_DIR}/test") -discover_unit_tests("${INCLUDE_PATH}" "AIP;UtilsCommonTestLib;CertifiedSenderCommonTestLib;AVSSystem;SDKInterfacesTests") +discover_unit_tests("${INCLUDE_PATH}" "AIP;UtilsCommonTestLib;CertifiedSenderCommonTestLib;AVSSystem;SDKInterfacesTests;acsdkAudioEncoder") diff --git a/CapabilityAgents/AIP/test/MockObserver.h b/CapabilityAgents/AIP/test/MockObserver.h index b59764086a..4f191a0767 100644 --- a/CapabilityAgents/AIP/test/MockObserver.h +++ b/CapabilityAgents/AIP/test/MockObserver.h @@ -29,6 +29,7 @@ namespace test { class MockObserver : public avsCommon::sdkInterfaces::AudioInputProcessorObserverInterface { public: MOCK_METHOD1(onStateChanged, void(avsCommon::sdkInterfaces::AudioInputProcessorObserverInterface::State state)); + MOCK_METHOD1(onASRProfileChanged, void(const std::string& profile)); }; } // namespace test diff --git a/CapabilityAgents/Alexa/include/Alexa/AlexaEventProcessedNotifier.h b/CapabilityAgents/Alexa/include/Alexa/AlexaEventProcessedNotifier.h index 5a07998cd8..5dfe0874cc 100644 --- a/CapabilityAgents/Alexa/include/Alexa/AlexaEventProcessedNotifier.h +++ b/CapabilityAgents/Alexa/include/Alexa/AlexaEventProcessedNotifier.h @@ -19,7 +19,7 @@ #include #include -#include +#include #include namespace alexaClientSDK { @@ -30,7 +30,7 @@ namespace alexa { * Relays notifications when Alexa.EventProcessed directive is received. */ class AlexaEventProcessedNotifier - : public acsdkNotifier::Notifier { + : public notifier::Notifier { public: /** * Factory method. diff --git a/CapabilityAgents/Alexa/src/AlexaInterfaceCapabilityAgent.cpp b/CapabilityAgents/Alexa/src/AlexaInterfaceCapabilityAgent.cpp index 86aca70035..6aaad98372 100644 --- a/CapabilityAgents/Alexa/src/AlexaInterfaceCapabilityAgent.cpp +++ b/CapabilityAgents/Alexa/src/AlexaInterfaceCapabilityAgent.cpp @@ -31,7 +31,7 @@ using namespace avsCommon::utils; using DefaultEndpointAnnotation = avsCommon::sdkInterfaces::endpoints::DefaultEndpointAnnotation; /// String to identify log entries originating from this file. -static const std::string TAG{"AlexaInterfaceCapabilityAgent"}; +#define TAG "AlexaInterfaceCapabilityAgent" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -168,7 +168,7 @@ void AlexaInterfaceCapabilityAgent::handleDirectiveImmediately(std::shared_ptr info) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, info] { + m_executor.execute([this, info] { if (!info || !info->directive) { ACSDK_ERROR(LX("handleDirectiveFailed").d("reason", "nullDirective")); return; diff --git a/CapabilityAgents/Alexa/src/AlexaInterfaceMessageSender.cpp b/CapabilityAgents/Alexa/src/AlexaInterfaceMessageSender.cpp index 4fa4a9f47c..3acf921f82 100644 --- a/CapabilityAgents/Alexa/src/AlexaInterfaceMessageSender.cpp +++ b/CapabilityAgents/Alexa/src/AlexaInterfaceMessageSender.cpp @@ -32,7 +32,7 @@ using namespace avsCommon::utils; using namespace avsCommon::utils::logger; /// String to identify log entries originating from this file. -static const std::string TAG{"AlexaInterfaceMessageSender"}; +#define TAG "AlexaInterfaceMessageSender" /// Name of response events. static const std::string EVENT_NAME_STATE_REPORT_STRING = "StateReport"; @@ -327,7 +327,7 @@ bool AlexaInterfaceMessageSender::sendCommonResponseEvent( endpoint, jsonPayload, responseNamespace == "" ? ALEXA_INTERFACE_NAME : responseNamespace); - m_executor.submit([this, event]() { + m_executor.execute([this, event]() { // Start collecting context for this endpoint. auto token = m_contextManager->getContext(shared_from_this(), event->endpoint.endpointId); m_pendingResponses[token] = event; @@ -408,7 +408,7 @@ void AlexaInterfaceMessageSender::onStateChanged( const CapabilityState& state, const AlexaStateChangeCauseType cause) { auto event = std::make_shared(identifier, state, cause); - m_executor.submit([this, event]() { + m_executor.execute([this, event]() { // Start collecting context for this endpoint. auto token = m_contextManager->getContext(shared_from_this(), event->tag.endpointId); m_pendingChangeReports[token] = event; @@ -419,7 +419,7 @@ void AlexaInterfaceMessageSender::onContextAvailable( const std::string& endpointId, const AVSContext& endpointContext, ContextRequestToken token) { - m_executor.submit([this, endpointId, endpointContext, token]() { + m_executor.execute([this, endpointId, endpointContext, token]() { ACSDK_DEBUG(LX("onContextAvailable").sensitive("endpointId", endpointId)); // Is this for a pending response event? @@ -436,7 +436,8 @@ void AlexaInterfaceMessageSender::onContextAvailable( } void AlexaInterfaceMessageSender::onContextFailure(const ContextRequestError error, ContextRequestToken token) { - m_executor.submit([this, error, token]() { + m_executor.execute([this, error, token]() { + (void)error; // mark capture as used for the case when logging is disabled. ACSDK_ERROR(LX("executeOnContextFailure").d("error", error)); // Is this for a pending response event? diff --git a/CapabilityAgents/ApiGateway/src/ApiGatewayCapabilityAgent.cpp b/CapabilityAgents/ApiGateway/src/ApiGatewayCapabilityAgent.cpp index 9030205d1a..09371f14e9 100644 --- a/CapabilityAgents/ApiGateway/src/ApiGatewayCapabilityAgent.cpp +++ b/CapabilityAgents/ApiGateway/src/ApiGatewayCapabilityAgent.cpp @@ -29,7 +29,7 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::utils::json::jsonUtils; /// String to identify log entries originating from this file. -static const std::string TAG{"ApiGateway"}; +#define TAG "ApiGateway" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -117,7 +117,7 @@ void ApiGatewayCapabilityAgent::handleDirectiveImmediately(std::shared_ptr info) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, info] { executeHandleDirective(info); }); + m_executor.execute([this, info] { executeHandleDirective(info); }); } void ApiGatewayCapabilityAgent::executeHandleDirective(std::shared_ptr info) { diff --git a/CapabilityAgents/CMakeLists.txt b/CapabilityAgents/CMakeLists.txt index 013ad92743..9987b8170b 100644 --- a/CapabilityAgents/CMakeLists.txt +++ b/CapabilityAgents/CMakeLists.txt @@ -14,8 +14,7 @@ set(CAPABILITY_AGENTS "SoftwareComponentReporter" "SpeakerManager" "SpeechSynthesizer" - "System" - "TemplateRuntime") + "System") if (COMMS) list(APPEND CAPABILITY_AGENTS "CallManager") diff --git a/CapabilityAgents/InteractionModel/acsdkInteractionModel/privateInclude/acsdkInteractionModel/InteractionModelNotifier.h b/CapabilityAgents/InteractionModel/acsdkInteractionModel/privateInclude/acsdkInteractionModel/InteractionModelNotifier.h index a60a79dbdf..8de5238f07 100644 --- a/CapabilityAgents/InteractionModel/acsdkInteractionModel/privateInclude/acsdkInteractionModel/InteractionModelNotifier.h +++ b/CapabilityAgents/InteractionModel/acsdkInteractionModel/privateInclude/acsdkInteractionModel/InteractionModelNotifier.h @@ -20,7 +20,7 @@ #include #include -#include +#include namespace alexaClientSDK { namespace acsdkInteractionModel { @@ -29,7 +29,7 @@ namespace acsdkInteractionModel { * Relays notifications related to acsdkInteractionModel. */ class InteractionModelNotifier - : public acsdkNotifier::Notifier< + : public notifier::Notifier< acsdkInteractionModelInterfaces::InteractionModelRequestProcessingObserverInterface> { public: /* diff --git a/CapabilityAgents/InteractionModel/acsdkInteractionModel/src/InteractionModelCapabilityAgent.cpp b/CapabilityAgents/InteractionModel/acsdkInteractionModel/src/InteractionModelCapabilityAgent.cpp index 236d9e2da9..f61304a6b9 100644 --- a/CapabilityAgents/InteractionModel/acsdkInteractionModel/src/InteractionModelCapabilityAgent.cpp +++ b/CapabilityAgents/InteractionModel/acsdkInteractionModel/src/InteractionModelCapabilityAgent.cpp @@ -34,7 +34,7 @@ using namespace avsCommon::utils; using namespace json::jsonUtils; /// String to identify log entries originating from this file. -static const std::string TAG{"InteractionModel"}; +#define TAG "InteractionModel" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -139,10 +139,44 @@ DirectiveHandlerConfiguration InteractionModelCapabilityAgent::getConfiguration( } void InteractionModelCapabilityAgent::handleDirectiveImmediately(std::shared_ptr directive) { ACSDK_DEBUG5(LX(__func__)); - handleDirective(std::make_shared(directive, nullptr)); + + // The logic for handleDirectiveImmediately and preHandleDirective should be same, + // which we only process directive without dialogRequestId + preHandleDirective(std::make_shared(directive, nullptr)); } void InteractionModelCapabilityAgent::preHandleDirective(std::shared_ptr info) { - // No-op + ACSDK_DEBUG5(LX(__func__)); + if (!info) { + ACSDK_ERROR(LX("preHandleDirectiveFailed").d("reason", "nullInfo")); + return; + } + + if (!info->directive) { + ACSDK_ERROR(LX("preHandleDirectiveFailed").d("reason", "nullDirective")); + return; + } + + // Both preHandleDirective and handleDirectiveImmediately interfaces only handle message + // with empty dialogRequestId. Other messages (having dialogRequestId in header) will be + // queued to handle sequentially. + if (info->directive->getDialogRequestId().empty()) { + std::string errMessage; + ExceptionErrorType errType; + + if (handleDirectiveHelper(info, &errMessage, &errType)) { + if (info->result) { + info->result->setCompleted(); + } + } else { + ACSDK_ERROR(LX("preHandleDirectiveFailed").d("reason", errMessage)); + m_exceptionEncounteredSender->sendExceptionEncountered( + info->directive->getUnparsedDirective(), errType, errMessage); + if (info->result) { + info->result->setFailed(errMessage); + } + } + removeDirective(info->directive->getMessageId()); + } } bool InteractionModelCapabilityAgent::handleDirectiveHelper( @@ -198,6 +232,7 @@ bool InteractionModelCapabilityAgent::handleDirectiveHelper( return false; } m_directiveSequencer->setDialogRequestId(uuid); + ACSDK_DEBUG(LX(__func__).d("processDirective", directiveName).d("dialogRequestId", uuid)); } else { *errMessage = "Dialog Request ID not specified"; *type = ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED; @@ -233,10 +268,12 @@ void InteractionModelCapabilityAgent::handleDirective(std::shared_ptrdirective->getDialogRequestId().empty() && handleDirectiveHelper(info, &errMessage, &errType)) { if (info->result) { info->result->setCompleted(); } diff --git a/CapabilityAgents/InteractionModel/acsdkInteractionModel/test/InteractionModelCapabilityAgentTest.cpp b/CapabilityAgents/InteractionModel/acsdkInteractionModel/test/InteractionModelCapabilityAgentTest.cpp index 14e1e660aa..bb372dda65 100644 --- a/CapabilityAgents/InteractionModel/acsdkInteractionModel/test/InteractionModelCapabilityAgentTest.cpp +++ b/CapabilityAgents/InteractionModel/acsdkInteractionModel/test/InteractionModelCapabilityAgentTest.cpp @@ -57,6 +57,21 @@ static const std::string CORRECT_NEW_DIALOG_REQUEST_DIRECTIVE_JSON_STRING = R"de } })delim"; +/// A sample Directive JSON string for the purposes of creating an AVSDirective object. +static const std::string CORRECT_REQUEST_PROCESSING_STARTED_DIRECTIVE_JSON_STRING = R"delim( + { + "directive": { + "header": { + "namespace": "InteractionModel", + "name": "RequestProcessingStarted", + "messageId": "12345", + "dialogRequestId": "3456" + }, + "payload": { + } + } + })delim"; + /// An invalid NewDialogRequest directive with an incorrect name static const std::string INCORRECT_NEW_DIALOG_REQUEST_DIRECTIVE_JSON_STRING_1 = R"delim( { @@ -147,6 +162,21 @@ static const std::string RPC_DIRECTIVE_JSON_STRING = R"delim( // Timeout to wait before indicating a test failed. std::chrono::milliseconds TIMEOUT{500}; +/// A wrapper for InteractionModelCapabilityAgent for easy testing +class InteractionModelCapabilityAgentWrapper : public InteractionModelCapabilityAgent { +public: + static void handleDirectiveWrapper( + std::shared_ptr directive, + std::shared_ptr capabilityAgent) { + capabilityAgent->handleDirective(std::make_shared(directive, nullptr)); + } + static void preHandleDirectiveWrapper( + std::shared_ptr directive, + std::shared_ptr capabilityAgent) { + capabilityAgent->preHandleDirective(std::make_shared(directive, nullptr)); + } +}; + /// Test harness for @c InteractionModelCapabilityAgent class. class InteractionModelCapabilityAgentTest : public Test { public: @@ -320,6 +350,55 @@ TEST_F(InteractionModelCapabilityAgentTest, test_processNewDialogRequestID) { ASSERT_EQ(TEST_DIALOG_REQUEST_AVS, m_mockDirectiveSequencer->getDialogRequestId()); } +/** + * Test to verify if a valid NewDialogRequest directive will set the dialogRequestID in the directive sequencer + * in preHandle hook. + */ +TEST_F(InteractionModelCapabilityAgentTest, test_preHandledNewDialogRequestID) { + // Create a dummy AVSDirective. + auto directivePair = AVSDirective::create(CORRECT_NEW_DIALOG_REQUEST_DIRECTIVE_JSON_STRING, nullptr, ""); + std::shared_ptr directive = std::move(directivePair.first); + std::shared_ptr agent = std::dynamic_pointer_cast(m_interactionModelCA); + + agent->preHandleDirective(directive, nullptr); + ASSERT_EQ(TEST_DIALOG_REQUEST_AVS, m_mockDirectiveSequencer->getDialogRequestId()); +} + +/** + * Test to verify if preHandle interface will NOT process directive having dialogRequestID. + */ +TEST_F(InteractionModelCapabilityAgentTest, test_preHandledRequestProcessingStarted) { + // Create a dummy AVSDirective. + auto directivePair = AVSDirective::create(CORRECT_REQUEST_PROCESSING_STARTED_DIRECTIVE_JSON_STRING, nullptr, ""); + std::shared_ptr directive = std::move(directivePair.first); + + InteractionModelCapabilityAgentWrapper::preHandleDirectiveWrapper(directive, m_interactionModelCA); + ASSERT_EQ("", m_mockDirectiveSequencer->getDialogRequestId()); +} + +/** + * Test to verify if preHandle interface will ignore directives with dialogRequestId. + */ +TEST_F(InteractionModelCapabilityAgentTest, test_preHandledNullNewDialogRequestID) { + std::shared_ptr agent = std::dynamic_pointer_cast(m_interactionModelCA); + + agent->preHandleDirective(nullptr, nullptr); + ASSERT_EQ("", m_mockDirectiveSequencer->getDialogRequestId()); +} + +/** + * Test to verify if a valid NewDialogRequest directive with empty dialogRequestId + * will not be handled in handleDirective hook. + */ +TEST_F(InteractionModelCapabilityAgentTest, test_handledNewDialogRequestID) { + // Create a dummy AVSDirective. + auto directivePair = AVSDirective::create(CORRECT_NEW_DIALOG_REQUEST_DIRECTIVE_JSON_STRING, nullptr, ""); + std::shared_ptr directive = std::move(directivePair.first); + + InteractionModelCapabilityAgentWrapper::handleDirectiveWrapper(directive, m_interactionModelCA); + ASSERT_EQ("", m_mockDirectiveSequencer->getDialogRequestId()); +} + /** * Test to verify if interface will ignore null directives */ diff --git a/CapabilityAgents/InteractionModel/acsdkInteractionModelInterfaces/include/acsdkInteractionModelInterfaces/InteractionModelNotifierInterface.h b/CapabilityAgents/InteractionModel/acsdkInteractionModelInterfaces/include/acsdkInteractionModelInterfaces/InteractionModelNotifierInterface.h index 17170763c3..0f1a8e2cdc 100644 --- a/CapabilityAgents/InteractionModel/acsdkInteractionModelInterfaces/include/acsdkInteractionModelInterfaces/InteractionModelNotifierInterface.h +++ b/CapabilityAgents/InteractionModel/acsdkInteractionModelInterfaces/include/acsdkInteractionModelInterfaces/InteractionModelNotifierInterface.h @@ -18,7 +18,7 @@ #include -#include +#include #include "acsdkInteractionModelInterfaces/InteractionModelRequestProcessingObserverInterface.h" @@ -29,7 +29,7 @@ namespace acsdkInteractionModelInterfaces { * Interface for registering to observe acsdkInteractionModel RequestProcessing notifications. */ using InteractionModelNotifierInterface = - acsdkNotifierInterfaces::NotifierInterface; + notifierInterfaces::NotifierInterface; } // namespace acsdkInteractionModelInterfaces } // namespace alexaClientSDK diff --git a/CapabilityAgents/ModeController/src/ModeControllerAttributeBuilder.cpp b/CapabilityAgents/ModeController/src/ModeControllerAttributeBuilder.cpp index 9186da72b3..3bbef75b6d 100644 --- a/CapabilityAgents/ModeController/src/ModeControllerAttributeBuilder.cpp +++ b/CapabilityAgents/ModeController/src/ModeControllerAttributeBuilder.cpp @@ -27,7 +27,7 @@ using namespace avsCommon::sdkInterfaces::modeController; using namespace avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG{"ModeControllerAttributeBuilder"}; +#define TAG "ModeControllerAttributeBuilder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/ModeController/src/ModeControllerCapabilityAgent.cpp b/CapabilityAgents/ModeController/src/ModeControllerCapabilityAgent.cpp index 84567f251b..e20280b49b 100644 --- a/CapabilityAgents/ModeController/src/ModeControllerCapabilityAgent.cpp +++ b/CapabilityAgents/ModeController/src/ModeControllerCapabilityAgent.cpp @@ -40,7 +40,7 @@ using namespace avsCommon::utils::json; using namespace rapidjson; /// String to identify log entries originating from this file. -static const std::string TAG{"ModeControllerCapabilityAgent"}; +#define TAG "ModeControllerCapabilityAgent" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -269,7 +269,7 @@ void ModeControllerCapabilityAgent::handleDirective(std::shared_ptrdirective->getName(); if (!info->directive->getEndpoint().hasValue() || @@ -311,7 +311,7 @@ void ModeControllerCapabilityAgent::provideState( ACSDK_DEBUG5( LX(__func__).d("contextRequestToken", contextRequestToken).sensitive("stateProviderName", stateProviderName)); - m_executor.submit([this, stateProviderName, contextRequestToken] { + m_executor.execute([this, stateProviderName, contextRequestToken] { ACSDK_DEBUG5(LX("provideStateInExecutor")); executeProvideState(stateProviderName, contextRequestToken); }); @@ -402,7 +402,7 @@ void ModeControllerCapabilityAgent::onModeChanged(const ModeState& mode, const A return; } - m_executor.submit([this, mode, cause] { + m_executor.execute([this, mode, cause] { m_contextManager->reportStateChange( CapabilityTag(NAMESPACE, MODEVALUE_PROPERTY_NAME, m_endpointId, m_instance), buildCapabilityState(mode), diff --git a/CapabilityAgents/PlaybackController/src/PlaybackCommand.cpp b/CapabilityAgents/PlaybackController/src/PlaybackCommand.cpp index bfb06db7d0..4fed6ecc2d 100644 --- a/CapabilityAgents/PlaybackController/src/PlaybackCommand.cpp +++ b/CapabilityAgents/PlaybackController/src/PlaybackCommand.cpp @@ -28,7 +28,7 @@ using namespace avsCommon::avs; using namespace rapidjson; /// String to identify log entries originating from this file. -static const std::string TAG("PlaybackCommand"); +#define TAG "PlaybackCommand" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/PlaybackController/src/PlaybackController.cpp b/CapabilityAgents/PlaybackController/src/PlaybackController.cpp index 07401dfa16..caacbd1e15 100644 --- a/CapabilityAgents/PlaybackController/src/PlaybackController.cpp +++ b/CapabilityAgents/PlaybackController/src/PlaybackController.cpp @@ -40,7 +40,7 @@ static const std::string PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_NAME = "Playbac static const std::string PLAYBACKCONTROLLER_CAPABILITY_INTERFACE_VERSION = "1.1"; /// String to identify log entries originating from this file. -static const std::string TAG("PlaybackController"); +#define TAG "PlaybackController" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -120,7 +120,7 @@ void PlaybackController::handleCommand(const PlaybackCommand& command) { }; ACSDK_DEBUG9(LX("buttonPressed").d("Button", command)); - m_executor.submit(task); + m_executor.execute(task); } void PlaybackController::onButtonPressed(PlaybackButton button) { @@ -167,11 +167,12 @@ void PlaybackController::onContextAvailable(const std::string& jsonContext) { }; ACSDK_DEBUG9(LX("onContextAvailable")); - m_executor.submit(task); + m_executor.execute(task); } void PlaybackController::onContextFailure(const ContextRequestError error) { auto task = [this, error]() { + (void)error; // mark capture as used when logging is disabled. if (m_commands.empty()) { // The queue shouldn't be empty, log a warning message and return here. ACSDK_WARN(LX("onContextFailureExecutor").m("Queue is empty, return.")); @@ -189,7 +190,7 @@ void PlaybackController::onContextFailure(const ContextRequestError error) { }; ACSDK_DEBUG9(LX("onContextFailure")); - m_executor.submit(task); + m_executor.execute(task); } PlaybackController::PlaybackController( diff --git a/CapabilityAgents/PlaybackController/src/PlaybackRouter.cpp b/CapabilityAgents/PlaybackController/src/PlaybackRouter.cpp index c47f2dcfb4..8f58d5339c 100644 --- a/CapabilityAgents/PlaybackController/src/PlaybackRouter.cpp +++ b/CapabilityAgents/PlaybackController/src/PlaybackRouter.cpp @@ -26,7 +26,7 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::avs; /// String to identify log entries originating from this file. -static const std::string TAG("PlaybackRouter"); +#define TAG "PlaybackRouter" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/PowerController/src/PowerControllerCapabilityAgent.cpp b/CapabilityAgents/PowerController/src/PowerControllerCapabilityAgent.cpp index 21d799a2b8..3e3b85fead 100644 --- a/CapabilityAgents/PowerController/src/PowerControllerCapabilityAgent.cpp +++ b/CapabilityAgents/PowerController/src/PowerControllerCapabilityAgent.cpp @@ -28,7 +28,7 @@ using namespace avsCommon::utils; using namespace avsCommon::utils::configuration; /// String to identify log entries originating from this file. -static const std::string TAG{"PowerControllerCapabilityAgent"}; +#define TAG "PowerControllerCapabilityAgent" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -164,7 +164,7 @@ void PowerControllerCapabilityAgent::handleDirective(std::shared_ptrdirective->getName(); if (!info->directive->getEndpoint().hasValue() || @@ -195,7 +195,7 @@ void PowerControllerCapabilityAgent::provideState( ACSDK_DEBUG5( LX(__func__).d("contextRequestToken", contextRequestToken).sensitive("stateProviderName", stateProviderName)); - m_executor.submit([this, stateProviderName, contextRequestToken] { + m_executor.execute([this, stateProviderName, contextRequestToken] { ACSDK_DEBUG5(LX("provideStateInExecutor")); executeProvideState(stateProviderName, contextRequestToken); }); @@ -254,7 +254,7 @@ void PowerControllerCapabilityAgent::onPowerStateChanged( return; } - m_executor.submit([this, powerState, cause] { + m_executor.execute([this, powerState, cause] { ACSDK_DEBUG5(LX("onPowerStateChangedInExecutor")); m_contextManager->reportStateChange( CapabilityTag(NAMESPACE, POWERSTATE_PROPERTY_NAME, m_endpointId), buildCapabilityState(powerState), cause); diff --git a/CapabilityAgents/RangeController/src/RangeControllerAttributeBuilder.cpp b/CapabilityAgents/RangeController/src/RangeControllerAttributeBuilder.cpp index e68d2ab07b..208bdbb0cc 100644 --- a/CapabilityAgents/RangeController/src/RangeControllerAttributeBuilder.cpp +++ b/CapabilityAgents/RangeController/src/RangeControllerAttributeBuilder.cpp @@ -29,7 +29,7 @@ using namespace avsCommon::sdkInterfaces::rangeController; using namespace avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG{"RangeControllerAttributeBuilder"}; +#define TAG "RangeControllerAttributeBuilder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/RangeController/src/RangeControllerCapabilityAgent.cpp b/CapabilityAgents/RangeController/src/RangeControllerCapabilityAgent.cpp index d885c0fc63..d21494e33e 100644 --- a/CapabilityAgents/RangeController/src/RangeControllerCapabilityAgent.cpp +++ b/CapabilityAgents/RangeController/src/RangeControllerCapabilityAgent.cpp @@ -41,7 +41,7 @@ using namespace avsCommon::utils::json; using namespace rapidjson; /// String to identify log entries originating from this file. -static const std::string TAG{"RangeControllerCapabilityAgent"}; +#define TAG "RangeControllerCapabilityAgent" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -271,7 +271,7 @@ void RangeControllerCapabilityAgent::handleDirective(std::shared_ptrdirective->getName(); if (!info->directive->getEndpoint().hasValue() || @@ -313,7 +313,7 @@ void RangeControllerCapabilityAgent::provideState( ACSDK_DEBUG5( LX(__func__).d("contextRequestToken", contextRequestToken).sensitive("stateProviderName", stateProviderName)); - m_executor.submit([this, stateProviderName, contextRequestToken] { + m_executor.execute([this, stateProviderName, contextRequestToken] { ACSDK_DEBUG5(LX("provideStateInExecutor")); executeProvideState(stateProviderName, contextRequestToken); }); @@ -417,7 +417,7 @@ void RangeControllerCapabilityAgent::onRangeChanged( return; } - m_executor.submit([this, rangeState, cause] { + m_executor.execute([this, rangeState, cause] { m_contextManager->reportStateChange( CapabilityTag(NAMESPACE, RANGEVALUE_PROPERTY_NAME, m_endpointId, m_instance), buildCapabilityState(rangeState), diff --git a/CapabilityAgents/SoftwareComponentReporter/src/SoftwareComponentReporterCapabilityAgent.cpp b/CapabilityAgents/SoftwareComponentReporter/src/SoftwareComponentReporterCapabilityAgent.cpp index bfa4c8e65c..1784ee49d1 100644 --- a/CapabilityAgents/SoftwareComponentReporter/src/SoftwareComponentReporterCapabilityAgent.cpp +++ b/CapabilityAgents/SoftwareComponentReporter/src/SoftwareComponentReporterCapabilityAgent.cpp @@ -31,7 +31,7 @@ using namespace avsCommon::utils::configuration; using namespace avsCommon::utils::json; /// String to identify log entries originating from this file. -static const std::string TAG{"SoftwareComponentReporterCapabilityAgent"}; +#define TAG "SoftwareComponentReporterCapabilityAgent" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/SpeakerManager/CMakeLists.txt b/CapabilityAgents/SpeakerManager/CMakeLists.txt index d5a55a27b2..7cfaf2ea82 100644 --- a/CapabilityAgents/SpeakerManager/CMakeLists.txt +++ b/CapabilityAgents/SpeakerManager/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 3.0) -project(SpeakerManager LANGUAGES CXX) +project(acsdkSpeakerManager LANGUAGES CXX) -include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) - -add_subdirectory("src") -add_subdirectory("test") +add_subdirectory("SpeakerManager") +add_subdirectory("SpeakerManagerComponent") diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/CMakeLists.txt b/CapabilityAgents/SpeakerManager/SpeakerManager/CMakeLists.txt new file mode 100644 index 0000000000..9b1c463097 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.0) +project(acsdkSpeakerManager LANGUAGES CXX) + +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +add_subdirectory("src") +add_subdirectory("test") diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/doc/Namespaces.dox b/CapabilityAgents/SpeakerManager/SpeakerManager/doc/Namespaces.dox new file mode 100644 index 0000000000..f3667514fc --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/doc/Namespaces.dox @@ -0,0 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * @namespace alexaClientSDK::speakerManager + * @brief Speaker API Capability Agent Interface and Implementation. + * + * This namespace contains public API and implementation for Speaker API capability agent. + * @see Lib_acsdkSpeakerManager + * @ingroup Lib_acsdkSpeakerManager + */ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/doc/SpeakerManager.dox b/CapabilityAgents/SpeakerManager/SpeakerManager/doc/SpeakerManager.dox new file mode 100644 index 0000000000..b9c08883b8 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/doc/SpeakerManager.dox @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * @defgroup Lib_acsdkSpeakerManager Capability Agent for AVS Speaker 1.0 Interface + * @brief Capability Agent API and implementation for + * AVS Speaker 1.0 + * interface. + * + * @see https://developer.amazon.com/en-US/docs/alexa/alexa-voice-service/speaker.html Speaker 1.0 Interface + * + * @see alexaClientSDK::speakerManager + * @see alexaClientSDK::speakerManager::test + * @see Lib_acsdkSpeakerManagerComponent + */ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/Factories.h b/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/Factories.h new file mode 100644 index 0000000000..468f0814ac --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/Factories.h @@ -0,0 +1,143 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_FACTORIES_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_FACTORIES_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace alexaClientSDK { +namespace speakerManager { + +/** + * @brief Creates speaker manager CA. + * + * Method creates new speaker manager capability agent, adds supplied volume interfaces, and registers instance in + * capabilities registry and in shutdown manager. + * + * Additional channel volume interfaces can be added after construction using public SpeakerManagerInterface methods. + * + * Speaker manager groups all channels by type, and applies volume settings and configurations uniformly to all channels + * of the same type. + * + * Speaker manager uses SpeakerManagerConfigInterface to load initial (bootstrap) platform configuration, and + * SpeakerManagerStorageInterface to store and load persistent settings. Those interfaces + * + * @param[in] config Interface to load platform configuration. + * @param[in] storage Interface to load and store persistent configuration. + * @param[in] contextManager A @c ContextManagerInterface to manage the context. + * @param[in] messageSender A @c MessageSenderInterface to send messages to AVS. + * @param[in] exceptionEncounteredSender An @c ExceptionEncounteredSenderInterface to send directive processing + * exceptions to AVS. + * @param[in] metricRecorder The metric recorder. + * @param[in] shutdownNotifier Factory uses this interface to register for shutdown notifications. + * @param[in] endpointCapabilitiesRegistrar Factory uses this interface to register capability. + * @param[in] volumeInterfaces Optional vector of @c ChannelVolumeInterfaces to register. Additional + * interfaces can be added with @c + * SpeakerManagerInterface::addChannelVolumeInterface() calls. + * + * @see createSpeakerManagerStorage() + * @see createSpeakerManagerConfig() + * @see createChannelVolumeFactory() + * + * @ingroup Lib_acsdkSpeakerManager + */ +std::shared_ptr createSpeakerManagerCapabilityAgent( + std::shared_ptr config, + std::shared_ptr storage, + std::shared_ptr contextManager, + std::shared_ptr messageSender, + std::shared_ptr exceptionEncounteredSender, + std::shared_ptr metricRecorder, + const std::shared_ptr& shutdownNotifier, + const std::shared_ptr& + endpointCapabilitiesRegistrar, + const std::vector>& volumeInterfaces = + {}) noexcept; + +/** + * @brief Create default implementation of @c ChannelVolumeFactoryInterface. + * + * @return Channel volume factory interface or nullptr on error. + */ +std::shared_ptr createChannelVolumeFactory() noexcept; + +/** + * @brief Adapt generic @c MiscStorageInterface into @c SpeakerManagerStorageInterface. + * + * Method returns an adapter of @c SpeakerManagerStorageInterface to @c SpeakerManagerStorageInterface. + * + * @param[in] storage Reference of @c MiscStorageInterface. This parameter must not be nullptr. + * + * @return Storage interface or nullptr on error. + * + * @ingroup Lib_acsdkSpeakerManager + */ +std::shared_ptr createSpeakerManagerStorage( + std::shared_ptr storage) noexcept; + +/** + * @brief Creates configuration interface for speaker manager. + * + * The method returns an interface that accesses configuration using @c ConfigurationNode facility. The returned object + * uses "speakerManagerCapabilityAgent" child and looks up the following keys: + * - "persistentStorage" -- Boolean flag that indicates if persistent storage is enabled. + * - "minUnmuteVolume" -- Minimum volume level for unmuting the channel. This setting applies to all channel types. + * - "defaultSpeakerVolume" -- Default speaker volume. + * - "defaultAlertsVolume" -- Default alerts volume. + * - "restoreMuteState" -- Boolean flag that indicates if mute state shall be preserved between device reboots. + * + * If AlexaClientSDKConfig.json configuration file is used, the example configuration may look like: + * @code{.json} + * { + * "speakerManagerCapabilityAgent": { + * "persistentStorage": true, + * "minUnmuteVolume": 10, + * "defaultSpeakerVolume": 40, + * "defaultAlertsVolume": 40, + * "restoreMuteState": true + * } + * } + * @endcode + * + * @return Interface to load initial configuration or nullptr on error. + * + * @ingroup Lib_acsdkSpeakerManager + */ +std::shared_ptr createSpeakerManagerConfig() noexcept; + +} // namespace speakerManager +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_FACTORIES_H_ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerConfigInterface.h b/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerConfigInterface.h new file mode 100644 index 0000000000..bc59f3688a --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerConfigInterface.h @@ -0,0 +1,85 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERCONFIGINTERFACE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERCONFIGINTERFACE_H_ + +#include + +namespace alexaClientSDK { +namespace speakerManager { + +struct SpeakerManagerStorageState; + +/** + * @brief Speaker manager configuration interface. + * + * This interface provides initial configuration for speaker manager capability agent. + * + * @see createSpeakerManagerConfig() + * + * @ingroup Lib_acsdkSpeakerManager + */ +struct SpeakerManagerConfigInterface { + /** + * @brief Virtual destructor to assure proper cleanup of derived types. + */ + virtual ~SpeakerManagerConfigInterface() noexcept = default; + + /** + * @brief Load persistent storage setting from platform configuration. + * + * @param[out] persistentStorage Flag if persistent storage is enabled for speaker settings. + * @return True if value is loaded, false is value is not present, is out of range, or operation failed. + */ + virtual bool getPersistentStorage(bool& persistentStorage) noexcept = 0; + + /** + * @brief Load minimum unmute volume from platform configuration. + * + * @param[out] minUnmuteVolume Minimum volume for unmuting speakers. The value must be in range 0..100 inclusive. + * @return True if value is loaded, false if value is not present, is out of range, or operation failed. + */ + virtual bool getMinUnmuteVolume(std::uint8_t& minUnmuteVolume) noexcept = 0; + + /** + * @brief Load minimum unmute volume from platform configuration. + * + * @param[out] restoreMuteState Flag if the speaker mute state must be preserved between sessions. + * @return True if value is loaded, false if value is not present, is out of range, or operation failed. + */ + virtual bool getRestoreMuteState(bool& restoreMuteState) noexcept = 0; + + /** + * @brief Load minimum unmute volume from platform configuration. + * + * @param[out] defaultSpeakerVolume Default volume for speaker channel. The value must be in range 0..100 inclusive. + * @return True if value is loaded, false if value is not present, is out of range, or operation failed. + */ + virtual bool getDefaultSpeakerVolume(std::uint8_t& defaultSpeakerVolume) noexcept = 0; + + /** + * @brief Load minimum unmute volume from platform configuration. + * + * @param[out] defaultAlertsVolume Default volume for alerts channel. The value must be in range 0..100 inclusive. + * @return True if value is loaded, false if value is not present, is out of range, or operation failed. + */ + virtual bool getDefaultAlertsVolume(std::uint8_t& defaultAlertsVolume) noexcept = 0; +}; + +} // namespace speakerManager +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERCONFIGINTERFACE_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageInterface.h b/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerStorageInterface.h similarity index 65% rename from CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageInterface.h rename to CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerStorageInterface.h index cd100bb911..af34a01834 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageInterface.h +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerStorageInterface.h @@ -13,41 +13,43 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGEINTERFACE_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGEINTERFACE_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERSTORAGEINTERFACE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERSTORAGEINTERFACE_H_ namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { struct SpeakerManagerStorageState; /** - * Storage interface for SpeakerManager. + * @brief Speaker manager storage interface. + * + * @see SpeakerManagerStorageState + * + * @ingroup Lib_acsdkSpeakerManager */ struct SpeakerManagerStorageInterface { /** * Virtual destructor to assure proper cleanup of derived types. */ - virtual ~SpeakerManagerStorageInterface() = default; + virtual ~SpeakerManagerStorageInterface() noexcept = default; /** * Loads state from underlying storage. * @param[out] state Pointer to state structure for loaded values. * @return Boolean flag if the operation is successful. */ - virtual bool loadState(SpeakerManagerStorageState& state) = 0; + virtual bool loadState(SpeakerManagerStorageState& state) noexcept = 0; /** * Stores state to underlying storage. * @param[in] state Reference of state structure for values to store. * @return Boolean flag if the operation is successful. */ - virtual bool saveState(const SpeakerManagerStorageState& state) = 0; + virtual bool saveState(const SpeakerManagerStorageState& state) noexcept = 0; }; } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGEINTERFACE_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERSTORAGEINTERFACE_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageState.h b/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerStorageState.h similarity index 61% rename from CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageState.h rename to CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerStorageState.h index 0037b1cbeb..dc010b9679 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerStorageState.h +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/include/acsdk/SpeakerManager/SpeakerManagerStorageState.h @@ -13,18 +13,23 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGESTATE_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGESTATE_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERSTORAGESTATE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERSTORAGESTATE_H_ #include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { /** - * Storage state for SpeakerManager. SpeakerManager configuration includes configuration for two channel types: speaker - * and alerts. There can be any number of channels for each of types, but all of them share the same configuraiton. + * @brief Storage state for SpeakerManager. + * + * SpeakerManager configuration includes configuration for two channel types: speaker and alerts. There can be any + * number of channels for each of types, but all of them share the same configuration. + * + * @see SpeakerManagerStorageInterface + * + * @ingroup Lib_acsdkSpeakerManager */ struct SpeakerManagerStorageState { /** @@ -43,7 +48,6 @@ struct SpeakerManagerStorageState { }; } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERSTORAGESTATE_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERSTORAGESTATE_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/ChannelVolumeManager.h b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/ChannelVolumeManager.h similarity index 87% rename from CapabilityAgents/SpeakerManager/include/SpeakerManager/ChannelVolumeManager.h rename to CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/ChannelVolumeManager.h index 9f670118b1..369c99e54f 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/ChannelVolumeManager.h +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/ChannelVolumeManager.h @@ -13,19 +13,23 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_CHANNELVOLUMEMANAGER_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_CHANNELVOLUMEMANAGER_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_CHANNELVOLUMEMANAGER_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_CHANNELVOLUMEMANAGER_H_ -#include #include +#include + namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { /** - * ChannelVolumeManager provides a concrete implementation of the ChannelVolumeInterface + * @brief Default implementation for @c ChannelVolumeInterface. + * + * ChannelVolumeManager provides a concrete implementation of the @c ChannelVolumeInterface. * It controls an underlying @c SpeakerInterface object and provides functionality to be able * to set SpeakerSettings and control Channel Volume Attenuation for this underlying SpeakerInterface. + * + * @ingroup Lib_acsdkSpeakerManager */ class ChannelVolumeManager : public avsCommon::sdkInterfaces::ChannelVolumeInterface { public: @@ -41,7 +45,7 @@ class ChannelVolumeManager : public avsCommon::sdkInterfaces::ChannelVolumeInter std::shared_ptr speaker, avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type = avsCommon::sdkInterfaces::ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, - std::function volumeCurve = nullptr); + std::function volumeCurve = nullptr) noexcept; /// ChannelVolumeInterface Functions. /// @{ @@ -73,7 +77,7 @@ class ChannelVolumeManager : public avsCommon::sdkInterfaces::ChannelVolumeInter ChannelVolumeManager( std::shared_ptr speaker, avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, - VolumeCurveFunction volumeCurve); + VolumeCurveFunction volumeCurve) noexcept; /** * Default Volume Curve Implementation that determines the desired attenuated @@ -94,7 +98,7 @@ class ChannelVolumeManager : public avsCommon::sdkInterfaces::ChannelVolumeInter * @param unduckedVolume current volume of the channel * @return int8_t volume to be attenuated to as per the volume curve */ - static int8_t defaultVolumeAttenuateFunction(int8_t unduckedVolume); + static int8_t defaultVolumeAttenuateFunction(int8_t unduckedVolume) noexcept; /// Mutex to synchronize the operations. mutable std::mutex m_mutex; @@ -115,7 +119,6 @@ class ChannelVolumeManager : public avsCommon::sdkInterfaces::ChannelVolumeInter const avsCommon::sdkInterfaces::ChannelVolumeInterface::Type m_type; }; } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_CHANNELVOLUMEMANAGER_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_CHANNELVOLUMEMANAGER_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/DefaultChannelVolumeFactory.h b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/DefaultChannelVolumeFactory.h similarity index 66% rename from CapabilityAgents/SpeakerManager/include/SpeakerManager/DefaultChannelVolumeFactory.h rename to CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/DefaultChannelVolumeFactory.h index aae9897390..28a993c115 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/DefaultChannelVolumeFactory.h +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/DefaultChannelVolumeFactory.h @@ -13,30 +13,25 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_DEFAULTCHANNELVOLUMEFACTORY_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_DEFAULTCHANNELVOLUMEFACTORY_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_DEFAULTCHANNELVOLUMEFACTORY_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_DEFAULTCHANNELVOLUMEFACTORY_H_ #include -#include "SpeakerManager/ChannelVolumeManager.h" +#include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { /** + * @brief Default channel volume factory implementation. + * * The @c DefaultChannelVolumeFactory provides a default implementation of @c ChannelVolumeFactoryInterface * using the @c ChannelVolumeManager. + * + * @ingroup Lib_acsdkSpeakerManager */ class DefaultChannelVolumeFactory : public alexaClientSDK::avsCommon::sdkInterfaces::ChannelVolumeFactoryInterface { public: - /** - * Creates a new @c ChannelVolumeFactoryInterface implementation. - * - * @return A shared ptr to a new @c ChannelVolumeFactoryInterface. - */ - static std::shared_ptr - createChannelVolumeFactoryInterface(); - /// ChannelVolumeFactoryInterface Functions. /// @{ virtual std::shared_ptr @@ -46,7 +41,8 @@ class DefaultChannelVolumeFactory : public alexaClientSDK::avsCommon::sdkInterfa std::function volumeCurve) override; /// @} }; + } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_DEFAULTCHANNELVOLUMEFACTORY_H_ + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_DEFAULTCHANNELVOLUMEFACTORY_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManager.h similarity index 86% rename from CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h rename to CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManager.h index 229b724c5a..cfac317a7a 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManager.h +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManager.h @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGER_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGER_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGER_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGER_H_ #include #include @@ -22,8 +22,11 @@ #include #include -#include #include +#include +#include +#include +#include #include #include #include @@ -41,15 +44,13 @@ #include #include #include -#include -#include -#include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { /** + * @brief Capability Agent for Speaker API. + * * This class implements a @c CapabilityAgent that handles the AVS @c Speaker API. * * The @c SpeakerManager can handle multiple @c ChannelVolumeInterface objects and dedupe them with @@ -67,6 +68,8 @@ namespace speakerManager { * @endcode * * Clients may extend the @c ChannelVolumeInterface::Type enum if multiple independent volume controls are needed. + * + * @ingroup Lib_acsdkSpeakerManager */ class SpeakerManager : public avsCommon::avs::CapabilityAgent @@ -90,16 +93,15 @@ class SpeakerManager * @param metricRecorder The metric recorder. */ static std::shared_ptr createSpeakerManagerCapabilityAgent( - const std::shared_ptr& storage, - const std::shared_ptr& contextManager, - const std::shared_ptr& messageSender, - const std::shared_ptr& - exceptionEncounteredSender, + std::shared_ptr config, + std::shared_ptr storage, + std::shared_ptr contextManager, + std::shared_ptr messageSender, + std::shared_ptr exceptionEncounteredSender, const std::shared_ptr& shutdownNotifier, - const acsdkManufactory::Annotated< - avsCommon::sdkInterfaces::endpoints::DefaultEndpointAnnotation, - avsCommon::sdkInterfaces::endpoints::EndpointCapabilitiesRegistrarInterface>& endpointCapabilitiesRegistrar, - const std::shared_ptr& metricRecorder); + const std::shared_ptr& + endpointCapabilitiesRegistrar, + std::shared_ptr metricRecorder) noexcept; /** * Create an instance of @c SpeakerManager, and register the @c ChannelVolumeInterfaces that will be controlled @@ -114,12 +116,13 @@ class SpeakerManager * @param metricRecorder The metric recorder. */ static std::shared_ptr create( - const std::shared_ptr& storage, + std::shared_ptr config, + std::shared_ptr storage, const std::vector>& volumeInterfaces, std::shared_ptr contextManager, std::shared_ptr messageSender, std::shared_ptr exceptionEncounteredSender, - std::shared_ptr metricRecorder = nullptr); + std::shared_ptr metricRecorder = nullptr) noexcept; /// @name CapabilityAgent Functions /// @{ @@ -130,12 +133,12 @@ class SpeakerManager void cancelDirective(std::shared_ptr info) override; /// @} - // @name RequiresShutdown Functions + /// @name RequiresShutdown Functions /// @{ void doShutdown() override; /// @} - // @name SpeakerManagerInterface Functions + /// @name SpeakerManagerInterface Functions /// @{ std::future setVolume( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, @@ -174,6 +177,10 @@ class SpeakerManager std::future getSpeakerSettings( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings* settings) override; + void onExternalSpeakerSettingsUpdate( + avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, + const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& speakerSettings, + const NotificationProperties& properties) override; void addSpeakerManagerObserver( std::shared_ptr observer) override; void removeSpeakerManagerObserver( @@ -200,17 +207,18 @@ class SpeakerManager * @param metricRecorder The metric recorder. */ SpeakerManager( - const std::shared_ptr& speakerManagerStorage, + std::shared_ptr speakerManagerConfig, + std::shared_ptr speakerManagerStorage, const std::vector>& groupVolumeInterfaces, std::shared_ptr contextManager, std::shared_ptr messageSender, std::shared_ptr exceptionEncounteredSender, - std::shared_ptr metricRecorder); + std::shared_ptr metricRecorder) noexcept; /// Hash functor to use identifier of @c ChannelVolumeInterface as the key in SpeakerSet. struct ChannelVolumeInterfaceHash { public: - size_t operator()(const std::shared_ptr& key) const { + size_t operator()(const std::shared_ptr& key) const noexcept { if (nullptr == key) { /// This should never happen because the only way to add a ChannelVolumeInterface into the SpeakerSet /// has a guard of nullptr. @@ -225,7 +233,7 @@ class SpeakerManager public: bool operator()( const std::shared_ptr channelVolumeInterface1, - std::shared_ptr channelVolumeInterface2) const { + std::shared_ptr channelVolumeInterface2) const noexcept { if (!channelVolumeInterface1 || !channelVolumeInterface2) { /// This should never happen because the only way to add a ChannelVolumeInterface into the SpeakerSet /// has a guard of nullptr. @@ -249,7 +257,7 @@ class SpeakerManager * @param channelVolumeInterface The @c ChannelVolumeInterface object. */ void addChannelVolumeInterfaceIntoSpeakerMap( - std::shared_ptr channelVolumeInterface); + std::shared_ptr channelVolumeInterface) noexcept; /** * Parses the payload from a string into a rapidjson document. @@ -258,21 +266,21 @@ class SpeakerManager * @param document The document that will contain the payload. * @return A bool indicating the results of the operation. */ - bool parseDirectivePayload(std::string payload, rapidjson::Document* document); + bool parseDirectivePayload(std::string payload, rapidjson::Document* document) noexcept; /** * Performs clean-up after a successful handling of a directive. * * @param info The current directive being processed. */ - void executeSetHandlingCompleted(std::shared_ptr info); + void executeSetHandlingCompleted(std::shared_ptr info) noexcept; /** * Removes the directive after it's been processed. * * @param info The current directive being processed. */ - void removeDirective(std::shared_ptr info); + void removeDirective(std::shared_ptr info) noexcept; /** * Sends an exception to AVS. @@ -284,7 +292,7 @@ class SpeakerManager void sendExceptionEncountered( std::shared_ptr info, const std::string& message, - avsCommon::avs::ExceptionErrorType type); + avsCommon::avs::ExceptionErrorType type) noexcept; /** * Function to update the state of the ContextManager. @@ -295,7 +303,7 @@ class SpeakerManager */ bool updateContextManager( const avsCommon::sdkInterfaces::ChannelVolumeInterface::Type& type, - const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& settings); + const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& settings) noexcept; /** * Sends Changed events to AVS. The events are identical except for the name. @@ -305,7 +313,7 @@ class SpeakerManager */ void executeSendSpeakerSettingsChangedEvent( const std::string& eventName, - avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings settings); + avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings settings) noexcept; /** * Function to set the volume for a specific @c Type. This runs on a worker thread. @@ -319,7 +327,7 @@ class SpeakerManager bool executeSetVolume( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, int8_t volume, - const avsCommon::sdkInterfaces::SpeakerManagerInterface::NotificationProperties& properties); + const avsCommon::sdkInterfaces::SpeakerManagerInterface::NotificationProperties& properties) noexcept; /** * Function to restore the volume from a mute state. This runs on a worker thread and will not send an event or @@ -331,7 +339,7 @@ class SpeakerManager */ bool executeRestoreVolume( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, - avsCommon::sdkInterfaces::SpeakerManagerObserverInterface::Source source); + avsCommon::sdkInterfaces::SpeakerManagerObserverInterface::Source source) noexcept; /** * Function to adjust the volume for a specific @c Type. This runs on a worker thread. @@ -345,7 +353,7 @@ class SpeakerManager bool executeAdjustVolume( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, int8_t delta, - const avsCommon::sdkInterfaces::SpeakerManagerInterface::NotificationProperties& properties); + const avsCommon::sdkInterfaces::SpeakerManagerInterface::NotificationProperties& properties) noexcept; /** * Function to set the mute for a specific @c Type. This runs on a worker thread. @@ -359,7 +367,7 @@ class SpeakerManager bool executeSetMute( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, bool mute, - const avsCommon::sdkInterfaces::SpeakerManagerInterface::NotificationProperties& properties); + const avsCommon::sdkInterfaces::SpeakerManagerInterface::NotificationProperties& properties) noexcept; #ifdef ENABLE_MAXVOLUME_SETTING /** @@ -368,7 +376,7 @@ class SpeakerManager * @param type The type of speaker to set a limit on the maximum volume. * @return A bool indicating success. */ - bool executeSetMaximumVolumeLimit(const int8_t maximumVolumeLimit); + bool executeSetMaximumVolumeLimit(const int8_t maximumVolumeLimit) noexcept; #endif // ENABLE_MAXVOLUME_SETTING /** @@ -381,7 +389,7 @@ class SpeakerManager */ bool executeGetSpeakerSettings( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, - avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings* settings); + avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings* settings) noexcept; /** * Function to set the speaker settings for a specific @c ChannelVolumeInterface Type. @@ -393,7 +401,7 @@ class SpeakerManager */ bool executeSetSpeakerSettings( const avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, - const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& settings); + const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& settings) noexcept; /** * Function that initializes and populates @c m_speakerSettings for the given @c type's speaker settings. @@ -401,7 +409,7 @@ class SpeakerManager * @param type The type of speaker to initialize * @return A bool indicating success. */ - bool executeInitializeSpeakerSettings(avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type); + bool executeInitializeSpeakerSettings(avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type) noexcept; /** * Function to send events when settings have changed. @@ -416,7 +424,7 @@ class SpeakerManager const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& settings, const std::string& eventName, const avsCommon::sdkInterfaces::SpeakerManagerObserverInterface::Source& source, - const avsCommon::sdkInterfaces::ChannelVolumeInterface::Type& type); + const avsCommon::sdkInterfaces::ChannelVolumeInterface::Type& type) noexcept; /** * Function to notify the observer when a @c SpeakerSettings change has occurred. @@ -428,12 +436,12 @@ class SpeakerManager void executeNotifyObserver( const avsCommon::sdkInterfaces::SpeakerManagerObserverInterface::Source& source, const avsCommon::sdkInterfaces::ChannelVolumeInterface::Type& type, - const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& settings); + const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings& settings) noexcept; /** * Persist channel configuration. */ - void executePersistConfiguration(); + void executePersistConfiguration() noexcept; /** * Helper method to convert internally stored channel state into config format. @@ -443,14 +451,14 @@ class SpeakerManager */ void convertSettingsToChannelState( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, - SpeakerManagerStorageState::ChannelState* storageState); + SpeakerManagerStorageState::ChannelState* storageState) noexcept; /** * Get the maximum volume limit. * * @return The maximum volume limit. */ - int8_t getMaximumVolumeLimit(); + int8_t getMaximumVolumeLimit() noexcept; /** * Applies Settings to All Speakers @@ -461,8 +469,7 @@ class SpeakerManager * @param args The arguments to call the task with. * @return A bool indicating success. */ - template - bool retryAndApplySettings(Task task, Args&&... args); + bool retryAndApplySettings(const std::function& task) noexcept; /** * Configure channel volume and mute status defaults. @@ -473,23 +480,23 @@ class SpeakerManager */ void presetChannelDefaults( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, - const SpeakerManagerStorageState::ChannelState& state); + const SpeakerManagerStorageState::ChannelState& state) noexcept; /** * Configures channels with default values. */ - void loadConfiguration(); + void loadConfiguration() noexcept; /** * Updates volume and mute status on managed channels according to configured settings. */ - void updateChannelSettings(); + void updateChannelSettings() noexcept; /** * Updates managed channels according to configured settings. * @param type Channel type. */ - void updateChannelSettings(avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type); + void updateChannelSettings(avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type) noexcept; /** * Helper function to adjust input volume into acceptable range. @@ -497,7 +504,7 @@ class SpeakerManager * @param volume Input volume. * @return Volume within correct limits. */ - int8_t adjustVolumeRange(int64_t volume); + int8_t adjustVolumeRange(int64_t volume) noexcept; /// Component's configuration access. SpeakerManagerConfigHelper m_config; @@ -512,7 +519,7 @@ class SpeakerManager std::shared_ptr m_messageSender; /// the @c volume to restore to when unmuting at 0 volume - int m_minUnmuteVolume; + uint8_t m_minUnmuteVolume; /// An unordered_map contains ChannelVolumeInterfaces keyed by @c Type. Only internal function /// addChannelVolumeInterfaceIntoSpeakerMap can insert an element into this map to ensure that no invalid element @@ -541,6 +548,9 @@ class SpeakerManager /// maximumVolumeLimit The maximum volume level speakers in this system can reach. int8_t m_maximumVolumeLimit; + /// Persistent Storage flag set from configuration + bool m_persistentStorage; + /// Restore mute state flag from configuration bool m_restoreMuteState; @@ -555,7 +565,6 @@ class SpeakerManager }; } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGER_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGER_H_ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConfig.h b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConfig.h new file mode 100644 index 0000000000..d94eac7f6e --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConfig.h @@ -0,0 +1,92 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERCONFIG_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERCONFIG_H_ + +#include +#include +#include + +namespace alexaClientSDK { +namespace speakerManager { + +/** + * @brief Configuration interface for SpeakerManager. + * + * This class accesses configuration using @c ConfigurationNode facility. Internally class uses + * "speakerManagerCapabilityAgent" child and looks up the following keys: + * - "persistentStorage" -- Boolean flag that indicates if persistent storage is enabled. + * - "minUnmuteVolume" -- Minimum volume level for unmuting the channel. This setting applies to all channel types. + * - "defaultSpeakerVolume" -- Default speaker volume. + * - "defaultAlertsVolume" -- Default alerts volume. + * - "restoreMuteState" -- Boolean flag that indicates if mute state shall be preserved between device reboots. + * + * If AlexaClientSDKConfig.json configuration file is used, the example configuration may look like: + * @code{.json} + * { + * "speakerManagerCapabilityAgent": { + * "persistentStorage": true, + * "minUnmuteVolume": 10, + * "defaultSpeakerVolume": 40, + * "defaultAlertsVolume": 40, + * "restoreMuteState": true + * } + * } + * @endcode + * + * @ingroup Lib_acsdkSpeakerManager + */ +class SpeakerManagerConfig : public SpeakerManagerConfigInterface { +public: + /** + * @brief Construct object and load configuration. + */ + SpeakerManagerConfig() noexcept; + + /// @name SpeakerManagerConfigInterface Functions + /// @{ + bool getPersistentStorage(bool& persistentStorage) noexcept override; + bool getMinUnmuteVolume(std::uint8_t& minUnmuteVolume) noexcept override; + bool getRestoreMuteState(bool& restoreMuteState) noexcept override; + bool getDefaultSpeakerVolume(std::uint8_t& defaultSpeakerVolume) noexcept override; + bool getDefaultAlertsVolume(std::uint8_t& defaultAlertsVolume) noexcept override; + /// @} +private: + /** + * @brief Load and validate values from platform configuration. + */ + void loadPlatformConfig() noexcept; + + /// Flag if persistent storage is enabled for speaker settings. + avsCommon::utils::Optional m_persistentStorage; + + /// Minimum volume for unmuting speakers. The value must be in range 0..100 inclusive. + avsCommon::utils::Optional m_minUnmuteVolume; + + /// Flag if the speaker mute state must be preserved between sessions. + avsCommon::utils::Optional m_restoreMuteState; + + /// Default volume for speaker channel. The value must be in range 0..100 inclusive. + avsCommon::utils::Optional m_defaultSpeakerVolume; + + /// Default volume for alerts channel. The value must be in range 0..100 inclusive. + avsCommon::utils::Optional m_defaultAlertsVolume; +}; + +} // namespace speakerManager +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERCONFIG_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConfigHelper.h b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConfigHelper.h similarity index 60% rename from CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConfigHelper.h rename to CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConfigHelper.h index 2561933635..f03167e7f1 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConfigHelper.h +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConfigHelper.h @@ -13,22 +13,23 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCONFIGHELPER_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCONFIGHELPER_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERCONFIGHELPER_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERCONFIGHELPER_H_ -#include -#include +#include +#include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { /** - * Helper class to manage configuration operations for SpeakerManager CA. + * @brief Helper class to manage configuration operations for SpeakerManager CA. * * This class implements all configuration operations and merges logic of accessing different configuration sources. * SpeakerManager get configuration values from three sources: hardcoded values, platform configuration, and persistent * storage. + * + * @ingroup Lib_acsdkSpeakerManager */ class SpeakerManagerConfigHelper { public: @@ -36,7 +37,9 @@ class SpeakerManagerConfigHelper { * Creates object. * @param[in] storage Storage interface. */ - SpeakerManagerConfigHelper(const std::shared_ptr& storage); + SpeakerManagerConfigHelper( + std::shared_ptr config, + std::shared_ptr storage) noexcept; /** * Load configuration. @@ -46,7 +49,7 @@ class SpeakerManagerConfigHelper { * * @param[out] state Pointer to configuration container to fill with config values. */ - void loadState(SpeakerManagerStorageState& state); + void loadState(SpeakerManagerStorageState& state) noexcept; /** * Saves configuration to to config storage. @@ -55,7 +58,16 @@ class SpeakerManagerConfigHelper { * * @return Boolean that indicates operation success. */ - bool saveState(const SpeakerManagerStorageState& state); + bool saveState(const SpeakerManagerStorageState& state) noexcept; + + /** + * Loads if persistent storage is enabled from configuration. By default persistent storage is not enabled, but it + * can be overridden by configuration. + * + * @return Returns configured value, where true indicates that the persistent storage flag has been retrieved, and + * false indicates that a default shall be kept. + */ + bool getPersistentStorage() const noexcept; /** * Loads minimum unmute volume level from platform configuration. The method tries to load the unmute value from @@ -63,7 +75,7 @@ class SpeakerManagerConfigHelper { * * @return Minimum volume level to unmute speakers. */ - int getMinUnmuteVolume() const; + int getMinUnmuteVolume() const noexcept; /** * Loads mute state handling from configuration. By default the speaker manager sets the mute status to the value @@ -72,36 +84,22 @@ class SpeakerManagerConfigHelper { * @return Returns configured value, where true indicates that mute status is configured according to the last * saved state, and "false" indicates a default shall be kept. */ - bool getRestoreMuteState() const; + bool getRestoreMuteState() const noexcept; private: - /** - * Load channels settings from hardcoded defaults. - * - * @param[out] state Destination container for configuration data. - */ - void loadHardcodedState(SpeakerManagerStorageState& state); - - /** - * Load channels settings from platform configuration. - * - * @param[out] state Destination container for configuration data. - * - * @return A bool indicating success. - */ - bool loadStateFromConfig(SpeakerManagerStorageState& state); - /** * Default values that are used when no other configuration sources are available. */ static const SpeakerManagerStorageState c_defaults; + /// Reference to configuration config interface. + const std::shared_ptr m_config; + /// Reference to configuration storage interface. - std::shared_ptr m_storage; + const std::shared_ptr m_storage; }; } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCONFIGHELPER_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERCONFIGHELPER_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConstants.h b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConstants.h similarity index 76% rename from CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConstants.h rename to CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConstants.h index cba1e3fb97..11461ac3fc 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerConstants.h +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerConstants.h @@ -16,15 +16,17 @@ /** * @file */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCONSTANTS_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCONSTANTS_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERCONSTANTS_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERCONSTANTS_H_ -#include "AVSCommon/AVS/NamespaceAndName.h" +#include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { +/// @addtogroup Lib_acsdkSpeakerManager +/// @{ + /// The Speaker interface namespace. const std::string NAMESPACE = "Speaker"; @@ -55,8 +57,9 @@ const std::string VOLUME_CHANGED = "VolumeChanged"; /// The @c VolumeMute event. const std::string MUTE_CHANGED = "MuteChanged"; +/// @} + } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCONSTANTS_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERCONSTANTS_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerMiscStorage.h b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerMiscStorage.h similarity index 65% rename from CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerMiscStorage.h rename to CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerMiscStorage.h index 33562cba1a..f777d7b48c 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerMiscStorage.h +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/privateInclude/acsdk/SpeakerManager/private/SpeakerManagerMiscStorage.h @@ -13,19 +13,22 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERMISCSTORAGE_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERMISCSTORAGE_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERMISCSTORAGE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERMISCSTORAGE_H_ #include -#include -#include +#include +#include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { /** - * Configuration interface for SpeakerManager. + * @brief Configuration interface implementation for speaker manager. + * + * This class adapts @c MiscStorageInterface to @c SpeakerManagerStorageInterface. + * + * @ingroup Lib_acsdkSpeakerManager */ class SpeakerManagerMiscStorage : public SpeakerManagerStorageInterface { public: @@ -36,18 +39,18 @@ class SpeakerManagerMiscStorage : public SpeakerManagerStorageInterface { * @return A unique pointer to the instance of the newly created @c SpeakerManagerMiscStorage. */ static std::shared_ptr create( - const std::shared_ptr& miscStorage); + std::shared_ptr miscStorage) noexcept; /// @name SpeakerManagerStorageInterface Functions /// @{ - bool loadState(SpeakerManagerStorageState& state) override; - bool saveState(const SpeakerManagerStorageState& state) override; + bool loadState(SpeakerManagerStorageState& state) noexcept override; + bool saveState(const SpeakerManagerStorageState& state) noexcept override; /// @} private: /// Helper to convert structure to JSON string. - static std::string convertToStateString(const SpeakerManagerStorageState& state); - static std::string convertToStateString(const SpeakerManagerStorageState::ChannelState& state); + static std::string convertToStateString(const SpeakerManagerStorageState& state) noexcept; + static std::string convertToStateString(const SpeakerManagerStorageState::ChannelState& state) noexcept; /** * Constructor. @@ -55,7 +58,7 @@ class SpeakerManagerMiscStorage : public SpeakerManagerStorageInterface { * @param miscStorage The underlying miscellaneous storage used to store component data. */ SpeakerManagerMiscStorage( - const std::shared_ptr& miscStorage); + std::shared_ptr miscStorage) noexcept; /** * Method to initialize the object. @@ -64,7 +67,7 @@ class SpeakerManagerMiscStorage : public SpeakerManagerStorageInterface { * * @return Boolean status, indicating operation success. */ - bool init(); + bool init() noexcept; /** * Helper to convert JSON string to structure. @@ -74,7 +77,7 @@ class SpeakerManagerMiscStorage : public SpeakerManagerStorageInterface { * * @return Boolean status, indicating operation success. */ - bool convertFromStateString(const std::string& stateString, SpeakerManagerStorageState& state); + bool convertFromStateString(const std::string& stateString, SpeakerManagerStorageState& state) noexcept; /** * Helper to convert JSON string to structure. @@ -84,14 +87,15 @@ class SpeakerManagerMiscStorage : public SpeakerManagerStorageInterface { * * @return Boolean status, indicating operation success. */ - bool convertFromStateString(const std::string& stateString, SpeakerManagerStorageState::ChannelState& state); + bool convertFromStateString( + const std::string& stateString, + SpeakerManagerStorageState::ChannelState& state) noexcept; /// The Misc storage. std::shared_ptr m_miscStorage; }; } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERMISCSTORAGE_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_PRIVATEINCLUDE_ACSDK_SPEAKERMANAGER_PRIVATE_SPEAKERMANAGERMISCSTORAGE_H_ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/src/CMakeLists.txt b/CapabilityAgents/SpeakerManager/SpeakerManager/src/CMakeLists.txt new file mode 100644 index 0000000000..d90f4e8d34 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/src/CMakeLists.txt @@ -0,0 +1,22 @@ +add_definitions("-DACSDK_LOG_MODULE=speakerManager") + +add_library(acsdkSpeakerManager + SpeakerManager.cpp + ChannelVolumeFactory.cpp + ChannelVolumeManager.cpp + DefaultChannelVolumeFactory.cpp + Factories.cpp + SpeakerManagerMiscStorage.cpp + SpeakerManagerConfig.cpp + SpeakerManagerConfigHelper.cpp) + +target_include_directories(acsdkSpeakerManager + PUBLIC + "${acsdkSpeakerManager_SOURCE_DIR}/include" + PRIVATE + "${acsdkSpeakerManager_SOURCE_DIR}/privateInclude") + +target_link_libraries(acsdkSpeakerManager AVSCommon acsdkShutdownManagerInterfaces Endpoints) + +# install target +asdk_install() diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/src/ChannelVolumeFactory.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/src/ChannelVolumeFactory.cpp new file mode 100644 index 0000000000..6390b03c6a --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/src/ChannelVolumeFactory.cpp @@ -0,0 +1,28 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 speakerManager { + +std::shared_ptr createChannelVolumeFactory() noexcept { + return std::make_shared(); +} + +} // namespace speakerManager +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/src/ChannelVolumeManager.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/src/ChannelVolumeManager.cpp similarity index 89% rename from CapabilityAgents/SpeakerManager/src/ChannelVolumeManager.cpp rename to CapabilityAgents/SpeakerManager/SpeakerManager/src/ChannelVolumeManager.cpp index e70f842d09..eb24ce6acd 100644 --- a/CapabilityAgents/SpeakerManager/src/ChannelVolumeManager.cpp +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/src/ChannelVolumeManager.cpp @@ -13,35 +13,49 @@ * permissions and limitations under the License. */ #include + +#include #include -#include "SpeakerManager/ChannelVolumeManager.h" namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { using namespace avsCommon::sdkInterfaces; using namespace avsCommon::avs::speakerConstants; -static const std::string TAG("ChannelVolumeManager"); +/// @addtogroup Lib_acsdkSpeakerManager +/// @{ + +/// @brief Logging tag +/// @private +#define TAG "ChannelVolumeManager" + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + * @private + */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /// The fraction of maximum volume used for the upper threshold in the default volume curve. -static const float UPPER_VOLUME_CURVE_FRACTION = 0.40; +/// @private +static constexpr float UPPER_VOLUME_CURVE_FRACTION = 0.40f; /// The fraction of maximum volume used for the lower threshold in the default volume curve. -static const float LOWER_VOLUME_CURVE_FRACTION = 0.20; +/// @private +static constexpr float LOWER_VOLUME_CURVE_FRACTION = 0.20f; /** * Checks whether a value is within the bounds. * - * @tparam T The class type of the input parameters. * @param value The value to check. * @param min The minimum value. * @param max The maximum value. + * + * @private */ -template -static bool withinBounds(T value, T min, T max) { +static bool withinBounds(std::int64_t value, std::int64_t min, std::int64_t max) noexcept { if (value < min || value > max) { ACSDK_ERROR(LX("checkBoundsFailed").d("value", value).d("min", min).d("max", max)); return false; @@ -49,10 +63,12 @@ static bool withinBounds(T value, T min, T max) { return true; } +/// @} + std::shared_ptr ChannelVolumeManager::create( std::shared_ptr speaker, ChannelVolumeInterface::Type type, - VolumeCurveFunction volumeCurve) { + VolumeCurveFunction volumeCurve) noexcept { if (!speaker) { ACSDK_ERROR(LX(__func__).d("reason", "Null SpeakerInterface").m("createFailed")); return nullptr; @@ -75,7 +91,7 @@ std::shared_ptr ChannelVolumeManager::create( ChannelVolumeManager::ChannelVolumeManager( std::shared_ptr speaker, ChannelVolumeInterface::Type type, - VolumeCurveFunction volumeCurve) : + VolumeCurveFunction volumeCurve) noexcept : ChannelVolumeInterface{}, m_speaker{speaker}, m_isDucked{false}, @@ -184,7 +200,7 @@ bool ChannelVolumeManager::getSpeakerSettings( return true; } -int8_t ChannelVolumeManager::defaultVolumeAttenuateFunction(int8_t currentVolume) { +int8_t ChannelVolumeManager::defaultVolumeAttenuateFunction(int8_t currentVolume) noexcept { const int8_t lowerBreakPoint = static_cast(AVS_SET_VOLUME_MAX * LOWER_VOLUME_CURVE_FRACTION); const int8_t upperBreakPoint = static_cast(AVS_SET_VOLUME_MAX * UPPER_VOLUME_CURVE_FRACTION); @@ -197,5 +213,4 @@ int8_t ChannelVolumeManager::defaultVolumeAttenuateFunction(int8_t currentVolume } } } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/src/DefaultChannelVolumeFactory.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/src/DefaultChannelVolumeFactory.cpp similarity index 70% rename from CapabilityAgents/SpeakerManager/src/DefaultChannelVolumeFactory.cpp rename to CapabilityAgents/SpeakerManager/SpeakerManager/src/DefaultChannelVolumeFactory.cpp index 5299b5aa90..c8c681a031 100644 --- a/CapabilityAgents/SpeakerManager/src/DefaultChannelVolumeFactory.cpp +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/src/DefaultChannelVolumeFactory.cpp @@ -12,25 +12,19 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ -#include "SpeakerManager/DefaultChannelVolumeFactory.h" + +#include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { -std::shared_ptr DefaultChannelVolumeFactory:: - createChannelVolumeFactoryInterface() { - return std::make_shared(); -} - std::shared_ptr DefaultChannelVolumeFactory:: createChannelVolumeInterface( std::shared_ptr speaker, alexaClientSDK::avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, std::function volumeCurve) { - return alexaClientSDK::capabilityAgents::speakerManager::ChannelVolumeManager::create(speaker, type, volumeCurve); + return ChannelVolumeManager::create(std::move(speaker), std::move(type), std::move(volumeCurve)); } } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/src/Factories.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/src/Factories.cpp new file mode 100644 index 0000000000..9d6164c379 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/src/Factories.cpp @@ -0,0 +1,94 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 speakerManager { + +/// @addtogroup Lib_acsdkSpeakerManager +/// @{ + +/// @brief String to identify log entries originating from this file. +/// @private +#define TAG "SpeakerManagerFactories" + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + * @private + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// @} + +std::shared_ptr createSpeakerManagerCapabilityAgent( + std::shared_ptr config, + std::shared_ptr storage, + std::shared_ptr contextManager, + std::shared_ptr messageSender, + std::shared_ptr exceptionEncounteredSender, + std::shared_ptr metricRecorder, + const std::shared_ptr& shutdownNotifier, + const std::shared_ptr& + endpointCapabilitiesRegistrar, + const std::vector>& volumeInterfaces) noexcept { + if (!shutdownNotifier) { + ACSDK_ERROR(LX(__func__).d("reason", "nullShutdownNotifier")); + return nullptr; + } + if (!endpointCapabilitiesRegistrar) { + ACSDK_ERROR(LX(__func__).d("reason", "nullEndpointCapabilitiesRegistrar")); + return nullptr; + } + + auto speakerManager = SpeakerManager::create( + std::move(config), + std::move(storage), + volumeInterfaces, + std::move(contextManager), + std::move(messageSender), + std::move(exceptionEncounteredSender), + std::move(metricRecorder)); + + if (!speakerManager) { + ACSDK_ERROR(LX(__func__).d("reason", "errorSpeakerManagerCreate")); + return nullptr; + } + + shutdownNotifier->addObserver(speakerManager); + endpointCapabilitiesRegistrar->withCapability(speakerManager, speakerManager); + + return speakerManager; +} + +std::shared_ptr createSpeakerManagerStorage( + std::shared_ptr storage) noexcept { + return SpeakerManagerMiscStorage::create(std::move(storage)); +} + +std::shared_ptr createSpeakerManagerConfig() noexcept { + return std::make_shared(); +} + +} // namespace speakerManager +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManager.cpp similarity index 80% rename from CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp rename to CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManager.cpp index 77cd9a8a75..ab529d73cd 100644 --- a/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManager.cpp @@ -31,12 +31,11 @@ #include #include -#include "SpeakerManager/SpeakerManagerConstants.h" -#include "SpeakerManager/SpeakerManager.h" -#include "SpeakerManager/SpeakerManagerMiscStorage.h" +#include +#include +#include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { using namespace acsdkManufactory; @@ -52,27 +51,29 @@ using namespace rapidjson; using DefaultEndpointAnnotation = avsCommon::sdkInterfaces::endpoints::DefaultEndpointAnnotation; +/// @addtogroup Lib_acsdkSpeakerManager +/// @{ + /// Speaker capability constants /// Speaker interface type -static const std::string SPEAKER_CAPABILITY_INTERFACE_TYPE = "AlexaInterface"; +static const auto SPEAKER_CAPABILITY_INTERFACE_TYPE = "AlexaInterface"; /// Speaker interface name -static const std::string SPEAKER_CAPABILITY_INTERFACE_NAME = "Speaker"; +static const auto SPEAKER_CAPABILITY_INTERFACE_NAME = "Speaker"; /// Speaker interface version -static const std::string SPEAKER_CAPABILITY_INTERFACE_VERSION = "1.0"; +static const auto SPEAKER_CAPABILITY_INTERFACE_VERSION = "1.0"; /// Retry timeout table -static const std::vector DEFAULT_RETRY_TABLE = {std::chrono::milliseconds(10).count(), - std::chrono::milliseconds(20).count(), - std::chrono::milliseconds(40).count()}; - -/// String to identify log entries originating from this file. -static const std::string TAG{"SpeakerManager"}; +static const std::vector DEFAULT_RETRY_TABLE = {static_cast(std::chrono::milliseconds(10).count()), + static_cast(std::chrono::milliseconds(20).count()), + static_cast(std::chrono::milliseconds(40).count())}; /// prefix for metrics emitted from the SpeakerManager CA -static const std::string SPEAKER_MANAGER_METRIC_PREFIX = "SPEAKER_MANAGER-"; +static const auto SPEAKER_MANAGER_METRIC_PREFIX = "SPEAKER_MANAGER-"; +/// String to identify log entries originating from this file. +#define TAG "SpeakerManager" /** * Create a LogEntry using this file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) @@ -82,9 +83,9 @@ static const std::string SPEAKER_MANAGER_METRIC_PREFIX = "SPEAKER_MANAGER-"; * @param value The value to check. * @param min The minimum value. * @param max The maximum value. + * @private */ -template -static bool withinBounds(T value, T min, T max) { +static bool withinBounds(std::int64_t value, std::int64_t min, std::int64_t max) noexcept { if (value < min || value > max) { ACSDK_ERROR(LX("checkBoundsFailed").d("value", value).d("min", min).d("max", max)); return false; @@ -97,8 +98,9 @@ static bool withinBounds(T value, T min, T max) { * * @param source The input @c SpeakerManagerObserverInterface::Source. * @return The @c String representing the source. + * @private */ -static inline std::string getSourceString(SpeakerManagerObserverInterface::Source source) { +static std::string getSourceString(SpeakerManagerObserverInterface::Source source) noexcept { std::stringstream ss; ss << source; return ss.str(); @@ -111,13 +113,17 @@ static inline std::string getSourceString(SpeakerManagerObserverInterface::Sourc * @param metricRecorder The @c MetricRecorderInterface which records Metric events. * @param eventName The name of the Metric event. * @param count Count of the metric that is emitted. + * @private */ static void submitMetric( const std::shared_ptr& metricRecorder, const std::string& eventName, - int count) { + int count) noexcept { + std::string activityName; + activityName.reserve(sizeof(SPEAKER_MANAGER_METRIC_PREFIX) - 1 + eventName.size()); + activityName.append(SPEAKER_MANAGER_METRIC_PREFIX, sizeof(SPEAKER_MANAGER_METRIC_PREFIX) - 1).append(eventName); auto metricEventBuilder = MetricEventBuilder{} - .setActivityName(SPEAKER_MANAGER_METRIC_PREFIX + eventName) + .setActivityName(activityName) .addDataPoint(DataPointCounterBuilder{}.setName(eventName).increment(count).build()); auto metricEvent = metricEventBuilder.build(); @@ -133,49 +139,20 @@ static void submitMetric( * Creates the Speaker capability configuration. * * @return The Speaker capability configuration. + * @private */ -static std::shared_ptr getSpeakerCapabilityConfiguration(); - -std::shared_ptr SpeakerManager::createSpeakerManagerCapabilityAgent( - const std::shared_ptr& storage, - const std::shared_ptr& contextManager, - const std::shared_ptr& messageSender, - const std::shared_ptr& exceptionEncounteredSender, - const std::shared_ptr& shutdownNotifier, - const Annotated& - endpointCapabilitiesRegistrar, - const std::shared_ptr& metricRecorder) { - if (!shutdownNotifier || !endpointCapabilitiesRegistrar) { - ACSDK_ERROR(LX("createSpeakerManagerCapabilityAgentFailed") - .d("reason", "nullParameter") - .d("isContextManagerNull", !contextManager) - .d("isDefaultEndpointCapabilitiesRegistrarNull", !endpointCapabilitiesRegistrar)); - return nullptr; - } - - auto speakerStorage = SpeakerManagerMiscStorage::create(storage); - std::vector> speakers; - auto speakerManager = SpeakerManager::create( - speakerStorage, speakers, contextManager, messageSender, exceptionEncounteredSender, metricRecorder); - - if (!speakerManager) { - ACSDK_ERROR(LX("createSpeakerManagerCapabilityAgentFailed")); - return nullptr; - } - - shutdownNotifier->addObserver(speakerManager); - endpointCapabilitiesRegistrar->withCapability(speakerManager, speakerManager); +static std::shared_ptr getSpeakerCapabilityConfiguration() noexcept; - return speakerManager; -} +/// @} std::shared_ptr SpeakerManager::create( - const std::shared_ptr& storage, + std::shared_ptr config, + std::shared_ptr storage, const std::vector>& groupVolumeInterfaces, std::shared_ptr contextManager, std::shared_ptr messageSender, std::shared_ptr exceptionEncounteredSender, - std::shared_ptr metricRecorder) { + std::shared_ptr metricRecorder) noexcept { if (!contextManager) { ACSDK_ERROR(LX("createFailed").d("reason", "nullContextManager")); return nullptr; @@ -190,25 +167,30 @@ std::shared_ptr SpeakerManager::create( return nullptr; } - auto speakerManager = std::shared_ptr(new SpeakerManager( - storage, groupVolumeInterfaces, contextManager, messageSender, exceptionEncounteredSender, metricRecorder)); - - return speakerManager; + return std::shared_ptr(new SpeakerManager( + std::move(config), + std::move(storage), + groupVolumeInterfaces, + std::move(contextManager), + std::move(messageSender), + std::move(exceptionEncounteredSender), + std::move(metricRecorder))); } SpeakerManager::SpeakerManager( - const std::shared_ptr& speakerManagerStorage, - const std::vector>& groupVolumeInterfaces, - std::shared_ptr contextManager, - std::shared_ptr messageSender, - std::shared_ptr exceptionEncounteredSender, - std::shared_ptr metricRecorder) : + std::shared_ptr speakerManagerConfig, + std::shared_ptr speakerManagerStorage, + const std::vector>& groupVolumeInterfaces, + std::shared_ptr contextManager, + std::shared_ptr messageSender, + std::shared_ptr exceptionEncounteredSender, + std::shared_ptr metricRecorder) noexcept : CapabilityAgent{NAMESPACE, exceptionEncounteredSender}, RequiresShutdown{"SpeakerManager"}, - m_config{speakerManagerStorage}, - m_metricRecorder{metricRecorder}, - m_contextManager{contextManager}, - m_messageSender{messageSender}, + m_config{std::move(speakerManagerConfig), std::move(speakerManagerStorage)}, + m_metricRecorder{std::move(metricRecorder)}, + m_contextManager{std::move(contextManager)}, + m_messageSender{std::move(messageSender)}, m_minUnmuteVolume{MIN_UNMUTE_VOLUME}, m_retryTimer{DEFAULT_RETRY_TABLE}, m_maxRetries{DEFAULT_RETRY_TABLE.size()}, @@ -217,15 +199,17 @@ SpeakerManager::SpeakerManager( for (auto& groupVolume : groupVolumeInterfaces) { addChannelVolumeInterfaceIntoSpeakerMap(groupVolume); } - - loadConfiguration(); // Load configuration (either from previous run, or from configuration). - updateChannelSettings(); // Apply loaded configuration - + m_persistentStorage = m_config.getPersistentStorage(); + if (m_persistentStorage) { + ACSDK_DEBUG5(LX("SpeakerManager").m("Persistent Storage is enabled.")); + loadConfiguration(); // Load configuration (either from storage, or from configuration). + updateChannelSettings(); // Apply loaded configuration + } m_capabilityConfigurations.insert(getSpeakerCapabilityConfiguration()); } void SpeakerManager::addChannelVolumeInterfaceIntoSpeakerMap( - std::shared_ptr channelVolumeInterface) { + std::shared_ptr channelVolumeInterface) noexcept { if (!channelVolumeInterface) { ACSDK_ERROR(LX("addChannelVolumeInterfaceFailed").d("reason", "nullChannelVolumeInterface")); return; @@ -258,11 +242,13 @@ void SpeakerManager::addChannelVolumeInterfaceIntoSpeakerMap( ACSDK_DEBUG(LX(__func__).d("type", type).d("sizeOfSpeakerSet", m_speakerMap[type].size())); } -std::shared_ptr getSpeakerCapabilityConfiguration() { +std::shared_ptr getSpeakerCapabilityConfiguration() noexcept { std::unordered_map configMap; - configMap.insert({CAPABILITY_INTERFACE_TYPE_KEY, SPEAKER_CAPABILITY_INTERFACE_TYPE}); - configMap.insert({CAPABILITY_INTERFACE_NAME_KEY, SPEAKER_CAPABILITY_INTERFACE_NAME}); - configMap.insert({CAPABILITY_INTERFACE_VERSION_KEY, SPEAKER_CAPABILITY_INTERFACE_VERSION}); + // Create a type for gcc < 6.x + using s = std::string; + configMap.insert({s(CAPABILITY_INTERFACE_TYPE_KEY), s(SPEAKER_CAPABILITY_INTERFACE_TYPE)}); + configMap.insert({s(CAPABILITY_INTERFACE_NAME_KEY), s(SPEAKER_CAPABILITY_INTERFACE_NAME)}); + configMap.insert({s(CAPABILITY_INTERFACE_VERSION_KEY), s(SPEAKER_CAPABILITY_INTERFACE_VERSION)}); return std::make_shared(configMap); } @@ -293,7 +279,7 @@ void SpeakerManager::handleDirectiveImmediately(std::shared_ptr di handleDirective(std::make_shared(directive, nullptr)); }; -bool SpeakerManager::parseDirectivePayload(std::string payload, Document* document) { +bool SpeakerManager::parseDirectivePayload(std::string payload, Document* document) noexcept { if (!document) { ACSDK_ERROR(LX("parseDirectivePayloadFailed").d("reason", "nullDocument")); return false; @@ -314,8 +300,8 @@ bool SpeakerManager::parseDirectivePayload(std::string payload, Document* docume void SpeakerManager::sendExceptionEncountered( std::shared_ptr info, const std::string& message, - avsCommon::avs::ExceptionErrorType type) { - m_executor.submit([this, info, message, type] { + avsCommon::avs::ExceptionErrorType type) noexcept { + m_executor.execute([this, info, message, type] { m_exceptionEncounteredSender->sendExceptionEncountered(info->directive->getUnparsedDirective(), type, message); if (info && info->result) { info->result->setFailed(message); @@ -324,14 +310,14 @@ void SpeakerManager::sendExceptionEncountered( }); } -void SpeakerManager::executeSetHandlingCompleted(std::shared_ptr info) { +void SpeakerManager::executeSetHandlingCompleted(std::shared_ptr info) noexcept { if (info && info->result) { info->result->setCompleted(); } removeDirective(info); } -void SpeakerManager::removeDirective(std::shared_ptr info) { +void SpeakerManager::removeDirective(std::shared_ptr info) noexcept { // Check result too, to catch cases where DirectiveInfo was created locally, without a nullptr result. // In those cases there is no messageId to remove because no result was expected. if (info->directive && info->result) { @@ -342,7 +328,7 @@ void SpeakerManager::removeDirective(std::shared_ptr info) { void SpeakerManager::executeSendSpeakerSettingsChangedEvent( const std::string& eventName, - SpeakerInterface::SpeakerSettings settings) { + SpeakerInterface::SpeakerSettings settings) noexcept { ACSDK_DEBUG9(LX("executeSendSpeakerSettingsChangedEvent")); rapidjson::Document payload(rapidjson::kObjectType); payload.AddMember(VOLUME_KEY, settings.volume, payload.GetAllocator()); @@ -385,7 +371,7 @@ void SpeakerManager::handleDirective(std::shared_ptr(AVS_SET_VOLUME_MIN), static_cast(AVS_SET_VOLUME_MAX))) { - m_executor.submit([this, volume, directiveType, info] { + m_executor.execute([this, volume, directiveType, info] { /* * Since AVS doesn't have a concept of Speaker IDs or types, no-op if a directive * comes in and there are no AVS_SPEAKER_VOLUME speakers. @@ -423,7 +409,7 @@ void SpeakerManager::handleDirective(std::shared_ptr(AVS_ADJUST_VOLUME_MIN), static_cast(AVS_ADJUST_VOLUME_MAX))) { - m_executor.submit([this, delta, directiveType, info] { + m_executor.execute([this, delta, directiveType, info] { /* * Since AVS doesn't have a concept of Speaker IDs or types, no-op if a directive * comes in and there are no AVS_SPEAKER_VOLUME speakers. @@ -459,7 +445,7 @@ void SpeakerManager::handleDirective(std::shared_ptr SpeakerManager::setVolume( bool SpeakerManager::executeSetVolume( ChannelVolumeInterface::Type type, int8_t volume, - const SpeakerManagerInterface::NotificationProperties& properties) { + const SpeakerManagerInterface::NotificationProperties& properties) noexcept { ACSDK_DEBUG9(LX("executeSetVolumeCalled").d("volume", static_cast(volume))); auto it = m_speakerMap.find(type); if (m_speakerMap.end() == it) { @@ -616,8 +602,8 @@ bool SpeakerManager::executeSetVolume( if (volume > maximumVolumeLimit) { ACSDK_DEBUG0(LX("adjustingSetVolumeValue") .d("reason", "valueHigherThanLimit") - .d("value", (int)volume) - .d("maximumVolumeLimitSetting", (int)maximumVolumeLimit)); + .d("value", static_cast(volume)) + .d("maximumVolumeLimitSetting", static_cast(maximumVolumeLimit))); adjustedVolume = maximumVolumeLimit; } @@ -658,8 +644,10 @@ bool SpeakerManager::executeSetVolume( ACSDK_DEBUG(LX("executeSetVolumeSuccess").d("newVolume", static_cast(settings.volume))); - if (previousVolume != settings.volume) { - executePersistConfiguration(); + if (m_persistentStorage) { + if (previousVolume != settings.volume) { + executePersistConfiguration(); + } } updateContextManager(type, settings); @@ -678,13 +666,13 @@ bool SpeakerManager::executeSetVolume( void SpeakerManager::convertSettingsToChannelState( avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type, - SpeakerManagerStorageState::ChannelState* storageState) { + SpeakerManagerStorageState::ChannelState* storageState) noexcept { const auto& settings = m_speakerSettings[type]; storageState->channelVolume = settings.volume; storageState->channelMuteStatus = settings.mute; } -void SpeakerManager::executePersistConfiguration() { +void SpeakerManager::executePersistConfiguration() noexcept { SpeakerManagerStorageState state; convertSettingsToChannelState(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, &state.speakerChannelState); convertSettingsToChannelState(ChannelVolumeInterface::Type::AVS_ALERTS_VOLUME, &state.alertsChannelState); @@ -698,7 +686,7 @@ void SpeakerManager::executePersistConfiguration() { bool SpeakerManager::executeRestoreVolume( ChannelVolumeInterface::Type type, - SpeakerManagerObserverInterface::Source source) { + SpeakerManagerObserverInterface::Source source) noexcept { SpeakerInterface::SpeakerSettings settings; if (!executeGetSpeakerSettings(type, &settings)) { @@ -710,7 +698,8 @@ bool SpeakerManager::executeRestoreVolume( return true; } - return executeSetVolume(type, m_minUnmuteVolume, SpeakerManagerInterface::NotificationProperties(source)); + return executeSetVolume( + type, static_cast(m_minUnmuteVolume), SpeakerManagerInterface::NotificationProperties(source)); } std::future SpeakerManager::adjustVolume( @@ -745,7 +734,7 @@ std::future SpeakerManager::adjustVolume( bool SpeakerManager::executeAdjustVolume( ChannelVolumeInterface::Type type, int8_t delta, - const SpeakerManagerInterface::NotificationProperties& properties) { + const SpeakerManagerInterface::NotificationProperties& properties) noexcept { ACSDK_DEBUG9(LX("executeAdjustVolumeCalled").d("delta", static_cast(delta))); auto it = m_speakerMap.find(type); if (m_speakerMap.end() == it) { @@ -826,8 +815,10 @@ bool SpeakerManager::executeAdjustVolume( ACSDK_DEBUG(LX("executeAdjustVolumeSuccess").d("newVolume", static_cast(settings.volume))); - if (previousVolume != settings.volume) { - executePersistConfiguration(); + if (m_persistentStorage) { + if (previousVolume != settings.volume) { + executePersistConfiguration(); + } } updateContextManager(type, settings); @@ -873,7 +864,7 @@ std::future SpeakerManager::setMute( bool SpeakerManager::executeSetMute( ChannelVolumeInterface::Type type, bool mute, - const SpeakerManagerInterface::NotificationProperties& properties) { + const SpeakerManagerInterface::NotificationProperties& properties) noexcept { ACSDK_DEBUG9(LX("executeSetMuteCalled").d("mute", mute)); SpeakerInterface::SpeakerSettings settings; if (!executeGetSpeakerSettings(type, &settings)) { @@ -925,7 +916,9 @@ bool SpeakerManager::executeSetMute( ACSDK_DEBUG(LX("executeSetMuteSuccess").d("mute", mute)); - executePersistConfiguration(); + if (m_persistentStorage) { + executePersistConfiguration(); + } updateContextManager(type, settings); @@ -941,14 +934,14 @@ bool SpeakerManager::executeSetMute( } #ifdef ENABLE_MAXVOLUME_SETTING -std::future SpeakerManager::setMaximumVolumeLimit(const int8_t maximumVolumeLimit) { +std::future SpeakerManager::setMaximumVolumeLimit(const int8_t maximumVolumeLimit) noexcept { return m_executor.submit([this, maximumVolumeLimit] { return withinBounds(maximumVolumeLimit, AVS_ADJUST_VOLUME_MIN, AVS_ADJUST_VOLUME_MAX) && executeSetMaximumVolumeLimit(maximumVolumeLimit); }); } -bool SpeakerManager::executeSetMaximumVolumeLimit(const int8_t maximumVolumeLimit) { +bool SpeakerManager::executeSetMaximumVolumeLimit(const int8_t maximumVolumeLimit) noexcept { ACSDK_DEBUG3(LX(__func__).d("maximumVolumeLimit", static_cast(maximumVolumeLimit))); // First adjust current volumes. @@ -987,7 +980,7 @@ void SpeakerManager::executeNotifySettingsChanged( const SpeakerInterface::SpeakerSettings& settings, const std::string& eventName, const SpeakerManagerObserverInterface::Source& source, - const ChannelVolumeInterface::Type& type) { + const ChannelVolumeInterface::Type& type) noexcept { // Only send an event if the AVS_SPEAKER_VOLUME settings changed. if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { executeSendSpeakerSettingsChangedEvent(eventName, settings); @@ -999,7 +992,7 @@ void SpeakerManager::executeNotifySettingsChanged( void SpeakerManager::executeNotifyObserver( const SpeakerManagerObserverInterface::Source& source, const ChannelVolumeInterface::Type& type, - const SpeakerInterface::SpeakerSettings& settings) { + const SpeakerInterface::SpeakerSettings& settings) noexcept { ACSDK_DEBUG9(LX("executeNotifyObserverCalled")); for (auto observer : m_observers) { observer->onSpeakerSettingsChanged(source, type, settings); @@ -1015,7 +1008,7 @@ std::future SpeakerManager::getSpeakerSettings( bool SpeakerManager::executeGetSpeakerSettings( ChannelVolumeInterface::Type type, - SpeakerInterface::SpeakerSettings* settings) { + SpeakerInterface::SpeakerSettings* settings) noexcept { ACSDK_DEBUG9(LX("executeGetSpeakerSettingsCalled")); if (!settings) { ACSDK_ERROR(LX("executeGetSpeakerSettingsFailed").d("reason", "nullSettings")); @@ -1037,7 +1030,8 @@ bool SpeakerManager::executeGetSpeakerSettings( return true; } -bool SpeakerManager::executeInitializeSpeakerSettings(avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type) { +bool SpeakerManager::executeInitializeSpeakerSettings( + avsCommon::sdkInterfaces::ChannelVolumeInterface::Type type) noexcept { ACSDK_DEBUG5(LX("executeInitializeSpeakerSettings").d("type", type)); auto it = m_speakerMap.find(type); if (m_speakerMap.end() == it) { @@ -1052,13 +1046,93 @@ bool SpeakerManager::executeInitializeSpeakerSettings(avsCommon::sdkInterfaces:: return false; } - m_speakerSettings[type] = settings; + if (m_persistentStorage) { + auto defaultSettingsIt = m_speakerSettings.find(type); + if (m_speakerSettings.end() == defaultSettingsIt) { + ACSDK_DEBUG5(LX("executeInitializeSpeakerSettings").d("Initializing new speaker setting", type)); + m_speakerSettings[type] = settings; + } + } else { + m_speakerSettings[type] = settings; + } + return true; } +void SpeakerManager::onExternalSpeakerSettingsUpdate( + ChannelVolumeInterface::Type type, + const SpeakerInterface::SpeakerSettings& speakerSettings, + const NotificationProperties& properties) { + ACSDK_DEBUG9(LX("onExternalSpeakerSettingsUpdate")); + m_executor.execute([this, type, speakerSettings, properties] { + auto it = m_speakerMap.find(type); + if (m_speakerMap.end() == it) { + ACSDK_ERROR(LX("onExternalSpeakerSettingsUpdateFailed").d("reason", "noSpeakersWithType").d("type", type)); + submitMetric(m_metricRecorder, "onExternalSpeakerSettingsUpdateFailedZeroSpeakers", 1); + return; + } + + submitMetric(m_metricRecorder, "onExternalSpeakerSettingsUpdateFailedZeroSpeakers", 0); + submitMetric(m_metricRecorder, "onExternalSpeakerSettingsUpdate", 1); + submitMetric( + m_metricRecorder, "onExternalSpeakerSettingsUpdateSource_" + getSourceString(properties.source), 1); + + auto adjustedVolume = + (speakerSettings.volume > AVS_SET_VOLUME_MIN) ? speakerSettings.volume : AVS_SET_VOLUME_MIN; + + auto maximumVolumeLimit = getMaximumVolumeLimit(); + + if (speakerSettings.volume > maximumVolumeLimit) { + ACSDK_DEBUG0(LX("adjustingUpdatedVolumeValue") + .d("reason", "valueHigherThanLimit") + .d("value", static_cast(speakerSettings.volume)) + .d("maximumVolumeLimitSetting", static_cast(maximumVolumeLimit))); + adjustedVolume = maximumVolumeLimit; + } + SpeakerInterface::SpeakerSettings settings; + if (!executeGetSpeakerSettings(type, &settings)) { + ACSDK_ERROR(LX("onExternalSpeakerSettingsUpdateFailed").d("reason", "executeGetSpeakerSettingsFailed")); + return; + } + auto previousVolume = settings.volume; + auto previousMute = settings.mute; + + // update the new settings. + settings.volume = adjustedVolume; + settings.mute = speakerSettings.mute; + if (!executeSetSpeakerSettings(type, settings)) { + ACSDK_ERROR(LX("executeOnVolumeUpdatedFailed").d("reason", "executeSetSpeakerSettingsFailed")); + return; + } + + if (m_persistentStorage) { + if ((previousVolume != settings.volume) || (previousMute != settings.mute)) { + executePersistConfiguration(); + } + } + + updateContextManager(type, settings); + + if (properties.notifyObservers) { + executeNotifyObserver(properties.source, type, settings); + } + + if (properties.notifyAVS) { + if (!(previousVolume == settings.volume && + SpeakerManagerObserverInterface::Source::LOCAL_API == properties.source)) { + executeNotifySettingsChanged(settings, VOLUME_CHANGED, properties.source, type); + } + if (previousMute != settings.mute) { + executeNotifySettingsChanged(settings, MUTE_CHANGED, properties.source, type); + } + } + return; + }); +} + bool SpeakerManager::executeSetSpeakerSettings( const ChannelVolumeInterface::Type type, - const SpeakerInterface::SpeakerSettings& settings) { + const SpeakerInterface::SpeakerSettings& settings) noexcept { ACSDK_DEBUG9(LX("executeSetSpeakerSettingsCalled")); if (m_speakerMap.end() == m_speakerMap.find(type)) { ACSDK_ERROR(LX("executeSetSpeakerSettings").d("reason", "noSpeakersWithTypeFound").d("type", type)); @@ -1071,6 +1145,26 @@ bool SpeakerManager::executeSetSpeakerSettings( void SpeakerManager::addChannelVolumeInterface(std::shared_ptr channelVolumeInterface) { addChannelVolumeInterfaceIntoSpeakerMap(channelVolumeInterface); + if (m_persistentStorage) { + SpeakerInterface::SpeakerSettings& settings = m_speakerSettings[channelVolumeInterface->getSpeakerType()]; + retryAndApplySettings([this, &settings, &channelVolumeInterface]() -> bool { + ACSDK_DEBUG9(LX(__func__) + .d("speaker id", channelVolumeInterface->getId()) + .d("speaker type", channelVolumeInterface->getSpeakerType()) + .d("default volume set to ", settings.volume)); + if (!channelVolumeInterface->setUnduckedVolume(settings.volume)) { + submitMetric(m_metricRecorder, "setVolumeFailed", 1); + return false; + } + if (!channelVolumeInterface->setMute(settings.mute)) { + submitMetric(m_metricRecorder, "setMuteFailed", 1); + return false; + } + submitMetric(m_metricRecorder, "setVolumeFailed", 0); + submitMetric(m_metricRecorder, "setMuteFailed", 0); + return true; + }); + } } std::unordered_set> SpeakerManager:: @@ -1078,17 +1172,15 @@ std::unordered_set> Spe return m_capabilityConfigurations; } -int8_t SpeakerManager::getMaximumVolumeLimit() { +int8_t SpeakerManager::getMaximumVolumeLimit() noexcept { return m_maximumVolumeLimit; } -template -bool SpeakerManager::retryAndApplySettings(Task task, Args&&... args) { - auto boundTask = std::bind(std::forward(task), std::forward(args)...); +bool SpeakerManager::retryAndApplySettings(const std::function& task) noexcept { size_t attempt = 0; m_waitCancelEvent.reset(); while (attempt < m_maxRetries) { - if (boundTask()) { + if (task()) { break; } @@ -1102,7 +1194,7 @@ bool SpeakerManager::retryAndApplySettings(Task task, Args&&... args) { return attempt < m_maxRetries; } -int8_t SpeakerManager::adjustVolumeRange(int64_t volume) { +int8_t SpeakerManager::adjustVolumeRange(int64_t volume) noexcept { auto adjustedVolume = std::min( static_cast(AVS_ADJUST_VOLUME_MAX), std::max(static_cast(AVS_ADJUST_VOLUME_MIN), volume)); return static_cast(adjustedVolume); @@ -1110,7 +1202,7 @@ int8_t SpeakerManager::adjustVolumeRange(int64_t volume) { void SpeakerManager::presetChannelDefaults( ChannelVolumeInterface::Type type, - const SpeakerManagerStorageState::ChannelState& state) { + const SpeakerManagerStorageState::ChannelState& state) noexcept { auto adjustedVolume = adjustVolumeRange(state.channelVolume); if (adjustedVolume != state.channelVolume) { @@ -1127,7 +1219,7 @@ void SpeakerManager::presetChannelDefaults( } } -void SpeakerManager::loadConfiguration() { +void SpeakerManager::loadConfiguration() noexcept { ACSDK_DEBUG5(LX("configureDefaults").m("Loading configuration")); m_minUnmuteVolume = m_config.getMinUnmuteVolume(); @@ -1140,12 +1232,12 @@ void SpeakerManager::loadConfiguration() { presetChannelDefaults(ChannelVolumeInterface::Type::AVS_ALERTS_VOLUME, state.alertsChannelState); } -void SpeakerManager::updateChannelSettings() { +void SpeakerManager::updateChannelSettings() noexcept { updateChannelSettings(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME); updateChannelSettings(ChannelVolumeInterface::Type::AVS_ALERTS_VOLUME); } -void SpeakerManager::updateChannelSettings(ChannelVolumeInterface::Type type) { +void SpeakerManager::updateChannelSettings(ChannelVolumeInterface::Type type) noexcept { auto it = m_speakerMap.find(type); if (it != m_speakerMap.end()) { SpeakerInterface::SpeakerSettings& settings = m_speakerSettings[type]; @@ -1181,5 +1273,4 @@ void SpeakerManager::updateChannelSettings(ChannelVolumeInterface::Type type) { } } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerConfig.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerConfig.cpp new file mode 100644 index 0000000000..26632b3f72 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerConfig.cpp @@ -0,0 +1,144 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 speakerManager { + +using namespace alexaClientSDK::avsCommon::avs::speakerConstants; +using namespace alexaClientSDK::avsCommon::utils::configuration; +using namespace alexaClientSDK::avsCommon::utils; + +/// @addtogroup Lib_acsdkSpeakerManager +/// @{ + +/// String to identify log entries originating from this file. +/// @private +#define TAG "SpeakerManagerConfig" + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + * @private + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// The key in our config file to find the root of speaker manager configuration. +/// @private +static const auto SPEAKERMANAGER_CONFIGURATION_ROOT_KEY = "speakerManagerCapabilityAgent"; +/// The key in our config file to find the persistentStorage value. +/// @private +static const auto SPEAKERMANAGER_PERSISTENT_STORAGE_SETTING_KEY = "persistentStorage"; +/// The key in our config file to find the minUnmuteVolume value. +/// @private +static const auto SPEAKERMANAGER_MIN_UNMUTE_VOLUME_KEY = "minUnmuteVolume"; +/// The key in our config file to find the defaultSpeakerVolume value. +/// @private +static const auto SPEAKERMANAGER_DEFAULT_SPEAKER_VOLUME_KEY = "defaultSpeakerVolume"; +/// The key in our config file to find the defaultAlertsVolume value. +/// @private +static const auto SPEAKERMANAGER_DEFAULT_ALERTS_VOLUME_KEY = "defaultAlertsVolume"; +/// The key in our config file to find mute status keep flag +/// @private +static const auto SPEAKERMANAGER_RESTORE_MUTE_STATE_KEY = "restoreMuteState"; + +/** + * @brief Helper to load a single volume level entry from configuration. + * + * @param[in] node Configuration node. + * @param[in] key Entry name. + * @param[out] value Volume entry result. + * + * @return True if entry with name @a key have been found, and it's value is within valid range. False otherwise. + * @private + */ +static bool loadVolumeConfig(ConfigurationNode& node, const char* key, Optional& value) noexcept { + int tmp; + if (node.getInt(key, &tmp) && tmp >= AVS_SET_VOLUME_MIN && tmp <= AVS_SET_VOLUME_MAX) { + value = static_cast(tmp); + return true; + } else { + return false; + } +} + +/// @} + +SpeakerManagerConfig::SpeakerManagerConfig() noexcept { + loadPlatformConfig(); +} + +bool SpeakerManagerConfig::getPersistentStorage(bool& persistentStorage) noexcept { + if (m_persistentStorage.hasValue()) { + persistentStorage = m_persistentStorage.value(); + return true; + } + return false; +} + +bool SpeakerManagerConfig::getMinUnmuteVolume(std::uint8_t& minUnmuteVolume) noexcept { + if (m_minUnmuteVolume.hasValue()) { + minUnmuteVolume = m_minUnmuteVolume.value(); + return true; + } + return false; +} + +bool SpeakerManagerConfig::getRestoreMuteState(bool& restoreMuteState) noexcept { + if (m_restoreMuteState.hasValue()) { + restoreMuteState = m_restoreMuteState.value(); + return true; + } + return false; +} + +bool SpeakerManagerConfig::getDefaultSpeakerVolume(std::uint8_t& defaultSpeakerVolume) noexcept { + if (m_defaultSpeakerVolume.hasValue()) { + defaultSpeakerVolume = m_defaultSpeakerVolume.value(); + return true; + } + return false; +} + +bool SpeakerManagerConfig::getDefaultAlertsVolume(std::uint8_t& defaultAlertsVolume) noexcept { + if (m_defaultAlertsVolume.hasValue()) { + defaultAlertsVolume = m_defaultAlertsVolume.value(); + return true; + } + return false; +} + +void SpeakerManagerConfig::loadPlatformConfig() noexcept { + auto configRoot = ConfigurationNode::getRoot().getChildNode(SPEAKERMANAGER_CONFIGURATION_ROOT_KEY); + + loadVolumeConfig(configRoot, SPEAKERMANAGER_DEFAULT_SPEAKER_VOLUME_KEY, m_defaultSpeakerVolume); + loadVolumeConfig(configRoot, SPEAKERMANAGER_DEFAULT_ALERTS_VOLUME_KEY, m_defaultAlertsVolume); + loadVolumeConfig(configRoot, SPEAKERMANAGER_MIN_UNMUTE_VOLUME_KEY, m_minUnmuteVolume); + + bool tmp; + if (configRoot.getBool(SPEAKERMANAGER_RESTORE_MUTE_STATE_KEY, &tmp)) { + m_restoreMuteState = tmp; + } + if (configRoot.getBool(SPEAKERMANAGER_PERSISTENT_STORAGE_SETTING_KEY, &tmp)) { + m_persistentStorage = tmp; + } +} + +} // namespace speakerManager +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerConfigHelper.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerConfigHelper.cpp new file mode 100644 index 0000000000..f406114f56 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerConfigHelper.cpp @@ -0,0 +1,94 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 speakerManager { + +using namespace avsCommon::sdkInterfaces; +using namespace avsCommon::sdkInterfaces::storage; +using namespace avsCommon::avs::speakerConstants; +using namespace avsCommon::utils::configuration; + +/// String to identify log entries originating from this file. +/// @private +#define TAG "SpeakerManagerConfigHelper" + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + * @private + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +const SpeakerManagerStorageState SpeakerManagerConfigHelper::c_defaults = {{DEFAULT_SPEAKER_VOLUME, false}, + {DEFAULT_ALERTS_VOLUME, false}}; + +SpeakerManagerConfigHelper::SpeakerManagerConfigHelper( + std::shared_ptr config, + std::shared_ptr storage) noexcept : + m_config{std::move(config)}, + m_storage{std::move(storage)} { +} + +int SpeakerManagerConfigHelper::getMinUnmuteVolume() const noexcept { + std::uint8_t minUnmuteVolume; + if (!m_config->getMinUnmuteVolume(minUnmuteVolume)) { + minUnmuteVolume = MIN_UNMUTE_VOLUME; + } + return minUnmuteVolume; +} + +bool SpeakerManagerConfigHelper::getPersistentStorage() const noexcept { + bool speakerPersistentStorageSetting; + if (!m_config->getPersistentStorage(speakerPersistentStorageSetting)) { + speakerPersistentStorageSetting = false; + } + return speakerPersistentStorageSetting; +} + +void SpeakerManagerConfigHelper::loadState(SpeakerManagerStorageState& state) noexcept { + if (m_storage->loadState(state)) { + return; + } + + state = c_defaults; + + std::uint8_t tmp; + if (m_config->getDefaultSpeakerVolume(tmp)) { + state.speakerChannelState.channelVolume = tmp; + } + if (m_config->getDefaultAlertsVolume(tmp)) { + state.alertsChannelState.channelVolume = tmp; + } +} + +bool SpeakerManagerConfigHelper::saveState(const SpeakerManagerStorageState& state) noexcept { + return m_storage->saveState(state); +} + +bool SpeakerManagerConfigHelper::getRestoreMuteState() const noexcept { + bool restoreMuteState; + if (!m_config->getRestoreMuteState(restoreMuteState)) { + restoreMuteState = true; + } + return restoreMuteState; +} + +} // namespace speakerManager +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManagerMiscStorage.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerMiscStorage.cpp similarity index 77% rename from CapabilityAgents/SpeakerManager/src/SpeakerManagerMiscStorage.cpp rename to CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerMiscStorage.cpp index 1209c2d99c..2d3690343b 100644 --- a/CapabilityAgents/SpeakerManager/src/SpeakerManagerMiscStorage.cpp +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/src/SpeakerManagerMiscStorage.cpp @@ -13,54 +13,62 @@ * permissions and limitations under the License. */ -#include +#include +#include +#include #include #include #include -#include "SpeakerManager/SpeakerManager.h" -#include "SpeakerManager/SpeakerManagerMiscStorage.h" -#include "SpeakerManager/SpeakerManagerStorageState.h" - namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { using namespace avsCommon::sdkInterfaces::storage; using namespace avsCommon::utils::json; +/// @addtogroup Lib_acsdkSpeakerManager +/// @{ + /// String to identify log entries originating from this file. -static const std::string TAG("SpeakerManagerMiscStorage"); +/// @private +#define TAG "SpeakerManagerMiscStorage" /** * Create a LogEntry using the file's TAG and the specified event string. * - * @param The event string for this @c LogEntry. + * @param event The event string for this @c LogEntry. + * @private */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /// Component name for Misc DB. -static const std::string COMPONENT_NAME = "SpeakerManager"; +/// @private +static const auto COMPONENT_NAME = "SpeakerManager"; /// Misc DB table for component state. -static const std::string COMPONENT_STATE_TABLE = "SpeakerManagerConfig"; +/// @private +static const auto COMPONENT_STATE_TABLE = "SpeakerManagerConfig"; /// Misc DB table entry for component state. -static const std::string COMPONENT_STATE_KEY = "SpeakerManagerConfig"; - +/// @private +static const auto COMPONENT_STATE_KEY = "SpeakerManagerConfig"; /// The key in our config for speaker volume. -static const std::string SPEAKER_CHANNEL_STATE = "speakerChannelState"; +/// @private +static const auto SPEAKER_CHANNEL_STATE = "speakerChannelState"; /// The key in our config for speaker volume. -static const std::string ALERTS_CHANNEL_STATE = "alertsChannelState"; -/// The key in our config for alerts volume. -static const std::string ALERTS_VOLUME_KEY = "alertsVolume"; +/// @private +static const auto ALERTS_CHANNEL_STATE = "alertsChannelState"; /// The key in our config for speaker volume. -static const std::string CHANNEL_VOLUME_KEY = "channelVolume"; +/// @private +static const auto CHANNEL_VOLUME_KEY = "channelVolume"; /// The key in our config for speaker volume. -static const std::string CHANNEL_MUTE_STATUS_KEY = "channelMuteStatus"; +/// @private +static const auto CHANNEL_MUTE_STATUS_KEY = "channelMuteStatus"; + +/// @} std::shared_ptr SpeakerManagerMiscStorage::create( - const std::shared_ptr& miscStorage) { + std::shared_ptr miscStorage) noexcept { if (miscStorage) { - auto res = std::shared_ptr(new SpeakerManagerMiscStorage(miscStorage)); + auto res = std::shared_ptr(new SpeakerManagerMiscStorage(std::move(miscStorage))); if (res->init()) { return res; } else { @@ -73,11 +81,11 @@ std::shared_ptr SpeakerManagerMiscStorage::create( } SpeakerManagerMiscStorage::SpeakerManagerMiscStorage( - const std::shared_ptr& miscStorage) : - m_miscStorage{miscStorage} { + std::shared_ptr miscStorage) noexcept : + m_miscStorage{std::move(miscStorage)} { } -bool SpeakerManagerMiscStorage::init() { +bool SpeakerManagerMiscStorage::init() noexcept { if (!m_miscStorage->isOpened() && !m_miscStorage->open()) { ACSDK_DEBUG3(LX(__func__).m("Couldn't open misc database. Creating.")); if (!m_miscStorage->createDatabase()) { @@ -112,7 +120,7 @@ bool SpeakerManagerMiscStorage::init() { bool SpeakerManagerMiscStorage::convertFromStateString( const std::string& stateString, - SpeakerManagerStorageState::ChannelState& state) { + SpeakerManagerStorageState::ChannelState& state) noexcept { rapidjson::Document document; if (!jsonUtils::parseJSON(stateString, &document)) { ACSDK_ERROR(LX("convertFromStateString").d("reason", "parsingError")); @@ -122,7 +130,7 @@ bool SpeakerManagerMiscStorage::convertFromStateString( int64_t tmpVolume; if (jsonUtils::retrieveValue(document, CHANNEL_VOLUME_KEY, &tmpVolume)) { - state.channelVolume = tmpVolume; + state.channelVolume = static_cast(tmpVolume); } else { return false; } @@ -135,7 +143,7 @@ bool SpeakerManagerMiscStorage::convertFromStateString( bool SpeakerManagerMiscStorage::convertFromStateString( const std::string& stateString, - SpeakerManagerStorageState& state) { + SpeakerManagerStorageState& state) noexcept { std::string tmp; return jsonUtils::retrieveValue(stateString, SPEAKER_CHANNEL_STATE, &tmp) && @@ -144,21 +152,22 @@ bool SpeakerManagerMiscStorage::convertFromStateString( convertFromStateString(tmp, state.alertsChannelState); } -bool SpeakerManagerMiscStorage::loadState(SpeakerManagerStorageState& state) { +bool SpeakerManagerMiscStorage::loadState(SpeakerManagerStorageState& state) noexcept { std::string stateString; return m_miscStorage->get(COMPONENT_NAME, COMPONENT_STATE_TABLE, COMPONENT_STATE_KEY, &stateString) && !stateString.empty() && convertFromStateString(stateString, state); } -std::string SpeakerManagerMiscStorage::convertToStateString(const SpeakerManagerStorageState::ChannelState& state) { +std::string SpeakerManagerMiscStorage::convertToStateString( + const SpeakerManagerStorageState::ChannelState& state) noexcept { JsonGenerator generator; generator.addMember(CHANNEL_VOLUME_KEY, state.channelVolume); generator.addMember(CHANNEL_MUTE_STATUS_KEY, state.channelMuteStatus); return generator.toString(); } -std::string SpeakerManagerMiscStorage::convertToStateString(const SpeakerManagerStorageState& state) { +std::string SpeakerManagerMiscStorage::convertToStateString(const SpeakerManagerStorageState& state) noexcept { ACSDK_DEBUG5(LX(__func__)); JsonGenerator generator; generator.addRawJsonMember(SPEAKER_CHANNEL_STATE, convertToStateString(state.speakerChannelState)); @@ -166,7 +175,7 @@ std::string SpeakerManagerMiscStorage::convertToStateString(const SpeakerManager return generator.toString(); } -bool SpeakerManagerMiscStorage::saveState(const SpeakerManagerStorageState& state) { +bool SpeakerManagerMiscStorage::saveState(const SpeakerManagerStorageState& state) noexcept { std::string stateString = convertToStateString(state); if (!m_miscStorage->put(COMPONENT_NAME, COMPONENT_STATE_TABLE, COMPONENT_STATE_KEY, stateString)) { ACSDK_ERROR(LX("saveStateFailed") @@ -180,5 +189,4 @@ bool SpeakerManagerMiscStorage::saveState(const SpeakerManagerStorageState& stat } } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/test/CMakeLists.txt b/CapabilityAgents/SpeakerManager/SpeakerManager/test/CMakeLists.txt new file mode 100644 index 0000000000..2d2df6e18e --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) + +if(BUILD_TESTING) + add_library(acsdkSpeakerManagerTestLib INTERFACE) + target_include_directories(acsdkSpeakerManagerTestLib INTERFACE "${acsdkSpeakerManager_SOURCE_DIR}/test/include") + + set(INCLUDE_PATH "${acsdkSpeakerManager_SOURCE_DIR}/privateInclude") + discover_unit_tests("${INCLUDE_PATH}" "acsdkSpeakerManager;UtilsCommonTestLib;SDKInterfacesTests;AVSCommonTestLib;acsdkSpeakerManagerTestLib") +endif() diff --git a/CapabilityAgents/SpeakerManager/test/ChannelVolumeManagerTest.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/test/ChannelVolumeManagerTest.cpp similarity index 95% rename from CapabilityAgents/SpeakerManager/test/ChannelVolumeManagerTest.cpp rename to CapabilityAgents/SpeakerManager/SpeakerManager/test/ChannelVolumeManagerTest.cpp index 9f3219623d..23aa44cb4d 100644 --- a/CapabilityAgents/SpeakerManager/test/ChannelVolumeManagerTest.cpp +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/ChannelVolumeManagerTest.cpp @@ -17,22 +17,23 @@ #include #include -#include "SpeakerManager/ChannelVolumeManager.h" +#include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { namespace test { + using namespace avsCommon::avs; using namespace avsCommon::avs::speakerConstants; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::sdkInterfaces::test; using namespace ::testing; -using namespace alexaClientSDK::capabilityAgents::speakerManager; /// Initial Settings for underlying @c SpeakerInterface. static const SpeakerInterface::SpeakerSettings INITIAL_SETTINGS{AVS_SET_VOLUME_MAX / 2, false}; +/// @brief Test fixture for ChannelVolumeManager. +/// @ingroup Test_acsdkSpeakerManager class ChannelVolumeManagerTest : public Test { public: /// SetUp before each test. @@ -69,7 +70,7 @@ void ChannelVolumeManagerTest::TearDown() { } static int8_t defaultVolumeCurve(int8_t currentVolume) { - const float lowerBreakPointFraction = 0.20, upperBreakPointFraction = 0.40; + const float lowerBreakPointFraction = 0.20f, upperBreakPointFraction = 0.40f; const int8_t lowerBreakPoint = static_cast(AVS_SET_VOLUME_MAX * lowerBreakPointFraction); const int8_t upperBreakPoint = static_cast(AVS_SET_VOLUME_MAX * upperBreakPointFraction); @@ -135,7 +136,7 @@ TEST_F(ChannelVolumeManagerTest, test_volumeIsRestoredToLatestUnduckedVolume) { ASSERT_TRUE(unit->startDucking()); // set unducked volume - auto newUnduckedVolume = INITIAL_SETTINGS.volume * 2; + auto newUnduckedVolume = static_cast(INITIAL_SETTINGS.volume * 2); ASSERT_EQ(true, unit->setUnduckedVolume(newUnduckedVolume)); // when stopDucking is called : volume should be restored to unducked volume @@ -173,5 +174,4 @@ TEST_F(ChannelVolumeManagerTest, test_stopDuckingWhenAlreadyUnducked) { } } // namespace test } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/test/Namespaces.dox b/CapabilityAgents/SpeakerManager/SpeakerManager/test/Namespaces.dox new file mode 100644 index 0000000000..e8f96a463e --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/Namespaces.dox @@ -0,0 +1,24 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * @namespace alexaClientSDK::speakerManager::test + * @brief Unit Tests and Mocks for Speaker API Capability Agent + * + * This namespace contains unit test for Speaker API capability agent implementation. + * + * @see Lib_acsdkSpeakerManager + * @ingroup Test_acsdkSpeakerManager + */ diff --git a/CapabilityAgents/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp similarity index 56% rename from CapabilityAgents/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp rename to CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp index b35b9b7f28..97ffd40592 100644 --- a/CapabilityAgents/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerConfigHelperTest.cpp @@ -13,53 +13,28 @@ * permissions and limitations under the License. */ -#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "SpeakerManager/SpeakerManagerConfigHelper.h" -#include "SpeakerManager/SpeakerManagerStorageState.h" namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { namespace test { using namespace avsCommon::avs; using namespace avsCommon::avs::speakerConstants; using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::utils::configuration; -using namespace avsCommon::utils::memory; -using namespace rapidjson; using namespace ::testing; -using namespace alexaClientSDK::capabilityAgents::speakerManager; -static const std::string JSON_TEST_CONFIG = - "{\"speakerManagerCapabilityAgent\":{\"minUnmuteVolume\":3,\"defaultSpeakerVolume\":5,\"defaultAlertsVolume\":6," - "\"restoreMuteState\":true}}"; -static const std::string JSON_TEST_CONFIG_NO_MUTE = - "{\"speakerManagerCapabilityAgent\":{\"minUnmuteVolume\":3,\"defaultSpeakerVolume\":5,\"defaultAlertsVolume\":6," - "\"restoreMuteState\":false}}"; - -class MockSpeakerManagerStorageInterface : public SpeakerManagerStorageInterface { -public: - MOCK_METHOD1(loadState, bool(SpeakerManagerStorageState&)); - MOCK_METHOD1(saveState, bool(const SpeakerManagerStorageState&)); -}; +/// @addtogroup Test_acsdkSpeakerManager +/// @{ +/// @brief Test fixture for SpeakerManagerConfigHelper. class SpeakerManagerConfigHelperTest : public Test { public: SpeakerManagerConfigHelperTest(); @@ -71,26 +46,46 @@ class SpeakerManagerConfigHelperTest : public Test { /// TearDown after each test. void TearDown() override; - // Upstream interface mock - std::shared_ptr> m_stubStorage; + /// Upstream interface mock + std::shared_ptr> m_stubStorage; + /// Upstream interface mock + std::shared_ptr m_stubConfig; }; +/// @} + SpeakerManagerConfigHelperTest::SpeakerManagerConfigHelperTest() : m_stubStorage() { } void SpeakerManagerConfigHelperTest::SetUp() { - m_stubStorage = std::make_shared>(); + m_stubStorage = std::make_shared>(); + m_stubConfig = std::make_shared>(); } void SpeakerManagerConfigHelperTest::TearDown() { m_stubStorage.reset(); + m_stubConfig.reset(); } TEST_F(SpeakerManagerConfigHelperTest, test_initDoesntCallLoadSave) { EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); - SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_getPersistentStorageFromConfiguration) { + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); + EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); + + // Provide a valid configuration. + EXPECT_CALL(*m_stubConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); + ASSERT_TRUE(helper.getPersistentStorage()); } TEST_F(SpeakerManagerConfigHelperTest, test_getMinUnmuteVolumeFromConfiguration) { @@ -98,50 +93,82 @@ TEST_F(SpeakerManagerConfigHelperTest, test_getMinUnmuteVolumeFromConfiguration) EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); // Provide a valid configuration. - std::shared_ptr istr(new std::istringstream(JSON_TEST_CONFIG)); - ConfigurationNode::uninitialize(); - ASSERT_TRUE(ConfigurationNode::initialize({istr})); + EXPECT_CALL(*m_stubConfig, getMinUnmuteVolume(_)).Times(1).WillOnce(Invoke([](std::uint8_t& res) { + res = 3; + return true; + })); - SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); ASSERT_EQ(3, helper.getMinUnmuteVolume()); } +TEST_F(SpeakerManagerConfigHelperTest, test_getPersistentStorageReturnsDefaults) { + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); + EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); + + // Provide an empty configuration. + EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); + + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); + ASSERT_FALSE(helper.getPersistentStorage()); +} + TEST_F(SpeakerManagerConfigHelperTest, test_getMinUnmuteVolumeReturnsDefaults) { EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); // Provide an empty configuration. - ConfigurationNode::uninitialize(); - ASSERT_TRUE(ConfigurationNode::initialize({})); + EXPECT_CALL(*m_stubConfig, getMinUnmuteVolume(_)).Times(1).WillOnce(Return(false)); - SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); ASSERT_EQ(MIN_UNMUTE_VOLUME, helper.getMinUnmuteVolume()); } TEST_F(SpeakerManagerConfigHelperTest, test_getRestoreMuteStateReturnsDefaults) { // Provide an empty configuration. - ConfigurationNode::uninitialize(); - ASSERT_TRUE(ConfigurationNode::initialize({})); + EXPECT_CALL(*m_stubConfig, getRestoreMuteState(_)).Times(1).WillOnce(Return(false)); - SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); ASSERT_TRUE(helper.getRestoreMuteState()); } +TEST_F(SpeakerManagerConfigHelperTest, test_getPersistentStorageReturnsTrue) { + EXPECT_CALL(*m_stubConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); + ASSERT_TRUE(helper.getPersistentStorage()); +} + +TEST_F(SpeakerManagerConfigHelperTest, test_getPersistentStorageReturnsFalse) { + EXPECT_CALL(*m_stubConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = false; + return true; + })); + + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); + ASSERT_FALSE(helper.getPersistentStorage()); +} + TEST_F(SpeakerManagerConfigHelperTest, test_getRestoreMuteStateReturnsTrue) { - ConfigurationNode::uninitialize(); - std::shared_ptr istr(new std::istringstream(JSON_TEST_CONFIG)); - ASSERT_TRUE(ConfigurationNode::initialize({istr})); + EXPECT_CALL(*m_stubConfig, getRestoreMuteState(_)).Times(1).WillOnce(Invoke([](bool& restoreMuteState) { + restoreMuteState = true; + return true; + })); - SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); ASSERT_TRUE(helper.getRestoreMuteState()); } TEST_F(SpeakerManagerConfigHelperTest, test_getRestoreMuteStateReturnsFalse) { - ConfigurationNode::uninitialize(); - std::shared_ptr istr(new std::istringstream(JSON_TEST_CONFIG_NO_MUTE)); - ASSERT_TRUE(ConfigurationNode::initialize({istr})); + EXPECT_CALL(*m_stubConfig, getRestoreMuteState(_)).Times(1).WillOnce(Invoke([](bool& restoreMuteState) { + restoreMuteState = false; + return true; + })); - SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); ASSERT_FALSE(helper.getRestoreMuteState()); } @@ -149,7 +176,7 @@ TEST_F(SpeakerManagerConfigHelperTest, test_loadStateDelegate) { EXPECT_CALL(*m_stubStorage, loadState(_)).Times(1); EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); - SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); ON_CALL(*m_stubStorage, loadState(_)).WillByDefault(Invoke([](SpeakerManagerStorageState& state) { state.speakerChannelState.channelVolume = 10; @@ -172,11 +199,20 @@ TEST_F(SpeakerManagerConfigHelperTest, test_loadStateFromConfig) { EXPECT_CALL(*m_stubStorage, loadState(_)).Times(1); EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); - std::shared_ptr istr(new std::istringstream(JSON_TEST_CONFIG)); - ConfigurationNode::uninitialize(); - ASSERT_TRUE(ConfigurationNode::initialize({istr})); - - SpeakerManagerConfigHelper helper(m_stubStorage); + EXPECT_CALL(*m_stubConfig, getDefaultSpeakerVolume(_)) + .Times(1) + .WillOnce(Invoke([](std::uint8_t& defaultSpeakerVolume) { + defaultSpeakerVolume = 5; + return true; + })); + EXPECT_CALL(*m_stubConfig, getDefaultAlertsVolume(_)) + .Times(1) + .WillOnce(Invoke([](std::uint8_t& defaultSpeakerVolume) { + defaultSpeakerVolume = 6; + return true; + })); + + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); ON_CALL(*m_stubStorage, loadState(_)).WillByDefault(Return(false)); @@ -193,10 +229,10 @@ TEST_F(SpeakerManagerConfigHelperTest, test_loadStateDefaults) { EXPECT_CALL(*m_stubStorage, loadState(_)).Times(1); EXPECT_CALL(*m_stubStorage, saveState(_)).Times(0); - ConfigurationNode::uninitialize(); - ASSERT_TRUE(ConfigurationNode::initialize({})); + EXPECT_CALL(*m_stubConfig, getDefaultSpeakerVolume(_)).Times(1).WillOnce(Return(false)); + EXPECT_CALL(*m_stubConfig, getDefaultAlertsVolume(_)).Times(1).WillOnce(Return(false)); - SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); ON_CALL(*m_stubStorage, loadState(_)).WillByDefault(Return(false)); @@ -213,7 +249,7 @@ TEST_F(SpeakerManagerConfigHelperTest, test_saveState) { EXPECT_CALL(*m_stubStorage, loadState(_)).Times(0); EXPECT_CALL(*m_stubStorage, saveState(_)).Times(1); - SpeakerManagerConfigHelper helper(m_stubStorage); + SpeakerManagerConfigHelper helper(m_stubConfig, m_stubStorage); SpeakerManagerStorageState saved = {{0, true}, {0, true}}; ON_CALL(*m_stubStorage, saveState(_)).WillByDefault(Invoke([&saved](const SpeakerManagerStorageState& state) { @@ -232,5 +268,4 @@ TEST_F(SpeakerManagerConfigHelperTest, test_saveState) { } // namespace test } // namespace speakerManager -} // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerConfigTest.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerConfigTest.cpp new file mode 100644 index 0000000000..9cb9e8e86d --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerConfigTest.cpp @@ -0,0 +1,276 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 speakerManager { +namespace test { + +using namespace avsCommon::utils::configuration; +using namespace ::testing; + +/// @addtogroup Test_acsdkSpeakerManager +/// @{ +/// Configuration without speaker manager root. +static const auto JSON_TEST_CONFIG_MISSING = R"( +{ +})"; +/// Configuration with speaker manager root but without entries. +static const auto JSON_TEST_CONFIG_EMPTY = R"( +{ + "speakerManagerCapabilityAgent": {} +})"; +/// Configuration with speaker manager root with persistentStorage. +static const auto JSON_TEST_CONFIG_PERSISTENT_STORAGE = R"( +{ + "speakerManagerCapabilityAgent": { + "persistentStorage": true + } +})"; +/// Configuration with speaker manager root with minUnmuteVolume. +static const auto JSON_TEST_CONFIG_MIN_UNMUTE_VOLUME = R"( +{ + "speakerManagerCapabilityAgent": { + "minUnmuteVolume": 3 + } +})"; +/// Configuration with speaker manager root with defaultSpeakerVolume. +static const auto JSON_TEST_CONFIG_DEFAULT_SPEAKER_VOLUME = R"( +{ + "speakerManagerCapabilityAgent": { + "defaultSpeakerVolume": 5 + } +})"; +/// Configuration with speaker manager root with defaultAlertsVolume. +static const auto JSON_TEST_CONFIG_DEFAULT_ALERTS_VOLUME = R"( +{ + "speakerManagerCapabilityAgent": { + "defaultAlertsVolume": 6 + } +})"; +/// Configuration with speaker manager root with defaultAlertsVolume. +static const auto JSON_TEST_CONFIG_RESTORE_MUTE_STATE = R"( +{ + "speakerManagerCapabilityAgent": { + "restoreMuteState": false + } +})"; +/// Full configuration. +static const auto JSON_TEST_CONFIG = R"( +{ + "speakerManagerCapabilityAgent": { + "persistentStorage": true, + "minUnmuteVolume": 3, + "defaultSpeakerVolume": 5, + "defaultAlertsVolume": 6, + "restoreMuteState": true + } +} +)"; + +/// @brief Test fixture for SpeakerManagerConfig. +class SpeakerManagerConfigTest : public Test { +protected: + /** + * Configure ConfigurationNode with a given string. + * + * @param jsonConfig Configuration string. + * @return True on success. + */ + bool configureJsonConfig(const char* jsonConfig); + + /// SetUp before each test. + void SetUp() override; + + /// TearDown after each test. + void TearDown() override; +}; + +/// @} + +void SpeakerManagerConfigTest::SetUp() { + ConfigurationNode::uninitialize(); +} + +void SpeakerManagerConfigTest::TearDown() { + ConfigurationNode::uninitialize(); +} + +bool SpeakerManagerConfigTest::configureJsonConfig(const char* jsonConfig) { + ConfigurationNode::uninitialize(); + std::shared_ptr istr(new std::istringstream(jsonConfig)); + return ConfigurationNode::initialize({istr}); +} + +/// Validate nothing breaks when config is missing. +TEST_F(SpeakerManagerConfigTest, test_ValidateMissingConfig) { + ASSERT_TRUE(configureJsonConfig(JSON_TEST_CONFIG_MISSING)); + + SpeakerManagerConfig config; + bool persistentStorage; + bool restoreMuteState; + uint8_t minUnmuteVolume; + uint8_t defaultSpeakerVolume; + uint8_t defaultAlertsVolume; + + ASSERT_FALSE(config.getPersistentStorage(persistentStorage)); + ASSERT_FALSE(config.getRestoreMuteState(restoreMuteState)); + ASSERT_FALSE(config.getMinUnmuteVolume(minUnmuteVolume)); + ASSERT_FALSE(config.getDefaultSpeakerVolume(defaultSpeakerVolume)); + ASSERT_FALSE(config.getDefaultAlertsVolume(defaultAlertsVolume)); +} + +/// Validate nothing breaks when config is empty. +TEST_F(SpeakerManagerConfigTest, test_ValidateEmptyConfig) { + ASSERT_TRUE(configureJsonConfig(JSON_TEST_CONFIG_EMPTY)); + + SpeakerManagerConfig config; + bool persistentStorage; + bool restoreMuteState; + uint8_t minUnmuteVolume; + uint8_t defaultSpeakerVolume; + uint8_t defaultAlertsVolume; + + ASSERT_FALSE(config.getPersistentStorage(persistentStorage)); + ASSERT_FALSE(config.getRestoreMuteState(restoreMuteState)); + ASSERT_FALSE(config.getMinUnmuteVolume(minUnmuteVolume)); + ASSERT_FALSE(config.getDefaultSpeakerVolume(defaultSpeakerVolume)); + ASSERT_FALSE(config.getDefaultAlertsVolume(defaultAlertsVolume)); +} + +/// Validate persistentVolume entry is read correctly. +TEST_F(SpeakerManagerConfigTest, test_ValidatePersistentStorageConfig) { + ASSERT_TRUE(configureJsonConfig(JSON_TEST_CONFIG_PERSISTENT_STORAGE)); + + SpeakerManagerConfig config; + bool persistentStorage = false; + bool restoreMuteState; + uint8_t minUnmuteVolume; + uint8_t defaultSpeakerVolume; + uint8_t defaultAlertsVolume; + + ASSERT_TRUE(config.getPersistentStorage(persistentStorage)); + ASSERT_TRUE(persistentStorage); + ASSERT_FALSE(config.getRestoreMuteState(restoreMuteState)); + ASSERT_FALSE(config.getMinUnmuteVolume(minUnmuteVolume)); + ASSERT_FALSE(config.getDefaultSpeakerVolume(defaultSpeakerVolume)); + ASSERT_FALSE(config.getDefaultAlertsVolume(defaultAlertsVolume)); +} + +/// Validate restoreMuteState entry is read correctly. +TEST_F(SpeakerManagerConfigTest, test_ValidateRestoreMuteStateConfig) { + ASSERT_TRUE(configureJsonConfig(JSON_TEST_CONFIG_RESTORE_MUTE_STATE)); + + SpeakerManagerConfig config; + bool persistentStorage; + bool restoreMuteState = true; + uint8_t minUnmuteVolume; + uint8_t defaultSpeakerVolume; + uint8_t defaultAlertsVolume; + + ASSERT_FALSE(config.getPersistentStorage(persistentStorage)); + ASSERT_TRUE(config.getRestoreMuteState(restoreMuteState)); + ASSERT_FALSE(restoreMuteState); + ASSERT_FALSE(config.getMinUnmuteVolume(minUnmuteVolume)); + ASSERT_FALSE(config.getDefaultSpeakerVolume(defaultSpeakerVolume)); + ASSERT_FALSE(config.getDefaultAlertsVolume(defaultAlertsVolume)); +} + +/// Validate minUnmuteVolume entry is read correctly. +TEST_F(SpeakerManagerConfigTest, test_ValidateMinUnmuteVolumeConfig) { + ASSERT_TRUE(configureJsonConfig(JSON_TEST_CONFIG_MIN_UNMUTE_VOLUME)); + + SpeakerManagerConfig config; + bool persistentStorage; + bool restoreMuteState; + uint8_t minUnmuteVolume = 0; + uint8_t defaultSpeakerVolume; + uint8_t defaultAlertsVolume; + + ASSERT_FALSE(config.getPersistentStorage(persistentStorage)); + ASSERT_FALSE(config.getRestoreMuteState(restoreMuteState)); + ASSERT_TRUE(config.getMinUnmuteVolume(minUnmuteVolume)); + ASSERT_EQ(3u, minUnmuteVolume); + ASSERT_FALSE(config.getDefaultSpeakerVolume(defaultSpeakerVolume)); + ASSERT_FALSE(config.getDefaultAlertsVolume(defaultAlertsVolume)); +} + +/// Validate defaultSpeakerVolume entry is read correctly. +TEST_F(SpeakerManagerConfigTest, test_ValidateDefaultSpeakerVolumeConfig) { + ASSERT_TRUE(configureJsonConfig(JSON_TEST_CONFIG_DEFAULT_SPEAKER_VOLUME)); + + SpeakerManagerConfig config; + bool persistentStorage; + bool restoreMuteState; + uint8_t minUnmuteVolume; + uint8_t defaultSpeakerVolume = 0; + uint8_t defaultAlertsVolume; + + ASSERT_FALSE(config.getPersistentStorage(persistentStorage)); + ASSERT_FALSE(config.getRestoreMuteState(restoreMuteState)); + ASSERT_FALSE(config.getMinUnmuteVolume(minUnmuteVolume)); + ASSERT_TRUE(config.getDefaultSpeakerVolume(defaultSpeakerVolume)); + ASSERT_EQ(5u, defaultSpeakerVolume); + ASSERT_FALSE(config.getDefaultAlertsVolume(defaultAlertsVolume)); +} + +/// Validate defaultAlertsVolume entry is read correctly. +TEST_F(SpeakerManagerConfigTest, test_ValidateDefaultAlertsVolumeConfig) { + ASSERT_TRUE(configureJsonConfig(JSON_TEST_CONFIG_DEFAULT_ALERTS_VOLUME)); + + SpeakerManagerConfig config; + bool persistentStorage; + bool restoreMuteState; + uint8_t minUnmuteVolume; + uint8_t defaultSpeakerVolume; + uint8_t defaultAlertsVolume = 0; + + ASSERT_FALSE(config.getPersistentStorage(persistentStorage)); + ASSERT_FALSE(config.getRestoreMuteState(restoreMuteState)); + ASSERT_FALSE(config.getMinUnmuteVolume(minUnmuteVolume)); + ASSERT_FALSE(config.getDefaultSpeakerVolume(defaultSpeakerVolume)); + ASSERT_TRUE(config.getDefaultAlertsVolume(defaultAlertsVolume)); + ASSERT_EQ(6u, defaultAlertsVolume); +} + +TEST_F(SpeakerManagerConfigTest, test_ValidateAllValues) { + ASSERT_TRUE(configureJsonConfig(JSON_TEST_CONFIG)); + + SpeakerManagerConfig config; + bool persistentStorage; + bool restoreMuteState; + uint8_t minUnmuteVolume; + uint8_t defaultSpeakerVolume; + uint8_t defaultAlertsVolume; + + ASSERT_TRUE(config.getPersistentStorage(persistentStorage)); + ASSERT_TRUE(persistentStorage); + ASSERT_TRUE(config.getRestoreMuteState(restoreMuteState)); + ASSERT_TRUE(restoreMuteState); + ASSERT_TRUE(config.getMinUnmuteVolume(minUnmuteVolume)); + ASSERT_EQ(3u, minUnmuteVolume); + ASSERT_TRUE(config.getDefaultSpeakerVolume(defaultSpeakerVolume)); + ASSERT_EQ(5u, defaultSpeakerVolume); + ASSERT_TRUE(config.getDefaultAlertsVolume(defaultAlertsVolume)); + ASSERT_EQ(6u, defaultAlertsVolume); +} + +} // namespace test +} // namespace speakerManager +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp similarity index 95% rename from CapabilityAgents/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp rename to CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp index b672df5a81..d0e2a6b9e5 100644 --- a/CapabilityAgents/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerMiscStorageTest.cpp @@ -13,30 +13,27 @@ * permissions and limitations under the License. */ +#include #include #include +#include #include -#include -#include -#include - -#include "SpeakerManager/SpeakerManagerMiscStorage.h" +#include namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { namespace test { using namespace avsCommon::avs; using namespace avsCommon::avs::speakerConstants; using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::sdkInterfaces::storage; -using namespace rapidjson; using namespace ::testing; -using namespace alexaClientSDK::capabilityAgents::speakerManager; -class MockMiscStorageInterface : public MiscStorageInterface { +/// @addtogroup Test_acsdkSpeakerManager +/// @{ + +class MockMiscStorage : public storage::MiscStorageInterface { public: MOCK_METHOD0(createDatabase, bool()); MOCK_METHOD0(open, bool()); @@ -71,6 +68,7 @@ static const std::string JSON_PAYLOAD = " }" "}"; +/// @brief Test fixture for SpeakerManagerMiscStorage. class SpeakerManagerMiscStorageTest : public ::testing::TestWithParam> { public: /// SetUp before each test. @@ -81,11 +79,13 @@ class SpeakerManagerMiscStorageTest : public ::testing::TestWithParam> m_stubMiscStorage; + std::shared_ptr> m_stubMiscStorage; }; +/// @} + void SpeakerManagerMiscStorageTest::SetUp() { - m_stubMiscStorage = std::make_shared>(); + m_stubMiscStorage = std::make_shared>(); } void SpeakerManagerMiscStorageTest::TearDown() { @@ -287,5 +287,4 @@ TEST_F(SpeakerManagerMiscStorageTest, test_parseJson) { } // namespace test } // namespace speakerManager -} // namespace capabilityAgents -} // namespace alexaClientSDK \ No newline at end of file +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerTest.cpp similarity index 55% rename from CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp rename to CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerTest.cpp index 1fb1a8799a..3a5590d445 100644 --- a/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerTest.cpp @@ -14,10 +14,21 @@ */ #include +#include #include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -28,18 +39,8 @@ #include #include #include -#include -#include -#include -#include -#include -#include - -#include "SpeakerManager/SpeakerManagerStorageInterface.h" -#include "SpeakerManager/SpeakerManager.h" namespace alexaClientSDK { -namespace capabilityAgents { namespace speakerManager { namespace test { @@ -52,35 +53,35 @@ using namespace avsCommon::utils::memory; using namespace rapidjson; using namespace ::testing; +/// @addtogroup Test_acsdkSpeakerManager +/// @{ + /// Timeout when waiting for futures to be set. static std::chrono::milliseconds TIMEOUT(1000); /// The @c MessageId identifer. -static const std::string MESSAGE_ID("messageId"); +static const auto MESSAGE_ID = "messageId"; /// A @c SetVolume/AdjustVolume payload. -static const std::string VOLUME_PAYLOAD = - "{" - "\"volume\":" + - std::to_string(AVS_SET_VOLUME_MAX) + - "" - "}"; +static const auto VOLUME_PAYLOAD = R"( +{ + "volume":100 +} +)"; /// A @c SetMute payload. -static const std::string MUTE_PAYLOAD = - "{" - "\"mute\":" + - MUTE_STRING + - "" - "}"; +static const auto MUTE_PAYLOAD = R"( +{ + "mute": true +} +)"; /// A @c SetMute payload to unmute. -static const std::string UNMUTE_PAYLOAD = - "{" - "\"mute\":" + - UNMUTE_STRING + - "" - "}"; +static const auto UNMUTE_PAYLOAD = R"( +{ + "mute": false +} +)"; #ifdef ENABLE_MAXVOLUME_SETTING /// A valid value to be used as maximum volume limit. @@ -94,23 +95,29 @@ static const int8_t INVALID_MAXIMUM_VOLUME_LIMIT = AVS_SET_VOLUME_MAX + 10; static const int8_t VALID_VOLUME_ADJUSTMENT = 10; /** - * A mock object to test that the observer is being correctly notified. + * @brief Extend MockSpeakerManagerStorage with helpers. */ -class MockObserver : public SpeakerManagerObserverInterface { +class MockSpeakerManagerStorageWithHelpers : public MockSpeakerManagerStorage { public: - MOCK_METHOD3( - onSpeakerSettingsChanged, - void(const Source&, const ChannelVolumeInterface::Type&, const SpeakerInterface::SpeakerSettings&)); -}; + /** + * Constructs object with default values and configures methods to return success. + */ + MockSpeakerManagerStorageWithHelpers() { + setDefaults(); + setSuccessMode(); + } -class MockSpeakerManagerStorage : public SpeakerManagerStorageInterface { -public: - MOCK_METHOD1(loadState, bool(SpeakerManagerStorageState&)); - MOCK_METHOD1(saveState, bool(const SpeakerManagerStorageState&)); + /** + * @brief Sets default values for cached channels' values. + */ + void setDefaults() { + m_state = {{AVS_SET_VOLUME_MIN, UNMUTE}, {AVS_SET_VOLUME_MIN, UNMUTE}}; + } - MockSpeakerManagerStorage() : - m_state{.speakerChannelState = {.channelVolume = AVS_SET_VOLUME_MIN, .channelMuteStatus = UNMUTE}, - .alertsChannelState = {.channelVolume = AVS_SET_VOLUME_MIN, .channelMuteStatus = UNMUTE}} { + /** + * @brief Configures mocked methods to load/store cached values. + */ + void setSuccessMode() { ON_CALL(*this, loadState(_)).WillByDefault(Invoke([this](SpeakerManagerStorageState& state) { state = this->m_state; return true; @@ -121,19 +128,21 @@ class MockSpeakerManagerStorage : public SpeakerManagerStorageInterface { })); } - void setDefaults() { - m_state = {.speakerChannelState = {.channelVolume = AVS_SET_VOLUME_MIN, .channelMuteStatus = UNMUTE}, - .alertsChannelState = {.channelVolume = AVS_SET_VOLUME_MIN, .channelMuteStatus = UNMUTE}}; - } - + /** + * @brief Configures mocked methods to fail. + */ void setFailureMode() { ON_CALL(*this, loadState(_)).WillByDefault(Return(false)); ON_CALL(*this, saveState(_)).WillByDefault(Return(false)); } + /// @brief Cached values for channels. SpeakerManagerStorageState m_state; }; +/** + * @brief Test fixture for SpeakerManager unit tests. + */ class SpeakerManagerTest : public ::testing::TestWithParam> { public: /// SetUp before each test. @@ -183,13 +192,17 @@ class SpeakerManagerTest : public ::testing::TestWithParam m_metricRecorder; - /* + /** * Set this to a nice mock. The only instance of the mock being called is the setStateProvider member, which we * explicitly test. */ std::shared_ptr> m_mockContextManager; - std::shared_ptr m_mockStorage; + /// Configuration interface mock. + std::shared_ptr m_mockConfig; + + /// Storage interface mock. + std::shared_ptr m_mockStorage; /// A strict mock that allows the test to strictly monitor the messages sent. std::shared_ptr> m_mockMessageSender; @@ -201,23 +214,27 @@ class SpeakerManagerTest : public ::testing::TestWithParam> m_mockDirectiveHandlerResult; /// A mock to allow testing of the observer callback behavior. - std::shared_ptr> m_observer; + std::shared_ptr> m_observer; /// A pointer to an instance of the SpeakerManager that will be instantiated per test. std::shared_ptr m_speakerManager; }; +/// @brief Test fixture for SpeakerManager. void SpeakerManagerTest::SetUp() { - m_mockStorage = std::make_shared>(); + m_mockConfig = std::make_shared>(); + m_mockStorage = std::make_shared>(); m_metricRecorder = std::make_shared>(); m_mockContextManager = std::make_shared>(); m_mockMessageSender = std::make_shared>(); m_mockExceptionSender = std::make_shared>(); m_mockDirectiveHandlerResult = make_unique>(); - m_observer = std::make_shared>(); + m_observer = std::make_shared>(); } +/// @} + void SpeakerManagerTest::TearDown() { if (m_speakerManager) { m_speakerManager->shutdown(); @@ -295,7 +312,13 @@ TEST_F(SpeakerManagerTest, test_nullContextManager) { auto channelVolumeInterfaces = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - m_mockStorage, channelVolumeInterfaces, nullptr, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + channelVolumeInterfaces, + nullptr, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); ASSERT_EQ(m_speakerManager, nullptr); } @@ -306,7 +329,13 @@ TEST_F(SpeakerManagerTest, test_nullContextManager) { TEST_F(SpeakerManagerTest, test_nullMessageSender) { auto channelVolumeInterfaces = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - m_mockStorage, channelVolumeInterfaces, m_mockContextManager, nullptr, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + channelVolumeInterfaces, + m_mockContextManager, + nullptr, + m_mockExceptionSender, + m_metricRecorder); ASSERT_EQ(m_speakerManager, nullptr); } @@ -317,7 +346,13 @@ TEST_F(SpeakerManagerTest, test_nullMessageSender) { TEST_F(SpeakerManagerTest, test_nullExceptionSender) { auto channelVolumeInterfaces = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - m_mockStorage, channelVolumeInterfaces, m_mockContextManager, m_mockMessageSender, nullptr, m_metricRecorder); + m_mockConfig, + m_mockStorage, + channelVolumeInterfaces, + m_mockContextManager, + m_mockMessageSender, + nullptr, + m_metricRecorder); ASSERT_EQ(m_speakerManager, nullptr); } @@ -327,11 +362,55 @@ TEST_F(SpeakerManagerTest, test_nullExceptionSender) { */ TEST_F(SpeakerManagerTest, test_noChannelVolumeInterfaces) { m_speakerManager = SpeakerManager::create( - m_mockStorage, {}, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + {}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); ASSERT_NE(m_speakerManager, nullptr); } +/** + * Tests that adding a channel volume interface does not overwrite existing default volume settings when persistent + * storage is enabled. + */ +TEST_F(SpeakerManagerTest, test_addChannelVolumeInterfaceDoesNotOverwriteDefaults) { + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + {}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + channelVolumeInterface->setUnduckedVolume(AVS_SET_VOLUME_MAX); + channelVolumeInterface->setMute(MUTE); + + SpeakerInterface::SpeakerSettings settings; + ASSERT_TRUE(channelVolumeInterface->getSpeakerSettings(&settings)); + ASSERT_EQ(settings.volume, AVS_SET_VOLUME_MAX); + ASSERT_EQ(settings.mute, MUTE); + + m_speakerManager->addChannelVolumeInterface(channelVolumeInterface); + auto future = m_speakerManager->getSpeakerSettings(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, &settings); + ASSERT_TRUE(future.get()); + ASSERT_EQ(settings.volume, AVS_SET_VOLUME_MIN); + ASSERT_EQ(settings.mute, UNMUTE); +} + /** * Tests that the SpeakerManager initially provides the state at constructor time. */ @@ -343,20 +422,64 @@ TEST_F(SpeakerManagerTest, test_contextManagerSetStateConstructor) { auto groups = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( - m_mockStorage, groups, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groups, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); } -/* +/** * Test setVolume with a value that's under the bounds. The operation should fail. */ TEST_F(SpeakerManagerTest, test_setVolumeUnderBounds) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); + // Expect call on initialization + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // Expect no more calls + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + std::future future = m_speakerManager->setVolume( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, AVS_SET_VOLUME_MIN - 1, properties); + ASSERT_FALSE(future.get()); +} + +/** + * Test setVolume with a value that's under the bounds with persistent storage enabled. The operation should fail. + */ +TEST_F(SpeakerManagerTest, test_setVolumeUnderBoundsWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + // Expect call on initialization EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {channelVolumeInterface}, m_mockContextManager, @@ -376,17 +499,54 @@ TEST_F(SpeakerManagerTest, test_setVolumeUnderBounds) { ASSERT_FALSE(future.get()); } -/* +/** * Test setVolume with a value that's over the bounds. The operation should fail. */ TEST_F(SpeakerManagerTest, test_setVolumeOverBounds) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); + // Expect call on initialization. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // Expect no more calls. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + std::future future = m_speakerManager->setVolume( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, AVS_SET_VOLUME_MAX + 1, properties); + ASSERT_FALSE(future.get()); +} + +/** + * Test setVolume with a value that's over the bounds with persistent storage enabled. The operation should fail. + */ +TEST_F(SpeakerManagerTest, test_setVolumeOverBoundsWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + // Expect call on initialization. EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {channelVolumeInterface}, m_mockContextManager, @@ -405,17 +565,55 @@ TEST_F(SpeakerManagerTest, test_setVolumeOverBounds) { ASSERT_FALSE(future.get()); } -/* +/** * Test adjustVolume with a value that's under the bounds. The operation should fail. */ TEST_F(SpeakerManagerTest, test_adjustVolumeUnderBounds) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); + // Expect call on initialization. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // Expect no more calls. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + + SpeakerManagerInterface::NotificationProperties properties; + std::future future = m_speakerManager->adjustVolume( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, AVS_ADJUST_VOLUME_MIN - 1, properties); + ASSERT_FALSE(future.get()); +} + +/** + * Test adjustVolume with a value that's under the bounds with persistent storage enabled. The operation should fail. + */ +TEST_F(SpeakerManagerTest, test_adjustVolumeUnderBoundsWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + // Expect call on initialization. EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {channelVolumeInterface}, m_mockContextManager, @@ -435,16 +633,52 @@ TEST_F(SpeakerManagerTest, test_adjustVolumeUnderBounds) { ASSERT_FALSE(future.get()); } -/* +/** * Test adjustVolume with a value that's over the bounds. The operation should fail. */ TEST_F(SpeakerManagerTest, test_adjustVolumeOverBounds) { + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + // Expect call on initialization. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // Expect no more calls. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + std::future future = m_speakerManager->adjustVolume( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, AVS_ADJUST_VOLUME_MAX + 1, properties); + ASSERT_FALSE(future.get()); +} + +/** + * Test adjustVolume with a value that's over the bounds with persistent storage enabled. The operation should fail. + */ +TEST_F(SpeakerManagerTest, test_adjustVolumeOverBoundsWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); // Expect call on initialization. EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {channelVolumeInterface}, m_mockContextManager, @@ -463,7 +697,7 @@ TEST_F(SpeakerManagerTest, test_adjustVolumeOverBounds) { ASSERT_FALSE(future.get()); } -/* +/** * Test if one speaker is out of sync, getSpeakingSettings should return the cached value correctly. */ TEST_F(SpeakerManagerTest, test_getCachedSettings) { @@ -476,6 +710,7 @@ TEST_F(SpeakerManagerTest, test_getCachedSettings) { EXPECT_CALL(*channelVolumeInterface1, getSpeakerSettings(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {channelVolumeInterface1, channelVolumeInterface2}, m_mockContextManager, @@ -499,7 +734,7 @@ TEST_F(SpeakerManagerTest, test_getCachedSettings) { ASSERT_EQ(settings.mute, MUTE); } -/* +/** * Test adjustVolume when the adjusted volume is unchanged. Should not send an event. */ TEST_F(SpeakerManagerTest, test_eventNotSentWhenAdjustVolumeUnchanged) { @@ -509,7 +744,13 @@ TEST_F(SpeakerManagerTest, test_eventNotSentWhenAdjustVolumeUnchanged) { auto groupVec = std::vector>{channelVolumeInterface}; m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); // The test adjusts the volume by AVS_ADJUST_VOLUME_MIN, which results in the lowest volume possible. SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; @@ -537,18 +778,31 @@ TEST_F(SpeakerManagerTest, test_eventNotSentWhenAdjustVolumeUnchanged) { } } -/* - * Test setVolume when the new volume is unchanged. Should not send an event. +/** + * Test adjustVolume when the adjusted volume is unchanged with persistent storage enabled. Should not send an event. */ -TEST_F(SpeakerManagerTest, test_eventNotSentWhenSetVolumeUnchanged) { +TEST_F(SpeakerManagerTest, test_eventNotSentWhenAdjustVolumeUnchangedWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); - EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(AVS_SET_VOLUME_MIN)).Times(Exactly(2)); auto groupVec = std::vector>{channelVolumeInterface}; + m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + // The test adjusts the volume by AVS_ADJUST_VOLUME_MIN, which results in the lowest volume possible. SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); SpeakerManagerInterface::NotificationProperties properties; @@ -558,8 +812,8 @@ TEST_F(SpeakerManagerTest, test_eventNotSentWhenSetVolumeUnchanged) { *m_observer, onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) .Times(Exactly(1)); - EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(0)); EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) .Times(AnyNumber()); @@ -569,41 +823,135 @@ TEST_F(SpeakerManagerTest, test_eventNotSentWhenSetVolumeUnchanged) { .Times(Exactly(1)); } - std::future future = m_speakerManager->setVolume(type, AVS_SET_VOLUME_MIN, properties); + std::future future = m_speakerManager->adjustVolume(type, AVS_ADJUST_VOLUME_MIN, properties); ASSERT_TRUE(future.get()); } } /** - * Test getConfiguration and ensure that all directives are handled. + * Test setVolume when the new volume is unchanged. Should not send an event. */ -TEST_F(SpeakerManagerTest, test_getConfiguration) { - auto channelVolumeInterfaceVec = createChannelVolumeInterfaces(); +TEST_F(SpeakerManagerTest, test_eventNotSentWhenSetVolumeUnchanged) { + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(AVS_SET_VOLUME_MIN)).Times(Exactly(1)); + auto groupVec = std::vector>{channelVolumeInterface}; m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, - channelVolumeInterfaceVec, + groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); - auto configuration = m_speakerManager->getConfiguration(); - auto neitherNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); - ASSERT_EQ(configuration[SET_VOLUME], neitherNonBlockingPolicy); - ASSERT_EQ(configuration[ADJUST_VOLUME], neitherNonBlockingPolicy); - ASSERT_EQ(configuration[SET_MUTE], neitherNonBlockingPolicy); -} + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; -/** - * Test that adding duplicated ChannelVolumeInterface instances in the SpeakerManager works correctly. - */ -TEST_F(SpeakerManagerTest, test_addDuplicatedChannelVolumeInterfaces) { - auto channelVolumeInterface = std::make_shared>(); - channelVolumeInterface->DelegateToReal(); - std::vector> channelVolumeInterfaceVec = {channelVolumeInterface, - channelVolumeInterface}; + for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) + .Times(Exactly(1)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } + + std::future future = m_speakerManager->setVolume(type, AVS_SET_VOLUME_MIN, properties); + ASSERT_TRUE(future.get()); + } +} + +/** + * Test setVolume when the new volume is unchanged with persistent storage enabled. Should not send an event. + */ +TEST_F(SpeakerManagerTest, test_eventNotSentWhenSetVolumeUnchangedWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(AVS_SET_VOLUME_MIN)).Times(Exactly(2)); + + auto groupVec = std::vector>{channelVolumeInterface}; + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + + for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) + .Times(Exactly(1)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } + + std::future future = m_speakerManager->setVolume(type, AVS_SET_VOLUME_MIN, properties); + ASSERT_TRUE(future.get()); + } +} + +/** + * Test getConfiguration and ensure that all directives are handled. + */ +TEST_F(SpeakerManagerTest, test_getConfiguration) { + auto channelVolumeInterfaceVec = createChannelVolumeInterfaces(); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + channelVolumeInterfaceVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + auto configuration = m_speakerManager->getConfiguration(); + auto neitherNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUMS_NONE, false); + ASSERT_EQ(configuration[SET_VOLUME], neitherNonBlockingPolicy); + ASSERT_EQ(configuration[ADJUST_VOLUME], neitherNonBlockingPolicy); + ASSERT_EQ(configuration[SET_MUTE], neitherNonBlockingPolicy); +} + +/** + * Test that adding duplicated ChannelVolumeInterface instances in the SpeakerManager works correctly. + */ +TEST_F(SpeakerManagerTest, test_addDuplicatedChannelVolumeInterfaces) { + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + std::vector> channelVolumeInterfaceVec = {channelVolumeInterface, + channelVolumeInterface}; m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, channelVolumeInterfaceVec, m_mockContextManager, @@ -625,6 +973,7 @@ TEST_F(SpeakerManagerTest, test_addNullObserver) { auto channelVolumeInterfaceVec = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, channelVolumeInterfaceVec, m_mockContextManager, @@ -640,6 +989,9 @@ TEST_F(SpeakerManagerTest, test_addNullObserver) { m_speakerManager->adjustVolume(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, AVS_ADJUST_VOLUME_MAX, properties) .wait(); m_speakerManager->setMute(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, MUTE, properties).wait(); + m_speakerManager->onExternalSpeakerSettingsUpdate( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, {AVS_SET_VOLUME_MAX, MUTE}, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); } /** @@ -652,6 +1004,7 @@ TEST_F(SpeakerManagerTest, test_removeSpeakerManagerObserver) { EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(2)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, channelVolumeInterfaceVec, m_mockContextManager, @@ -667,6 +1020,9 @@ TEST_F(SpeakerManagerTest, test_removeSpeakerManagerObserver) { m_speakerManager->adjustVolume(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, AVS_ADJUST_VOLUME_MAX, properties) .wait(); m_speakerManager->setMute(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, MUTE, properties).wait(); + m_speakerManager->onExternalSpeakerSettingsUpdate( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, {AVS_SET_VOLUME_MAX, MUTE}, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); } /** @@ -678,6 +1034,7 @@ TEST_F(SpeakerManagerTest, test_removeNullObserver) { EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(2)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, channelVolumeInterfaceVec, m_mockContextManager, @@ -692,9 +1049,12 @@ TEST_F(SpeakerManagerTest, test_removeNullObserver) { m_speakerManager->adjustVolume(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, AVS_ADJUST_VOLUME_MAX, properties) .wait(); m_speakerManager->setMute(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, MUTE, properties).wait(); + m_speakerManager->onExternalSpeakerSettingsUpdate( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, {AVS_SET_VOLUME_MAX, MUTE}, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); } -/* +/** * Test retry logic for SetVolume on speaker type AVS_SPEAKER_VOLUME. Returning false once for speaker->setVolume() * triggers retry and when successful returns the future of value true. */ @@ -702,6 +1062,7 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForSetVolume) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {channelVolumeInterface}, m_mockContextManager, @@ -720,7 +1081,7 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForSetVolume) { ASSERT_TRUE(future.get()); } -/* +/** * Test retry logic for AdjustVolume on speakers of type AVS_SPEAKER_VOLUME. Return false once for the second speaker * during adjustVolume() to trigger a retry. The delta should not be applied again to the first speaker during retry. */ @@ -731,6 +1092,7 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForAdjustVolume) { channelVolumeInterface2->DelegateToReal(); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {channelVolumeInterface1, channelVolumeInterface2}, m_mockContextManager, @@ -762,7 +1124,7 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForAdjustVolume) { ASSERT_EQ(speakerSettings.volume, DEFAULT_SETTINGS.volume + VALID_VOLUME_ADJUSTMENT); } -/* +/** * Test retry logic for SetMute on speaker type AVS_SPEAKER_VOLUME. Returning false once for speaker->setMute() * triggers retry and when successful returns the future of value true. */ @@ -770,6 +1132,7 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForSetMute) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {channelVolumeInterface}, m_mockContextManager, @@ -790,7 +1153,7 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsForSetMute) { ASSERT_TRUE(future.get()); } -/* +/** * Test retryAndApplySettings() failure for setVolume, adjustVolume and setMute on speaker type AVS_SPEAKER_VOLUME. * Repeatedly returning false for adjustVolume() and setMute() to trigger retries. After retrying maximum times, * returning the future of false. @@ -799,6 +1162,7 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsFails) { auto channelVolumeInterface = std::make_shared>(); channelVolumeInterface->DelegateToReal(); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {channelVolumeInterface}, m_mockContextManager, @@ -839,7 +1203,6 @@ TEST_F(SpeakerManagerTest, test_retryAndApplySettingsFails) { * Test that setting a maximum volume limit succeeds and a local call to setVolume or adjustVolume will * completely fail. */ - TEST_F(SpeakerManagerTest, test_setMaximumVolumeLimit) { auto avsChannelVolumeInterface = std::make_shared>(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME); @@ -859,6 +1222,7 @@ TEST_F(SpeakerManagerTest, test_setMaximumVolumeLimit) { EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {avsChannelVolumeInterface, alertsChannelVolumeInterface}, m_mockContextManager, @@ -916,6 +1280,7 @@ TEST_F(SpeakerManagerTest, testSetMaximumVolumeLimitWhileVolumeIsHigher) { EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {avsChannelVolumeInterface, alertsChannelVolumeInterface}, m_mockContextManager, @@ -950,6 +1315,7 @@ TEST_F(SpeakerManagerTest, testAVSSetVolumeHigherThanLimit) { EXPECT_TRUE(alertsChannelVolumeInterface->setUnduckedVolume(VALID_MAXIMUM_VOLUME_LIMIT - 1)); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, {avsChannelVolumeInterface, alertsChannelVolumeInterface}, m_mockContextManager, @@ -972,6 +1338,7 @@ TEST_F(SpeakerManagerTest, testSetMaximumVolumeLimitWithInvalidValue) { auto avsChannelVolumeInterface = createChannelVolumeInterfaces(); m_speakerManager = SpeakerManager::create( + m_mockConfig, m_mockStorage, avsChannelVolumeInterface, m_mockContextManager, @@ -1022,20 +1389,26 @@ TEST_P(SpeakerManagerTest, test_setVolume) { for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); group->DelegateToReal(); - EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(1)); + EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(0)); EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(1)); groupVec.push_back(group); } m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); SpeakerManagerInterface::NotificationProperties properties(SpeakerManagerObserverInterface::Source::DIRECTIVE); for (auto type : getUniqueTypes(groupVec)) { - EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); EXPECT_CALL( *m_observer, onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::DIRECTIVE, type, expectedSettings)) @@ -1056,21 +1429,35 @@ TEST_P(SpeakerManagerTest, test_setVolume) { } /** - * Parameterized test for adjustVolume. One event should be sent if an AVS_SPEAKER_VOLUME typed speaker is modified. + * Parameterized test for setVolume with persistent storage enabled. One event should be sent if an AVS_SPEAKER_VOLUME + * typed speaker is modified. */ -TEST_P(SpeakerManagerTest, test_adjustVolume) { +TEST_P(SpeakerManagerTest, test_setVolumeWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + std::vector> groupVec; for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); group->DelegateToReal(); + EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(1)); + EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(1)); groupVec.push_back(group); } m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); - // The test adjusts the volume by AVS_ADJUST_VOLUME_MAX, which results in the lowest volume possible. SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); SpeakerManagerInterface::NotificationProperties properties(SpeakerManagerObserverInterface::Source::DIRECTIVE); @@ -1091,36 +1478,42 @@ TEST_P(SpeakerManagerTest, test_adjustVolume) { .Times(Exactly(1)); } - std::future future = m_speakerManager->adjustVolume(type, AVS_ADJUST_VOLUME_MAX, properties); + std::future future = m_speakerManager->setVolume(type, AVS_SET_VOLUME_MAX, properties); ASSERT_TRUE(future.get()); } } /** - * Parameterized test for setMute. One event should be sent if an AVS_SPEAKER_VOLUME typed speaker is modified. + * Parameterized test for onExternalSpeakerSettingsUpdate. One event should be sent if an AVS_SPEAKER_VOLUME typed + * speaker is modified. */ -TEST_P(SpeakerManagerTest, test_setMute) { +TEST_P(SpeakerManagerTest, test_onExternalSpeakerSettingsUpdate) { std::vector> groupVec; for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); group->DelegateToReal(); - EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); - EXPECT_CALL(*group, setMute(MUTE)).Times(Exactly(1)); groupVec.push_back(group); } m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); - SpeakerInterface::SpeakerSettings expectedSettings{DEFAULT_SETTINGS.volume, MUTE}; + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); - SpeakerManagerInterface::NotificationProperties properties(SpeakerManagerObserverInterface::Source::DIRECTIVE); + SpeakerManagerInterface::NotificationProperties properties; for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); EXPECT_CALL( *m_observer, - onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::DIRECTIVE, type, expectedSettings)) + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) .Times(Exactly(1)); if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); @@ -1132,96 +1525,699 @@ TEST_P(SpeakerManagerTest, test_setMute) { .Times(Exactly(1)); } - std::future future = m_speakerManager->setMute(type, MUTE, properties); - ASSERT_TRUE(future.get()); + m_speakerManager->onExternalSpeakerSettingsUpdate(type, expectedSettings, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); } } /** - * Parameterized test for getSpeakerSettings. Operation should succeed with default speaker settings. + * Parameterized test for onExternalSpeakerSettingsUpdate with persistent storage enabled. One event should be sent if + * an AVS_SPEAKER_VOLUME typed speaker is modified. */ -TEST_P(SpeakerManagerTest, test_getSpeakerSettings) { +TEST_P(SpeakerManagerTest, test_onExternalSpeakerSettingsUpdateWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + std::vector> groupVec; - std::set uniqueTypes; for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); group->DelegateToReal(); - - // There should be one call to getSpeakerSettings for the first speaker of each type. - if (uniqueTypes.find(typeOfSpeaker) == uniqueTypes.end()) { - EXPECT_CALL(*group, getSpeakerSettings(_)).Times(AtLeast(1)); - uniqueTypes.insert(typeOfSpeaker); - } - groupVec.push_back(group); } m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); - EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; - for (auto speaker : groupVec) { - // SpeakerManager attempts to cache speaker settings initially. No getSpeakerSettings() call should be made to - // each speaker. - auto mockSpeaker = std::dynamic_pointer_cast>(speaker); - ASSERT_TRUE(mockSpeaker); - EXPECT_CALL(*mockSpeaker, getSpeakerSettings(_)).Times(0); - } + for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) + .Times(Exactly(1)); + if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } - for (auto type : uniqueTypes) { - SpeakerInterface::SpeakerSettings settings; - // Query SpeakerMananger for speaker settings, value should be cached and not queried from each speaker. - std::future future = m_speakerManager->getSpeakerSettings(type, &settings); - ASSERT_TRUE(future.get()); - ASSERT_EQ(settings.volume, DEFAULT_SETTINGS.volume); - ASSERT_EQ(settings.mute, DEFAULT_SETTINGS.mute); + m_speakerManager->onExternalSpeakerSettingsUpdate(type, expectedSettings, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); } } /** - * Tests SetVolume Directive. Expect that the volume is unmuted and set, as well at most one - * event is sent. In the event there are no AVS_SPEAKER_VOLUME speakers registered, no event will be sent. - * In addition, only AVS_SPEAKER_VOLUME speakers should be affected. + * Test onExternalSpeakerSettingsUpdate when the new volume is unchanged. Should not send an event. */ -TEST_P(SpeakerManagerTest, test_setVolumeDirective) { - std::vector> groupVec; - int eventsSent = 0; - SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; +TEST_F(SpeakerManagerTest, test_eventNotSentWhenOnExternalSpeakerSettingsUpdateUnchanged) { + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + // expect call during initialization + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(AVS_SET_VOLUME_MIN)).Times(Exactly(0)); - // Create Speaker objects. - for (auto& typeOfSpeaker : GetParam()) { - auto group = std::make_shared>(typeOfSpeaker); - group->DelegateToReal(); - int timesCalled = 0; - if (typeOfSpeaker == ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME) { - timesCalled = 1; - } + auto groupVec = std::vector>{channelVolumeInterface}; + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); - SpeakerInterface::SpeakerSettings temp; - group->getSpeakerSettings(&temp); - if (temp.mute) { - EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); - EXPECT_CALL(*group, setMute(UNMUTE)).Times(Exactly(timesCalled)); + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + + for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) + .Times(Exactly(1)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); } - EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(1)); - EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(timesCalled)); - groupVec.push_back(group); + m_speakerManager->onExternalSpeakerSettingsUpdate(type, expectedSettings, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); } +} - auto uniqueTypes = getUniqueTypes(groupVec); +/** + * Test onExternalSpeakerSettingsUpdate when the new volume is unchanged with persistent storage enabled. Should not + * send an event. + */ +TEST_F(SpeakerManagerTest, test_eventNotSentWhenOnExternalSpeakerSettingsUpdateUnchangedWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); - // Creation expectations based on type. - if (uniqueTypes.count(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME)) { - eventsSent = 1; + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + // expect call during initialization + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(AVS_SET_VOLUME_MIN)).Times(Exactly(1)); - EXPECT_CALL( - *m_observer, - onSpeakerSettingsChanged( - SpeakerManagerObserverInterface::Source::DIRECTIVE, + auto groupVec = std::vector>{channelVolumeInterface}; + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + + for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) + .Times(Exactly(1)); + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } + + m_speakerManager->onExternalSpeakerSettingsUpdate(type, expectedSettings, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); + } +} + +/** + * Test onExternalSpeakerSettingsUpdate with a value that's under the bounds. The operation should fail. + */ +TEST_F(SpeakerManagerTest, test_onExternalSpeakerSettingsUpdateUnderBounds) { + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + + // Expect call on initialization + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // Expect calls with volume set to minimum + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(1)); + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + m_speakerManager->onExternalSpeakerSettingsUpdate( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, {AVS_SET_VOLUME_MIN - 1, MUTE}, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); + + SpeakerInterface::SpeakerSettings settings; + // Query SpeakerMananger for speaker settings + auto future = m_speakerManager->getSpeakerSettings(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, &settings); + ASSERT_TRUE(future.get()); + ASSERT_EQ(settings.volume, AVS_SET_VOLUME_MIN); + ASSERT_EQ(settings.mute, MUTE); +} + +/** + * Test onExternalSpeakerSettingsUpdate with a value that's under the bounds with persistent storage enabled. + * The operation should fail. + */ +TEST_F(SpeakerManagerTest, test_onExternalSpeakerSettingsUpdateUnderBoundsWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + + // Expect call on initialization + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // Expect calls with volume set to minimum + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(1)); + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + m_speakerManager->onExternalSpeakerSettingsUpdate( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, {AVS_SET_VOLUME_MIN - 1, MUTE}, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); + + SpeakerInterface::SpeakerSettings settings; + // Query SpeakerMananger for speaker settings + auto future = m_speakerManager->getSpeakerSettings(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, &settings); + ASSERT_TRUE(future.get()); + ASSERT_EQ(settings.volume, AVS_SET_VOLUME_MIN); + ASSERT_EQ(settings.mute, MUTE); +} + +/** + * Test onExternalSpeakerSettingsUpdate with a value that's over the bounds. The operation should fail. + */ +TEST_F(SpeakerManagerTest, test_onExternalSpeakerSettingsUpdateOverBounds) { + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + + // Expect call on initialization. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(0)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // Expect no more calls. + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(1)); + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + m_speakerManager->onExternalSpeakerSettingsUpdate( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, {AVS_SET_VOLUME_MAX + 1, UNMUTE}, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); + + SpeakerInterface::SpeakerSettings settings; + // Query SpeakerMananger for speaker settings + auto future = m_speakerManager->getSpeakerSettings(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, &settings); + ASSERT_TRUE(future.get()); + ASSERT_EQ(settings.volume, AVS_SET_VOLUME_MAX); + ASSERT_EQ(settings.mute, UNMUTE); +} + +/** + * Test onExternalSpeakerSettingsUpdate with a value that's over the bounds with persistent storage enabled. The + * operation should fail. + */ +TEST_F(SpeakerManagerTest, test_onExternalSpeakerSettingsUpdateOverBoundsWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + auto channelVolumeInterface = std::make_shared>(); + channelVolumeInterface->DelegateToReal(); + + // Expect call on initialization. + EXPECT_CALL(*channelVolumeInterface, setUnduckedVolume(_)).Times(Exactly(1)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + {channelVolumeInterface}, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // Expect no more calls. + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(1)); + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties; + m_speakerManager->onExternalSpeakerSettingsUpdate( + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, {AVS_SET_VOLUME_MAX + 1, UNMUTE}, properties); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); + + SpeakerInterface::SpeakerSettings settings; + // Query SpeakerMananger for speaker settings + auto future = m_speakerManager->getSpeakerSettings(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, &settings); + ASSERT_TRUE(future.get()); + ASSERT_EQ(settings.volume, AVS_SET_VOLUME_MAX); + ASSERT_EQ(settings.mute, UNMUTE); +} + +/** + * Parameterized test for adjustVolume. One event should be sent if an AVS_SPEAKER_VOLUME typed speaker is modified. + */ +TEST_P(SpeakerManagerTest, test_adjustVolume) { + std::vector> groupVec; + + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + groupVec.push_back(group); + } + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // The test adjusts the volume by AVS_ADJUST_VOLUME_MAX, which results in the lowest volume possible. + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties(SpeakerManagerObserverInterface::Source::DIRECTIVE); + + for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(0)); + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::DIRECTIVE, type, expectedSettings)) + .Times(Exactly(1)); + if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } + + std::future future = m_speakerManager->adjustVolume(type, AVS_ADJUST_VOLUME_MAX, properties); + ASSERT_TRUE(future.get()); + } +} + +/** + * Parameterized test for adjustVolume with persistent storage enabled. One event should be sent if an + * AVS_SPEAKER_VOLUME typed speaker is modified. + */ +TEST_P(SpeakerManagerTest, test_adjustVolumeWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + std::vector> groupVec; + + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + groupVec.push_back(group); + } + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + // The test adjusts the volume by AVS_ADJUST_VOLUME_MAX, which results in the lowest volume possible. + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties(SpeakerManagerObserverInterface::Source::DIRECTIVE); + + for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL(*m_mockStorage, saveState(_)).Times(Exactly(1)); + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::DIRECTIVE, type, expectedSettings)) + .Times(Exactly(1)); + if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } + + std::future future = m_speakerManager->adjustVolume(type, AVS_ADJUST_VOLUME_MAX, properties); + ASSERT_TRUE(future.get()); + } +} + +/** + * Parameterized test for setMute. One event should be sent if an AVS_SPEAKER_VOLUME typed speaker is modified. + */ +TEST_P(SpeakerManagerTest, test_setMute) { + std::vector> groupVec; + + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + EXPECT_CALL(*group, setMute(_)).Times(Exactly(0)); + EXPECT_CALL(*group, setMute(MUTE)).Times(Exactly(1)); + groupVec.push_back(group); + } + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + SpeakerInterface::SpeakerSettings expectedSettings{DEFAULT_SETTINGS.volume, MUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties(SpeakerManagerObserverInterface::Source::DIRECTIVE); + + for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::DIRECTIVE, type, expectedSettings)) + .Times(Exactly(1)); + if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } + + std::future future = m_speakerManager->setMute(type, MUTE, properties); + ASSERT_TRUE(future.get()); + } +} + +/** + * Parameterized test for setMute with persistent storage enabled. One event should be sent if an AVS_SPEAKER_VOLUME + * typed speaker is modified. + */ +TEST_P(SpeakerManagerTest, test_setMuteWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + std::vector> groupVec; + + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); + EXPECT_CALL(*group, setMute(MUTE)).Times(Exactly(1)); + groupVec.push_back(group); + } + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + SpeakerInterface::SpeakerSettings expectedSettings{DEFAULT_SETTINGS.volume, MUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + SpeakerManagerInterface::NotificationProperties properties(SpeakerManagerObserverInterface::Source::DIRECTIVE); + + for (auto type : getUniqueTypes(groupVec)) { + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::DIRECTIVE, type, expectedSettings)) + .Times(Exactly(1)); + if (ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } + + std::future future = m_speakerManager->setMute(type, MUTE, properties); + ASSERT_TRUE(future.get()); + } +} + +/** + * Parameterized test for getSpeakerSettings. Operation should succeed with default speaker settings. + */ +TEST_P(SpeakerManagerTest, test_getSpeakerSettings) { + std::vector> groupVec; + std::set uniqueTypes; + + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + + // There should be one call to getSpeakerSettings for the first speaker of each type. + if (uniqueTypes.find(typeOfSpeaker) == uniqueTypes.end()) { + EXPECT_CALL(*group, getSpeakerSettings(_)).Times(AtLeast(1)); + uniqueTypes.insert(typeOfSpeaker); + } + + groupVec.push_back(group); + } + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + m_speakerManager->addSpeakerManagerObserver(m_observer); + + for (auto speaker : groupVec) { + // SpeakerManager attempts to cache speaker settings initially. No getSpeakerSettings() call should be made to + // each speaker. + auto mockSpeaker = std::dynamic_pointer_cast>(speaker); + ASSERT_TRUE(mockSpeaker); + EXPECT_CALL(*mockSpeaker, getSpeakerSettings(_)).Times(0); + } + + for (auto type : uniqueTypes) { + SpeakerInterface::SpeakerSettings settings; + // Query SpeakerMananger for speaker settings, value should be cached and not queried from each speaker. + std::future future = m_speakerManager->getSpeakerSettings(type, &settings); + ASSERT_TRUE(future.get()); + ASSERT_EQ(settings.volume, DEFAULT_SETTINGS.volume); + ASSERT_EQ(settings.mute, DEFAULT_SETTINGS.mute); + } +} + +/** + * Tests SetVolume Directive. Expect that the volume is unmuted and set, as well at most one + * event is sent. In the event there are no AVS_SPEAKER_VOLUME speakers registered, no event will be sent. + * In addition, only AVS_SPEAKER_VOLUME speakers should be affected. + */ +TEST_P(SpeakerManagerTest, test_setVolumeDirective) { + std::vector> groupVec; + int eventsSent = 0; + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; + + // Create Speaker objects. + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + int timesCalled = 0; + if (typeOfSpeaker == ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME) { + timesCalled = 1; + } + + SpeakerInterface::SpeakerSettings temp; + group->getSpeakerSettings(&temp); + if (temp.mute) { + EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); + EXPECT_CALL(*group, setMute(UNMUTE)).Times(Exactly(timesCalled)); + } + EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(timesCalled)); + + groupVec.push_back(group); + } + + auto uniqueTypes = getUniqueTypes(groupVec); + + // Creation expectations based on type. + if (uniqueTypes.count(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME)) { + eventsSent = 1; + + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged( + SpeakerManagerObserverInterface::Source::DIRECTIVE, + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, + expectedSettings)) + .Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)).Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } else { + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)).Times(Exactly(0)); + } + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(eventsSent)); + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + m_speakerManager->addSpeakerManagerObserver(m_observer); + + // Create Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = std::make_shared(SET_VOLUME.nameSpace, SET_VOLUME.name, MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, VOLUME_PAYLOAD, attachmentManager, ""); + + m_speakerManager->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + m_speakerManager->CapabilityAgent::handleDirective(MESSAGE_ID); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); +} + +/** + * Tests SetVolume Directive with persistent storage enabled. Expect that the volume is unmuted and set, as well at most + * one event is sent. In the event there are no AVS_SPEAKER_VOLUME speakers registered, no event will be sent. + * In addition, only AVS_SPEAKER_VOLUME speakers should be affected. + */ +TEST_P(SpeakerManagerTest, test_setVolumeDirectiveWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + std::vector> groupVec; + int eventsSent = 0; + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; + + // Create Speaker objects. + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + int timesCalled = 0; + if (typeOfSpeaker == ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME) { + timesCalled = 1; + } + + SpeakerInterface::SpeakerSettings temp; + group->getSpeakerSettings(&temp); + if (temp.mute) { + EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); + EXPECT_CALL(*group, setMute(UNMUTE)).Times(Exactly(timesCalled)); + } + EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(1)); + EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(timesCalled)); + + groupVec.push_back(group); + } + + auto uniqueTypes = getUniqueTypes(groupVec); + + // Creation expectations based on type. + if (uniqueTypes.count(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME)) { + eventsSent = 1; + + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged( + SpeakerManagerObserverInterface::Source::DIRECTIVE, ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, expectedSettings)) .Times(Exactly(1)); @@ -1240,7 +2236,13 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirective) { .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); m_speakerManager->addSpeakerManagerObserver(m_observer); // Create Directive. @@ -1264,6 +2266,91 @@ TEST_P(SpeakerManagerTest, test_adjustVolumeDirective) { int eventsSent = 0; SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; + // Create Speaker objects. + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + int timesCalled = 0; + if (typeOfSpeaker == ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME) { + timesCalled = 1; + } + + SpeakerInterface::SpeakerSettings temp; + group->getSpeakerSettings(&temp); + if (temp.mute) { + EXPECT_CALL(*group, setMute(_)).Times(Exactly(1)); + EXPECT_CALL(*group, setMute(UNMUTE)).Times(Exactly(timesCalled)); + } + EXPECT_CALL(*group, setUnduckedVolume(_)).Times(Exactly(0)); + EXPECT_CALL(*group, setUnduckedVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(timesCalled)); + + groupVec.push_back(group); + } + + auto uniqueTypes = getUniqueTypes(groupVec); + + // Creation expectations based on type. + if (uniqueTypes.count(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME)) { + eventsSent = 1; + + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged( + SpeakerManagerObserverInterface::Source::DIRECTIVE, + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, + expectedSettings)) + .Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)).Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } else { + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)).Times(Exactly(0)); + } + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(eventsSent)); + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + m_speakerManager->addSpeakerManagerObserver(m_observer); + + // Create Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = std::make_shared(ADJUST_VOLUME.nameSpace, ADJUST_VOLUME.name, MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, VOLUME_PAYLOAD, attachmentManager, ""); + + m_speakerManager->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + m_speakerManager->CapabilityAgent::handleDirective(MESSAGE_ID); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); +} + +/** + * Tests AdjustVolume Directive with persistent storage enabled. Expect that the volume is unmuted and adjusted, as well + * at most one event is sent. In the event there are no AVS_SPEAKER_VOLUME speakers registered, no event will be sent. + * In addition, only AVS_SPEAKER_VOLUME speakers should be affected. + */ +TEST_P(SpeakerManagerTest, test_adjustVolumeDirectiveWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + std::vector> groupVec; + int eventsSent = 0; + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; + // Create Speaker objects. for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); @@ -1313,7 +2400,13 @@ TEST_P(SpeakerManagerTest, test_adjustVolumeDirective) { .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); m_speakerManager->addSpeakerManagerObserver(m_observer); // Create Directive. @@ -1337,6 +2430,86 @@ TEST_P(SpeakerManagerTest, test_setMuteDirective) { int eventsSent = 0; SpeakerInterface::SpeakerSettings expectedSettings{DEFAULT_SETTINGS.volume, MUTE}; + // Create Speaker objects. + for (auto& typeOfSpeaker : GetParam()) { + auto group = std::make_shared>(typeOfSpeaker); + group->DelegateToReal(); + int timesCalled = 0; + if (typeOfSpeaker == ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME) { + timesCalled = 1; + } + + EXPECT_CALL(*group, setMute(_)).Times(Exactly(0)); + EXPECT_CALL(*group, setMute(MUTE)).Times(Exactly(timesCalled)); + + groupVec.push_back(group); + } + + auto uniqueTypes = getUniqueTypes(groupVec); + + // Creation expectations based on type. + if (uniqueTypes.count(ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME)) { + eventsSent = 1; + + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged( + SpeakerManagerObserverInterface::Source::DIRECTIVE, + ChannelVolumeInterface::Type::AVS_SPEAKER_VOLUME, + expectedSettings)) + .Times(Exactly(1)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)).Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } else { + EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)).Times(Exactly(0)); + } + + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(eventsSent)); + EXPECT_CALL(*(m_mockDirectiveHandlerResult.get()), setCompleted()) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); + + m_speakerManager = SpeakerManager::create( + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); + m_speakerManager->addSpeakerManagerObserver(m_observer); + + // Create Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = std::make_shared(SET_MUTE.nameSpace, SET_MUTE.name, MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, MUTE_PAYLOAD, attachmentManager, ""); + + m_speakerManager->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + m_speakerManager->CapabilityAgent::handleDirective(MESSAGE_ID); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); +} + +/** + * Tests SetMute Directive with persistent storage enabled. Expect that the volume is muted, as well at most one + * event is sent. In the event there are no AVS_SPEAKER_VOLUME speakers registered, no event will be sent. + * In addition, only AVS_SPEAKER_VOLUME speakers should be affected. + */ +TEST_P(SpeakerManagerTest, test_setMuteDirectiveWithPersistentStorage) { + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + + std::vector> groupVec; + int eventsSent = 0; + SpeakerInterface::SpeakerSettings expectedSettings{DEFAULT_SETTINGS.volume, MUTE}; + // Create Speaker objects. for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); @@ -1381,7 +2554,13 @@ TEST_P(SpeakerManagerTest, test_setMuteDirective) { .WillOnce(InvokeWithoutArgs(this, &SpeakerManagerTest::wakeOnSetCompleted)); m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); m_speakerManager->addSpeakerManagerObserver(m_observer); // Create Directive. @@ -1411,7 +2590,13 @@ TEST_P(SpeakerManagerTest, test_setVolumeDirectiveWhenMuted) { } m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); for (auto& group : groupVec) { auto mockGroup = std::dynamic_pointer_cast>(group); @@ -1492,6 +2677,12 @@ TEST_P(SpeakerManagerTest, test_getSpeakerConfigDefaults) { std::vector> groupVec; std::set uniqueTypes; + // Enable Persistent Storage Setting. + EXPECT_CALL(*m_mockConfig, getPersistentStorage(_)).Times(1).WillOnce(Invoke([](bool& persistentStorage) { + persistentStorage = true; + return true; + })); + for (auto& typeOfSpeaker : GetParam()) { auto group = std::make_shared>(typeOfSpeaker); group->DelegateToReal(); @@ -1508,7 +2699,13 @@ TEST_P(SpeakerManagerTest, test_getSpeakerConfigDefaults) { m_mockStorage->setFailureMode(); m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -1562,7 +2759,13 @@ TEST_P(SpeakerManagerTest, test_getSpeakerConfigFromStorage) { m_mockStorage->setDefaults(); m_speakerManager = SpeakerManager::create( - m_mockStorage, groupVec, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender, m_metricRecorder); + m_mockConfig, + m_mockStorage, + groupVec, + m_mockContextManager, + m_mockMessageSender, + m_mockExceptionSender, + m_metricRecorder); EXPECT_CALL(*m_observer, onSpeakerSettingsChanged(_, _, _)).Times(Exactly(0)); m_speakerManager->addSpeakerManagerObserver(m_observer); @@ -1588,5 +2791,4 @@ TEST_P(SpeakerManagerTest, test_getSpeakerConfigFromStorage) { } // namespace test } // namespace speakerManager -} // namespace capabilityAgents -} // namespace alexaClientSDK \ No newline at end of file +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerTest.dox b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerTest.dox new file mode 100644 index 0000000000..961c63dd42 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/SpeakerManagerTest.dox @@ -0,0 +1,34 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * @defgroup Test_acsdkSpeakerManager Unit tests for Speaker API Capability Agent. + * @brief Capability Agent unit tests. + * + * @see alexaClientSDK::speakerManager + * @see alexaClientSDK::speakerManager::test + * + * @ingroup Lib_acsdkSpeakerManager + */ + +/** + * @defgroup Lib_acsdkSpeakerManagerTestLib Test mocks for speaker manager interfaces. + * @brief Capability Agent unit tests. + * + * @see alexaClientSDK::speakerManager + * @see alexaClientSDK::speakerManager::test + * + * @ingroup Lib_acsdkSpeakerManager + */ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerConfig.h b/CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerConfig.h new file mode 100644 index 0000000000..9901fab15d --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerConfig.h @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_TEST_INCLUDE_ACSDK_SPEAKERMANAGER_TEST_MOCKSPEAKERMANAGERCONFIG_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_TEST_INCLUDE_ACSDK_SPEAKERMANAGER_TEST_MOCKSPEAKERMANAGERCONFIG_H_ + +#include + +namespace alexaClientSDK { +namespace speakerManager { +namespace test { + +/** + * @brief Mock object for @a SpeakerManagerConfigInterface. + * + * @see SpeakerManagerConfigInterface + * @ingroup Lib_acsdkSpeakerManagerTestLib + */ +class MockSpeakerManagerConfig : public SpeakerManagerConfigInterface { +public: + MOCK_NOEXCEPT_METHOD1(getPersistentStorage, bool(bool&)); + MOCK_NOEXCEPT_METHOD1(getMinUnmuteVolume, bool(std::uint8_t&)); + MOCK_NOEXCEPT_METHOD1(getRestoreMuteState, bool(bool&)); + MOCK_NOEXCEPT_METHOD1(getDefaultSpeakerVolume, bool(std::uint8_t&)); + MOCK_NOEXCEPT_METHOD1(getDefaultAlertsVolume, bool(std::uint8_t&)); +}; + +} // namespace test +} // namespace speakerManager +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_TEST_INCLUDE_ACSDK_SPEAKERMANAGER_TEST_MOCKSPEAKERMANAGERCONFIG_H_ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerObserver.h b/CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerObserver.h new file mode 100644 index 0000000000..c30213f867 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerObserver.h @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_TEST_INCLUDE_ACSDK_SPEAKERMANAGER_TEST_MOCKSPEAKERMANAGEROBSERVER_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_TEST_INCLUDE_ACSDK_SPEAKERMANAGER_TEST_MOCKSPEAKERMANAGEROBSERVER_H_ + +#include +#include + +namespace alexaClientSDK { +namespace speakerManager { +namespace test { + +/** + * @brief Mock object for @a SpeakerManagerObserverInterface. + * + * @see avsCommon::sdkInterfaces::SpeakerManagerObserverInterface + * @ingroup Lib_acsdkSpeakerManagerTestLib + */ +class MockSpeakerManagerObserver : public avsCommon::sdkInterfaces::SpeakerManagerObserverInterface { +public: + MOCK_METHOD3( + onSpeakerSettingsChanged, + void( + const Source&, + const avsCommon::sdkInterfaces::ChannelVolumeInterface::Type&, + const avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings&)); +}; + +} // namespace test +} // namespace speakerManager +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_TEST_INCLUDE_ACSDK_SPEAKERMANAGER_TEST_MOCKSPEAKERMANAGEROBSERVER_H_ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerStorage.h b/CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerStorage.h new file mode 100644 index 0000000000..b770141df1 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManager/test/include/acsdk/SpeakerManager/test/MockSpeakerManagerStorage.h @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_TEST_INCLUDE_ACSDK_SPEAKERMANAGER_TEST_MOCKSPEAKERMANAGERSTORAGE_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_TEST_INCLUDE_ACSDK_SPEAKERMANAGER_TEST_MOCKSPEAKERMANAGERSTORAGE_H_ + +#include + +namespace alexaClientSDK { +namespace speakerManager { +namespace test { + +/** + * @brief Mock object for @a SpeakerManagerStorageInterface. + * + * @see SpeakerManagerStorageInterface + * @ingroup Lib_acsdkSpeakerManagerTestLib + */ +class MockSpeakerManagerStorage : public SpeakerManagerStorageInterface { +public: + MOCK_NOEXCEPT_METHOD1(loadState, bool(SpeakerManagerStorageState&)); + MOCK_NOEXCEPT_METHOD1(saveState, bool(const SpeakerManagerStorageState&)); +}; + +} // namespace test +} // namespace speakerManager +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGER_TEST_INCLUDE_ACSDK_SPEAKERMANAGER_TEST_MOCKSPEAKERMANAGERSTORAGE_H_ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/CMakeLists.txt b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/CMakeLists.txt new file mode 100644 index 0000000000..89b9b5e934 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.0) +project(acsdkSpeakerManagerComponent LANGUAGES CXX) + +add_subdirectory("src") diff --git a/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/doc/Namespaces.dox b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/doc/Namespaces.dox new file mode 100644 index 0000000000..5b243b21c3 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/doc/Namespaces.dox @@ -0,0 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * @module Lib_acsdkSpeakerManager Speaker Manager Capability Agent Components + * + * @namespace alexaClientSDK::speakerManagerComponent + * @brief Speaker Manager Capability Agent Components + * + * This namespace contains factory methods for Speaker Manager components. + * + * @ingroup Lib_acsdkSpeakerManagerComponent + */ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/doc/SpeakerManagerComponent.dox b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/doc/SpeakerManagerComponent.dox new file mode 100644 index 0000000000..b750a0bfd4 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/doc/SpeakerManagerComponent.dox @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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. + */ + +/** + * @defgroup Lib_acsdkSpeakerManagerComponent Speaker Capability Agent Components + * @brief Manufactory support for Speaker capability agent components. + * + * Speaker manager contains the following components: + * - Speaker manager capability agent + * - Speaker manager configuration integration + * - Speaker manager storage integration + * - Channel volume factory + * + * @ingroup alexaClientSDK::speakerManager + */ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/include/acsdk/SpeakerManager/ChannelVolumeFactoryComponent.h b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/include/acsdk/SpeakerManager/ChannelVolumeFactoryComponent.h new file mode 100644 index 0000000000..50d92f87af --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/include/acsdk/SpeakerManager/ChannelVolumeFactoryComponent.h @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_INCLUDE_ACSDK_SPEAKERMANAGER_CHANNELVOLUMEFACTORYCOMPONENT_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_INCLUDE_ACSDK_SPEAKERMANAGER_CHANNELVOLUMEFACTORYCOMPONENT_H_ + +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace speakerManagerComponent { + +/// @addtogroup Lib_acsdkSpeakerManagerComponent +/// @{ + +/** + * @brief Component for ChannelVolumeFactoryInterface. + * + * Definition of a Manufactory component for the ChannelVolumeFactoryInterface. + */ +using ChannelVolumeFactoryComponent = + acsdkManufactory::Component>; + +/** + * @brief Create component for ChannelVolumeFactoryInterface. + * + * Creates an manufactory component that exports a shared pointer to an implementation of @c + * ChannelVolumeFactoryInterface. + * + * @return A component. + */ +ChannelVolumeFactoryComponent getChannelVolumeFactoryComponent(); + +/// @} + +} // namespace speakerManagerComponent +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_INCLUDE_ACSDK_SPEAKERMANAGER_CHANNELVOLUMEFACTORYCOMPONENT_H_ diff --git a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerComponent.h b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/include/acsdk/SpeakerManager/SpeakerManagerComponent.h similarity index 73% rename from CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerComponent.h rename to CapabilityAgents/SpeakerManager/SpeakerManagerComponent/include/acsdk/SpeakerManager/SpeakerManagerComponent.h index 6321b07c58..a082cf2952 100644 --- a/CapabilityAgents/SpeakerManager/include/SpeakerManager/SpeakerManagerComponent.h +++ b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/include/acsdk/SpeakerManager/SpeakerManagerComponent.h @@ -13,8 +13,8 @@ * permissions and limitations under the License. */ -#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_H_ -#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_H_ +#ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_H_ +#define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_H_ #include #include @@ -22,22 +22,25 @@ #include #include #include -#include #include #include #include -#include #include #include #include #include +#include namespace alexaClientSDK { -namespace capabilityAgents { -namespace speakerManager { +namespace speakerManagerComponent { + +/// @addtogroup Lib_acsdkSpeakerManagerComponent +/// @{ /** - * Definition of a Manufactory component for the SpeakerManager. + * @brief Component for @c SpeakerManagerInterface. + * + * Definition of a Manufactory component for the avsCommon::sdkInterfaces::SpeakerManagerInterface. */ using SpeakerManagerComponent = acsdkManufactory::Component< std::shared_ptr, @@ -52,14 +55,17 @@ using SpeakerManagerComponent = acsdkManufactory::Component< acsdkManufactory::Import>>; /** + * @brief Create component for @c SpeakerManagerInterface. + * * Creates an manufactory component that exports a shared pointer to an implementation of @c SpeakerManagerInterface. * * @return A component. */ -SpeakerManagerComponent getComponent(); +SpeakerManagerComponent getSpeakerManagerComponent() noexcept; + +/// @} -} // namespace speakerManager -} // namespace capabilityAgents +} // namespace speakerManagerComponent } // namespace alexaClientSDK -#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_INCLUDE_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_H_ +#endif // ALEXA_CLIENT_SDK_CAPABILITYAGENTS_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_INCLUDE_ACSDK_SPEAKERMANAGER_SPEAKERMANAGERCOMPONENT_H_ diff --git a/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/CMakeLists.txt b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/CMakeLists.txt new file mode 100644 index 0000000000..6d26f9a7d3 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/CMakeLists.txt @@ -0,0 +1,10 @@ +add_definitions("-DACSDK_LOG_MODULE=acsdkSpeakerManagerComponent") + +add_library(acsdkSpeakerManagerComponent ChannelVolumeFactoryComponent.cpp SpeakerManagerComponent.cpp) + +target_include_directories(acsdkSpeakerManagerComponent PUBLIC "${acsdkSpeakerManagerComponent_SOURCE_DIR}/include") + +target_link_libraries(acsdkSpeakerManagerComponent acsdkSpeakerManager acsdkManufactory) + +# install target +asdk_install() diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManagerComponent.cpp b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/ChannelVolumeFactoryComponent.cpp similarity index 63% rename from CapabilityAgents/SpeakerManager/src/SpeakerManagerComponent.cpp rename to CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/ChannelVolumeFactoryComponent.cpp index 12b851c9f8..1220c0fec1 100644 --- a/CapabilityAgents/SpeakerManager/src/SpeakerManagerComponent.cpp +++ b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/ChannelVolumeFactoryComponent.cpp @@ -15,20 +15,18 @@ #include -#include "SpeakerManager/SpeakerManager.h" -#include "SpeakerManager/SpeakerManagerComponent.h" +#include +#include namespace alexaClientSDK { -namespace capabilityAgents { -namespace speakerManager { +namespace speakerManagerComponent { using namespace acsdkManufactory; -using namespace capabilityAgents::speakerManager; +using namespace speakerManager; -SpeakerManagerComponent getComponent() { - return ComponentAccumulator<>().addRequiredFactory(SpeakerManager::createSpeakerManagerCapabilityAgent); +ChannelVolumeFactoryComponent getChannelVolumeFactoryComponent() { + return ComponentAccumulator<>().addRetainedFactory(createChannelVolumeFactory); } -} // namespace speakerManager -} // namespace capabilityAgents +} // namespace speakerManagerComponent } // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/SpeakerManagerComponent.cpp b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/SpeakerManagerComponent.cpp new file mode 100644 index 0000000000..daf760c0e6 --- /dev/null +++ b/CapabilityAgents/SpeakerManager/SpeakerManagerComponent/src/SpeakerManagerComponent.cpp @@ -0,0 +1,80 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All 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 speakerManagerComponent { + +using namespace acsdkManufactory; +using namespace speakerManager; + +/** + * @brief Helper for manufactory. + * + * This method uses annotated types for correct interface lookup in manufactory. + * + * @param[in] storage A @c MiscStorageInterface to access persistent configuration. + * @param[in] contextManager A @c ContextManagerInterface to manage the context. + * @param[in] messageSender A @c MessageSenderInterface to send messages to AVS. + * @param[in] exceptionEncounteredSender An @c ExceptionEncounteredSenderInterface to send directive processing + * exceptions to AVS. + * @param[in] shutdownNotifier A @c ShutdownNotifierInterface to notify the SpeakerManager when it's time + * to shut down. + * @param[in] endpointCapabilitiesRegistrar The @c EndpointCapabilitiesRegistrarInterface for the default endpoint + * (annotated with DefaultEndpointAnnotation), so that the SpeakerManager can register itself as a capability + * with the default endpoint. + * @param[in] metricRecorder The metric recorder. + * + * @return Reference to SpeakerManager CA or nullptr on error. + * + * @see createSpeakerManagerCapabilityAgent() + * @private + * @ingroup Lib_acsdkSpeakerManagerComponent + */ +static std::shared_ptr createSpeakerManagerCA( + std::shared_ptr config, + std::shared_ptr storage, + std::shared_ptr contextManager, + std::shared_ptr messageSender, + std::shared_ptr exceptionEncounteredSender, + const std::shared_ptr& shutdownNotifier, + const Annotated< + avsCommon::sdkInterfaces::endpoints::DefaultEndpointAnnotation, + avsCommon::sdkInterfaces::endpoints::EndpointCapabilitiesRegistrarInterface>& endpointCapabilitiesRegistrar, + std::shared_ptr metricRecorder) noexcept { + return createSpeakerManagerCapabilityAgent( + std::move(config), + std::move(storage), + std::move(contextManager), + std::move(messageSender), + std::move(exceptionEncounteredSender), + std::move(metricRecorder), + shutdownNotifier, + endpointCapabilitiesRegistrar); +} + +SpeakerManagerComponent getSpeakerManagerComponent() noexcept { + return ComponentAccumulator<>() + .addRequiredFactory(createSpeakerManagerCA) + .addRequiredFactory(createSpeakerManagerStorage) + .addRequiredFactory(createSpeakerManagerConfig); +} + +} // namespace speakerManagerComponent +} // namespace alexaClientSDK diff --git a/CapabilityAgents/SpeakerManager/src/CMakeLists.txt b/CapabilityAgents/SpeakerManager/src/CMakeLists.txt deleted file mode 100644 index 565e2341b0..0000000000 --- a/CapabilityAgents/SpeakerManager/src/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -add_definitions("-DACSDK_LOG_MODULE=speakerManager") - -add_library(SpeakerManager SpeakerManager.cpp - ChannelVolumeManager.cpp - DefaultChannelVolumeFactory.cpp - SpeakerManagerComponent.cpp - SpeakerManagerMiscStorage.cpp - SpeakerManagerConfigHelper.cpp) - -target_include_directories(SpeakerManager PUBLIC - "${ContextManager_INCLUDE_DIRS}" - "${SpeakerManager_SOURCE_DIR}/include") - -target_link_libraries(SpeakerManager AVSCommon acsdkManufactory acsdkShutdownManagerInterfaces Endpoints) - -# install target -asdk_install() diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManagerConfigHelper.cpp b/CapabilityAgents/SpeakerManager/src/SpeakerManagerConfigHelper.cpp deleted file mode 100644 index 56e400e48a..0000000000 --- a/CapabilityAgents/SpeakerManager/src/SpeakerManagerConfigHelper.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 - -using namespace alexaClientSDK::avsCommon::sdkInterfaces; -using namespace alexaClientSDK::avsCommon::sdkInterfaces::storage; -using namespace alexaClientSDK::avsCommon::avs::speakerConstants; -using namespace alexaClientSDK::capabilityAgents::speakerManager; -using namespace alexaClientSDK::avsCommon::utils::configuration; - -/// String to identify log entries originating from this file. -static const std::string TAG{"SpeakerManagerConfigHelper"}; - -/** - * Create a LogEntry using this file's TAG and the specified event string. - * - * @param TAG Component tag. - * @param evemt The event string for this @c LogEntry. - */ -#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) - -/// The key in our config file to find the root of speaker manager configuration. -static const std::string SPEAKERMANAGER_CONFIGURATION_ROOT_KEY = "speakerManagerCapabilityAgent"; -/// The key in our config file to find the minUnmuteVolume value. -static const std::string SPEAKERMANAGER_MIN_UNMUTE_VOLUME_KEY = "minUnmuteVolume"; -/// The key in our config file to find the defaultSpeakerVolume value. -static const std::string SPEAKERMANAGER_DEFAULT_SPEAKER_VOLUME_KEY = "defaultSpeakerVolume"; -/// The key in our config file to find the defaultAlertsVolume value. -static const std::string SPEAKERMANAGER_DEFAULT_ALERTS_VOLUME_KEY = "defaultAlertsVolume"; -/// The key in our config file to find mute status keep flag -static const std::string SPEAKERMANAGER_RESTORE_MUTE_STATE_KEY = "restoreMuteState"; - -const SpeakerManagerStorageState SpeakerManagerConfigHelper::c_defaults = {{DEFAULT_SPEAKER_VOLUME, false}, - {DEFAULT_ALERTS_VOLUME, false}}; - -SpeakerManagerConfigHelper::SpeakerManagerConfigHelper(const std::shared_ptr& storage) : - m_storage(storage) { -} - -int SpeakerManagerConfigHelper::getMinUnmuteVolume() const { - int minUnmuteVolume = MIN_UNMUTE_VOLUME; - - auto node = ConfigurationNode::getRoot()[SPEAKERMANAGER_CONFIGURATION_ROOT_KEY]; - // If key is present, then read and initialize the value from config or set to default. - node.getInt(SPEAKERMANAGER_MIN_UNMUTE_VOLUME_KEY, &minUnmuteVolume, MIN_UNMUTE_VOLUME); - - return minUnmuteVolume; -} - -void SpeakerManagerConfigHelper::loadState(SpeakerManagerStorageState& state) { - if (!m_storage->loadState(state) && !loadStateFromConfig(state)) { - loadHardcodedState(state); - } -} - -bool SpeakerManagerConfigHelper::saveState(const SpeakerManagerStorageState& state) { - return m_storage->saveState(state); -} - -bool SpeakerManagerConfigHelper::loadStateFromConfig(SpeakerManagerStorageState& state) { - int speakerVolume = 0; - int alertsVolume = 0; - - auto node = ConfigurationNode::getRoot()[SPEAKERMANAGER_CONFIGURATION_ROOT_KEY]; - - if (node.getInt(SPEAKERMANAGER_DEFAULT_SPEAKER_VOLUME_KEY, &speakerVolume) && - node.getInt(SPEAKERMANAGER_DEFAULT_ALERTS_VOLUME_KEY, &alertsVolume)) { - state.speakerChannelState.channelMuteStatus = false; - state.speakerChannelState.channelVolume = speakerVolume; - state.alertsChannelState.channelMuteStatus = false; - state.alertsChannelState.channelVolume = alertsVolume; - - return true; - } - return false; -} - -void SpeakerManagerConfigHelper::loadHardcodedState(SpeakerManagerStorageState& state) { - state = c_defaults; -} - -bool SpeakerManagerConfigHelper::getRestoreMuteState() const { - auto node = ConfigurationNode::getRoot()[SPEAKERMANAGER_CONFIGURATION_ROOT_KEY]; - bool result = false; - if (node.getBool(SPEAKERMANAGER_RESTORE_MUTE_STATE_KEY, &result)) { - return result; - } else { - return true; - } -} diff --git a/CapabilityAgents/SpeakerManager/test/CMakeLists.txt b/CapabilityAgents/SpeakerManager/test/CMakeLists.txt deleted file mode 100644 index 6ab01b8b35..0000000000 --- a/CapabilityAgents/SpeakerManager/test/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) - -set(INCLUDE_PATH - "${SpeakerManager_INCLUDE_DIRS}" - "${AVSCommon_SOURCE_DIR}/AVS/test") - -discover_unit_tests("${INCLUDE_PATH}" "SpeakerManager;UtilsCommonTestLib;SDKInterfacesTests") diff --git a/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp b/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp index dfb53ab5cf..bb0ee35b78 100644 --- a/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp +++ b/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp @@ -52,7 +52,7 @@ static const std::string SPEECHSYNTHESIZER_CAPABILITY_INTERFACE_NAME = "SpeechSy static const std::string SPEECHSYNTHESIZER_CAPABILITY_INTERFACE_VERSION = "1.3"; /// String to identify log entries originating from this file. -static const std::string TAG{"SpeechSynthesizer"}; +#define TAG "SpeechSynthesizer" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -261,7 +261,7 @@ avsCommon::avs::DirectiveHandlerConfiguration SpeechSynthesizer::getConfiguratio void SpeechSynthesizer::addObserver(std::shared_ptr observer) { ACSDK_DEBUG9(LX("addObserver").d("observer", observer.get())); - m_executor.submit([this, observer]() { m_observers.insert(observer); }); + m_executor.execute([this, observer]() { m_observers.insert(observer); }); } void SpeechSynthesizer::removeObserver(std::shared_ptr observer) { @@ -277,7 +277,7 @@ void SpeechSynthesizer::onDeregistered() { void SpeechSynthesizer::handleDirectiveImmediately(std::shared_ptr directive) { ACSDK_DEBUG9(LX("handleDirectiveImmediately").d("messageId", directive->getMessageId())); auto info = createDirectiveInfo(directive, nullptr); - m_executor.submit([this, info]() { executeHandleImmediately(info); }); + m_executor.execute([this, info]() { executeHandleImmediately(info); }); } void SpeechSynthesizer::preHandleDirective(std::shared_ptr info) { @@ -287,7 +287,7 @@ void SpeechSynthesizer::preHandleDirective(std::shared_ptr info) } ACSDK_DEBUG9(LX("preHandleDirective").d("messageId", info->directive->getMessageId())); - m_executor.submit([this, info]() { executePreHandle(info); }); + m_executor.execute([this, info]() { executePreHandle(info); }); } void SpeechSynthesizer::handleDirective(std::shared_ptr info) { @@ -300,13 +300,13 @@ void SpeechSynthesizer::handleDirective(std::shared_ptr info) { if (info->directive->getName() == "Speak") { ACSDK_METRIC_MSG(TAG, info->directive, Metrics::Location::SPEECH_SYNTHESIZER_RECEIVE); } - m_executor.submit([this, info]() { executeHandle(info); }); + m_executor.execute([this, info]() { executeHandle(info); }); } void SpeechSynthesizer::cancelDirective(std::shared_ptr info) { if (info && info->directive) { ACSDK_DEBUG9(LX("cancelDirective").d("messageId", info->directive->getMessageId())); - m_executor.submit([this, info]() { executeCancel(info); }); + m_executor.execute([this, info]() { executeCancel(info); }); } else { ACSDK_WARN(LX("cancelDirective").d("reason", "infoNotAvailable")); } @@ -340,7 +340,7 @@ void SpeechSynthesizer::onFocusChanged(FocusState newFocus, MixingBehavior behav } auto currentInfo = std::make_shared>(nullptr); - m_executor.submit([this, desiredState, currentInfo]() { + m_executor.execute([this, desiredState, currentInfo]() { *currentInfo = m_currentInfo; executeStateChange(desiredState); }); @@ -354,7 +354,7 @@ void SpeechSynthesizer::onFocusChanged(FocusState newFocus, MixingBehavior behav .d("initialDesiredState", desiredState) .d("desiredState", m_desiredState) .d("currentState", m_currentState)); - m_executor.submit([this, currentInfo]() { + m_executor.execute([this, currentInfo]() { ACSDK_DEBUG9( LX("onFocusChangedLambda") .d("currentInfo", @@ -377,7 +377,7 @@ void SpeechSynthesizer::provideState( const avsCommon::avs::NamespaceAndName& stateProviderName, const unsigned int stateRequestToken) { ACSDK_DEBUG9(LX("provideState").d("token", stateRequestToken)); - m_executor.submit([this, stateRequestToken]() { + m_executor.execute([this, stateRequestToken]() { std::lock_guard lock(m_mutex); executeProvideStateLocked(stateRequestToken); }); @@ -404,7 +404,7 @@ void SpeechSynthesizer::onPlaybackStarted(SourceId id, const MediaPlayerState&) ACSDK_DEBUG9(LX("onPlaybackStarted").d("callbackSourceId", id)); ACSDK_METRIC_IDS(TAG, "SpeechStarted", "", "", Metrics::Location::SPEECH_SYNTHESIZER_RECEIVE); - m_executor.submit([this, id] { + m_executor.execute([this, id] { if (id != m_mediaSourceId) { ACSDK_ERROR(LX("queueingExecutePlaybackStartedFailed") .d("reason", "mismatchSourceId") @@ -429,7 +429,7 @@ void SpeechSynthesizer::onPlaybackFinished(SourceId id, const MediaPlayerState&) ACSDK_DEBUG9(LX("onPlaybackFinished").d("callbackSourceId", id)); ACSDK_METRIC_IDS(TAG, "SpeechFinished", "", "", Metrics::Location::SPEECH_SYNTHESIZER_RECEIVE); - m_executor.submit([this, id] { + m_executor.execute([this, id] { if (id != m_mediaSourceId) { ACSDK_ERROR(LX("queueingExecutePlaybackFinishedFailed") .d("reason", "mismatchSourceId") @@ -456,13 +456,13 @@ void SpeechSynthesizer::onPlaybackError( std::string error, const MediaPlayerState&) { ACSDK_DEBUG9(LX("onPlaybackError").d("callbackSourceId", id)); - m_executor.submit([this, type, error]() { executePlaybackError(type, error); }); + m_executor.execute([this, type, error]() { executePlaybackError(type, error); }); } void SpeechSynthesizer::onPlaybackStopped(SourceId id, const MediaPlayerState&) { ACSDK_DEBUG9(LX("onPlaybackStopped").d("callbackSourceId", id)); - m_executor.submit([this, id]() { executePlaybackStopped(id); }); + m_executor.execute([this, id]() { executePlaybackStopped(id); }); } void SpeechSynthesizer::onBufferUnderrun(SourceId id, const MediaPlayerState&) { @@ -658,6 +658,7 @@ void SpeechSynthesizer::executePreHandleAfterValidation(std::shared_ptr, AVSDirective::ParseStatus> directivePair = + AVSDirective::create(UNPARSED_DIRECTIVE_TEST, nullptr, CONTEXT_ID_TEST); + + std::shared_ptr directiveOne = std::move(directivePair.first); + { + SCOPED_TRACE("Empty attachment manager directive."); + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) + .Times(0) + .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); + EXPECT_CALL( + *(m_mockSpeechPlayer.get()), + attachmentSetSource(A>(), nullptr)) + .Times(0); + EXPECT_CALL(*(m_mockSpeechPlayer.get()), play(_)).Times(0); + + m_speechSynthesizer->handleDirectiveImmediately(directiveOne); + } + + auto avsMessageHeader = std::make_shared( + NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_TEST_2, DIALOG_REQUEST_ID_TEST); + std::shared_ptr directiveTwo = + AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST); + { + SCOPED_TRACE("Check success after failed."); + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _)) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel)); + EXPECT_CALL( + *(m_mockSpeechPlayer.get()), + attachmentSetSource(A>(), nullptr)) + .Times(AtLeast(1)); + EXPECT_CALL(*(m_mockSpeechPlayer.get()), play(_)).Times(AtLeast(1)); + EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_)) + .Times(1) + .WillOnce(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST)); + EXPECT_CALL(*(m_mockSpeechPlayer.get()), getMediaPlayerState(_)).Times(AtLeast(2)); + EXPECT_CALL( + *(m_mockContextManager.get()), + setState(NAMESPACE_AND_NAME_SPEECH_STATE, PLAYING_STATE_TEST, StateRefreshPolicy::ALWAYS, 0)) + .Times(AtLeast(1)) + .WillOnce(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState)); + EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_)) + .Times(AtLeast(1)) + .WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage)); + EXPECT_CALL(*m_mockCaptionManager, isEnabled()).WillRepeatedly(Return(true)); + EXPECT_CALL(*m_mockCaptionManager, onCaption(_, _)).Times(1); + EXPECT_CALL(*m_mockPowerResourceManager, acquire(_, _)).Times(AtLeast(1)); + + std::vector data; + EXPECT_CALL( + *m_mockSpeechSynthesizerObserver, + onStateChanged(SpeechSynthesizerObserverInterface::SpeechSynthesizerState::GAINING_FOCUS, _, _, _)) + .Times(1); + EXPECT_CALL( + *m_mockSpeechSynthesizerObserver, + onStateChanged(SpeechSynthesizerObserverInterface::SpeechSynthesizerState::PLAYING, _, _, Eq(data))) + .Times(1); + + m_speechSynthesizer->addObserver(m_mockSpeechSynthesizerObserver); + m_speechSynthesizer->handleDirectiveImmediately(directiveTwo); + ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(MY_WAIT_TIMEOUT)); + m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND, MixingBehavior::PRIMARY); + ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(MY_WAIT_TIMEOUT)); + m_wakeSetStatePromise = std::promise(); + m_wakeSetStateFuture = m_wakeSetStatePromise.get_future(); + ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted()); + } +} + } // namespace test } // namespace speechSynthesizer } // namespace capabilityAgents -} // namespace alexaClientSDK +} // namespace alexaClientSDK \ No newline at end of file diff --git a/CapabilityAgents/System/src/LocaleHandler.cpp b/CapabilityAgents/System/src/LocaleHandler.cpp index a850e8dcf4..42b378e85e 100644 --- a/CapabilityAgents/System/src/LocaleHandler.cpp +++ b/CapabilityAgents/System/src/LocaleHandler.cpp @@ -36,7 +36,7 @@ using namespace avsCommon::utils::logger; using namespace settings; /// String to identify log entries originating from this file. -static const std::string TAG("LocaleHandler"); +#define TAG "LocaleHandler" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -115,7 +115,7 @@ void LocaleHandler::handleDirective(std::shared_ptr info) { diff --git a/CapabilityAgents/System/src/ReportStateHandler.cpp b/CapabilityAgents/System/src/ReportStateHandler.cpp index e361dad64c..974d6590f0 100644 --- a/CapabilityAgents/System/src/ReportStateHandler.cpp +++ b/CapabilityAgents/System/src/ReportStateHandler.cpp @@ -31,7 +31,7 @@ using namespace avsCommon::utils::error; using namespace settings; /// String to identify log entries originating from this file. -static const std::string TAG("ReportStateHandler"); +#define TAG "ReportStateHandler" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -171,7 +171,7 @@ void ReportStateHandler::handleDirectiveImmediately(std::shared_ptr info) { @@ -185,7 +185,7 @@ void ReportStateHandler::handleDirective(std::shared_ptrdirective)); if (info->result) { if (ok) { @@ -241,7 +241,7 @@ void ReportStateHandler::initialize() { m_connectionObserver = SettingConnectionObserver::create([this](bool isConnected) { if (isConnected) { std::lock_guard lock(m_stateMutex); - m_executor.submit([this] { sendReportState(); }); + m_executor.execute([this] { sendReportState(); }); } }); m_connectionManager->addConnectionStatusObserver(m_connectionObserver); diff --git a/CapabilityAgents/System/src/RevokeAuthorizationHandler.cpp b/CapabilityAgents/System/src/RevokeAuthorizationHandler.cpp index c45de104e3..0686d5fada 100644 --- a/CapabilityAgents/System/src/RevokeAuthorizationHandler.cpp +++ b/CapabilityAgents/System/src/RevokeAuthorizationHandler.cpp @@ -31,7 +31,7 @@ using namespace avsCommon::utils::json; using namespace rapidjson; /// String to identify log entries originating from this file. -static const std::string TAG{"RevokeAuthorizationHandler"}; +#define TAG "RevokeAuthorizationHandler" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/System/src/SoftwareInfoSendRequest.cpp b/CapabilityAgents/System/src/SoftwareInfoSendRequest.cpp index f06c1b43d9..21ca43378e 100644 --- a/CapabilityAgents/System/src/SoftwareInfoSendRequest.cpp +++ b/CapabilityAgents/System/src/SoftwareInfoSendRequest.cpp @@ -40,7 +40,7 @@ using namespace avsCommon::utils::json; using namespace rapidjson; /// String to identify log entries originating from this file. -static const std::string TAG{"SoftwareInfoSendRequest"}; +#define TAG "SoftwareInfoSendRequest" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/System/src/SoftwareInfoSender.cpp b/CapabilityAgents/System/src/SoftwareInfoSender.cpp index 82ea66aa28..4589cdd6f6 100644 --- a/CapabilityAgents/System/src/SoftwareInfoSender.cpp +++ b/CapabilityAgents/System/src/SoftwareInfoSender.cpp @@ -34,7 +34,7 @@ using namespace avsCommon::utils::json; using namespace rapidjson; /// String to identify log entries originating from this file. -static const std::string TAG{"SoftwareInfoSender"}; +#define TAG "SoftwareInfoSender" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/System/src/StateReportGenerator.cpp b/CapabilityAgents/System/src/StateReportGenerator.cpp index 9d96882579..66f408b0c0 100644 --- a/CapabilityAgents/System/src/StateReportGenerator.cpp +++ b/CapabilityAgents/System/src/StateReportGenerator.cpp @@ -27,7 +27,7 @@ using namespace avsCommon::utils; using namespace avsCommon::utils::json; /// String to identify log entries originating from this file. -static const std::string TAG("StateReportGenerator"); +#define TAG "StateReportGenerator" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/System/src/SystemCapabilityProvider.cpp b/CapabilityAgents/System/src/SystemCapabilityProvider.cpp index 9748c8a857..f5a3611721 100644 --- a/CapabilityAgents/System/src/SystemCapabilityProvider.cpp +++ b/CapabilityAgents/System/src/SystemCapabilityProvider.cpp @@ -29,7 +29,7 @@ namespace system { using namespace avsCommon::avs; /// String to identify log entries originating from this file. -static const std::string TAG{"SystemCapabilityProvider"}; +#define TAG "SystemCapabilityProvider" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/System/src/TimeZoneHandler.cpp b/CapabilityAgents/System/src/TimeZoneHandler.cpp index 73c398d911..a21302208a 100644 --- a/CapabilityAgents/System/src/TimeZoneHandler.cpp +++ b/CapabilityAgents/System/src/TimeZoneHandler.cpp @@ -34,7 +34,7 @@ using namespace avsCommon::utils::logger; using namespace settings; /// String to identify log entries originating from this file. -static const std::string TAG("TimeZoneHandler"); +#define TAG "TimeZoneHandler" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -85,7 +85,7 @@ void TimeZoneHandler::handleDirectiveImmediately(std::shared_ptr d return; } auto info = createDirectiveInfo(directive, nullptr); - m_executor.submit([this, info]() { executeHandleDirectiveImmediately(info); }); + m_executor.execute([this, info]() { executeHandleDirectiveImmediately(info); }); } void TimeZoneHandler::preHandleDirective(std::shared_ptr info) { // intentional no-op @@ -95,7 +95,7 @@ void TimeZoneHandler::handleDirective(std::shared_ptr info) { // intentional no-op diff --git a/CapabilityAgents/System/src/UserInactivityMonitor.cpp b/CapabilityAgents/System/src/UserInactivityMonitor.cpp index 3d8ca625bf..3e052f33ad 100644 --- a/CapabilityAgents/System/src/UserInactivityMonitor.cpp +++ b/CapabilityAgents/System/src/UserInactivityMonitor.cpp @@ -34,7 +34,7 @@ using namespace avsCommon::utils::json; using namespace rapidjson; /// String to identify log entries originating from this file. -static const std::string TAG("UserInactivityMonitor"); +#define TAG "UserInactivityMonitor" /// Number of seconds in one hour. static const int SECONDS_IN_HOUR = 3600; @@ -147,7 +147,7 @@ void UserInactivityMonitor::sendInactivityReport() { return; } Document inactivityPayload(kObjectType); - SizeType payloadKeySize = INACTIVITY_EVENT_PAYLOAD_KEY.length(); + SizeType payloadKeySize = static_cast(INACTIVITY_EVENT_PAYLOAD_KEY.length()); const Pointer::Token payloadKey[] = {{INACTIVITY_EVENT_PAYLOAD_KEY.c_str(), payloadKeySize, kPointerInvalidIndex}}; // AVS requires inactivity time to be a multiple of 3600. diff --git a/CapabilityAgents/TemplateRuntime/src/CMakeLists.txt b/CapabilityAgents/TemplateRuntime/src/CMakeLists.txt deleted file mode 100644 index 8755e32d20..0000000000 --- a/CapabilityAgents/TemplateRuntime/src/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -add_definitions("-DACSDK_LOG_MODULE=templateRuntime") - -add_library(TemplateRuntime - "${CMAKE_CURRENT_LIST_DIR}/TemplateRuntime.cpp" - "${CMAKE_CURRENT_LIST_DIR}/RenderPlayerInfoCardsProviderRegistrar.cpp") - -target_include_directories(TemplateRuntime - PUBLIC "${TemplateRuntime_SOURCE_DIR}/include") - -target_link_libraries(TemplateRuntime AVSCommon) - -# install target -asdk_install() \ No newline at end of file diff --git a/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp b/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp deleted file mode 100644 index 5a19ef6994..0000000000 --- a/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp +++ /dev/null @@ -1,820 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "TemplateRuntime/TemplateRuntime.h" - -namespace alexaClientSDK { -namespace capabilityAgents { -namespace templateRuntime { - -using namespace avsCommon::avs; -using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::utils; -using namespace avsCommon::utils::configuration; -using namespace avsCommon::utils::json; - -/// TemplateRuntime capability constants -/// TemplateRuntime interface type -static const std::string TEMPLATERUNTIME_CAPABILITY_INTERFACE_TYPE = "AlexaInterface"; -/// TemplateRuntime interface name -static const std::string TEMPLATERUNTIME_CAPABILITY_INTERFACE_NAME = "TemplateRuntime"; -/// TemplateRuntime interface version -static const std::string TEMPLATERUNTIME_CAPABILITY_INTERFACE_VERSION = "1.1"; - -/// String to identify log entries originating from this file. -static const std::string TAG{"TemplateRuntime"}; - -/// The key in our config file to find the root of template runtime configuration. -static const std::string TEMPLATERUNTIME_CONFIGURATION_ROOT_KEY = "templateRuntimeCapabilityAgent"; -/// The key in our config file to set the display card timeout value when TTS is in FINISHED state -static const std::string TEMPLATERUNTIME_TTS_FINISHED_KEY = "displayCardTTSFinishedTimeout"; -/// The key in our config file to set the display card timeout value when AudioPlayer is in FINISHED state -static const std::string TEMPLATERUNTIME_AUDIOPLAYBACK_FINISHED_KEY = "displayCardAudioPlaybackFinishedTimeout"; -/// The key in our config file to set the display card timeout value when AudioPlayer is in STOPPED or PAUSE state -static const std::string TEMPLATERUNTIME_AUDIOPLAYBACK_STOPPED_PAUSED_KEY = - "displayCardAudioPlaybackStoppedPausedTimeout"; - -/** - * 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 name of the @c FocusManager channel used by @c TemplateRuntime. -static const std::string CHANNEL_NAME = avsCommon::sdkInterfaces::FocusManagerInterface::VISUAL_CHANNEL_NAME; - -/// The namespace for this capability agent. -static const std::string NAMESPACE{"TemplateRuntime"}; - -/// The name for RenderTemplate directive. -static const std::string RENDER_TEMPLATE{"RenderTemplate"}; - -/// The name for RenderPlayerInfo directive. -static const std::string RENDER_PLAYER_INFO{"RenderPlayerInfo"}; - -/// The RenderTemplate directive signature. -static const NamespaceAndName TEMPLATE{NAMESPACE, RENDER_TEMPLATE}; - -/// The RenderPlayerInfo directive signature. -static const NamespaceAndName PLAYER_INFO{NAMESPACE, RENDER_PLAYER_INFO}; - -/// Tag for find the AudioItemId in the payload of the RenderPlayerInfo directive -static const std::string AUDIO_ITEM_ID_TAG{"audioItemId"}; - -/// Maximum queue size allowed for m_audioItems. -static const size_t MAXIMUM_QUEUE_SIZE{100}; - -/// Default timeout for clearing the RenderTemplate display card when SpeechSynthesizer is in FINISHED state. -static const std::chrono::milliseconds DEFAULT_TTS_FINISHED_TIMEOUT_MS{2000}; - -/// Default timeout for clearing the RenderPlayerInfo display card when AudioPlayer is in FINISHED state. -static const std::chrono::milliseconds DEFAULT_AUDIO_FINISHED_TIMEOUT_MS{2000}; - -/// Default timeout for clearing the RenderPlayerInfo display card when AudioPlayer is in STOPPED/PAUSED state. -static const std::chrono::milliseconds DEFAULT_AUDIO_STOPPED_PAUSED_TIMEOUT_MS{60000}; - -/** - * Creates the TemplateRuntime capability configuration. - * - * @return The TemplateRuntime capability configuration. - */ -static std::shared_ptr getTemplateRuntimeCapabilityConfiguration(); - -std::shared_ptr TemplateRuntime::createTemplateRuntime( - const std::shared_ptr& - renderPlayerInfoCardsProviderRegistrar, - std::shared_ptr focusManager, - std::shared_ptr exceptionSender) { - if (!renderPlayerInfoCardsProviderRegistrar || !focusManager || !exceptionSender) { - ACSDK_ERROR(LX("createFailed") - .d("isRenderPlayerInfoCardsProviderRegistrarNull", !renderPlayerInfoCardsProviderRegistrar) - .d("isFocusManagerNull", !focusManager) - .d("isExceptionSenderNull", !exceptionSender)); - return nullptr; - } - - auto providers = renderPlayerInfoCardsProviderRegistrar->getProviders(); - return TemplateRuntime::create(providers, focusManager, exceptionSender); -} - -std::shared_ptr TemplateRuntime::create( - const std::unordered_set>& - renderPlayerInfoCardInterface, - std::shared_ptr focusManager, - std::shared_ptr exceptionSender) { - if (!focusManager) { - ACSDK_ERROR(LX("createFailed").d("reason", "nullFocusManager")); - return nullptr; - } - - if (!exceptionSender) { - ACSDK_ERROR(LX("createFailed").d("reason", "nullExceptionSender")); - return nullptr; - } - std::shared_ptr templateRuntime( - new TemplateRuntime(renderPlayerInfoCardInterface, focusManager, exceptionSender)); - - if (!templateRuntime->initialize()) { - ACSDK_ERROR(LX("createFailed").d("reason", "Initialization error.")); - return nullptr; - } - - for (const auto& renderPlayerInfoCardProvider : renderPlayerInfoCardInterface) { - if (!renderPlayerInfoCardProvider) { - ACSDK_ERROR(LX("createFailed").d("reason", "nullRenderPlayerInfoCardInterface")); - return nullptr; - } - renderPlayerInfoCardProvider->setObserver(templateRuntime); - } - - return templateRuntime; -} - -/** - * Initializes the object by reading the values from configuration. - */ -bool TemplateRuntime::initialize() { - auto configurationRoot = ConfigurationNode::getRoot()[TEMPLATERUNTIME_CONFIGURATION_ROOT_KEY]; - - // If key is present, then read and initilize the value from config or set to default. - configurationRoot.getDuration( - TEMPLATERUNTIME_TTS_FINISHED_KEY, &m_ttsFinishedTimeout, DEFAULT_TTS_FINISHED_TIMEOUT_MS); - - // If key is present, then read and initilize the value from config or set to default. - configurationRoot.getDuration( - TEMPLATERUNTIME_AUDIOPLAYBACK_FINISHED_KEY, &m_audioPlaybackFinishedTimeout, DEFAULT_AUDIO_FINISHED_TIMEOUT_MS); - - // If key is present, then read and initilize the value from config or set to default. - configurationRoot.getDuration( - TEMPLATERUNTIME_AUDIOPLAYBACK_STOPPED_PAUSED_KEY, - &m_audioPlaybackStoppedPausedTimeout, - DEFAULT_AUDIO_STOPPED_PAUSED_TIMEOUT_MS); - - return true; -} - -void TemplateRuntime::handleDirectiveImmediately(std::shared_ptr directive) { - ACSDK_DEBUG5(LX("handleDirectiveImmediately")); - handleDirective(std::make_shared(directive, nullptr)); -} - -void TemplateRuntime::preHandleDirective(std::shared_ptr info) { - ACSDK_DEBUG5(LX("preHandleDirective")); - // do nothing. -} - -void TemplateRuntime::handleDirective(std::shared_ptr info) { - ACSDK_DEBUG5(LX("handleDirective")); - if (!info || !info->directive) { - ACSDK_ERROR(LX("preHandleDirectiveFailed").d("reason", "nullDirectiveInfo")); - return; - } - if (info->directive->getName() == TEMPLATE.name) { - handleRenderTemplateDirective(info); - } else if (info->directive->getName() == PLAYER_INFO.name) { - handleRenderPlayerInfoDirective(info); - } else { - handleUnknownDirective(info); - } -} - -void TemplateRuntime::cancelDirective(std::shared_ptr info) { - removeDirective(info); -} - -DirectiveHandlerConfiguration TemplateRuntime::getConfiguration() const { - ACSDK_DEBUG5(LX("getConfiguration")); - DirectiveHandlerConfiguration configuration; - auto visualNonBlockingPolicy = BlockingPolicy(BlockingPolicy::MEDIUM_VISUAL, false); - - configuration[TEMPLATE] = visualNonBlockingPolicy; - configuration[PLAYER_INFO] = visualNonBlockingPolicy; - return configuration; -} - -void TemplateRuntime::onFocusChanged(avsCommon::avs::FocusState newFocus, MixingBehavior) { - m_executor.submit([this, newFocus]() { executeOnFocusChangedEvent(newFocus); }); -} - -void TemplateRuntime::onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity state, const Context& context) { - ACSDK_DEBUG5(LX("onRenderPlayerCardsInfoChanged")); - m_executor.submit([this, state, context]() { - ACSDK_DEBUG5(LX("onPlayerActivityChangedInExecutor")); - executeAudioPlayerInfoUpdates(state, context); - }); -} - -void TemplateRuntime::onDialogUXStateChanged( - avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState newState) { - ACSDK_DEBUG5(LX("onDialogUXStateChanged").d("state", newState)); - m_executor.submit([this, newState]() { - if (TemplateRuntime::State::DISPLAYING == m_state && m_lastDisplayedDirective && - m_lastDisplayedDirective->directive->getName() == RENDER_TEMPLATE) { - if (avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState::IDLE == newState) { - executeStartTimer(m_ttsFinishedTimeout); - } else if ( - avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState::EXPECTING == newState || - avsCommon::sdkInterfaces::DialogUXStateObserverInterface::DialogUXState::SPEAKING == newState) { - executeStopTimer(); - } - } - }); -} - -void TemplateRuntime::addObserver( - std::shared_ptr observer) { - ACSDK_DEBUG5(LX("addObserver")); - if (!observer) { - ACSDK_ERROR(LX("addObserver").m("Observer is null.")); - return; - } - m_executor.submit([this, observer]() { - ACSDK_DEBUG5(LX("addObserverInExecutor")); - if (!m_observers.insert(observer).second) { - ACSDK_ERROR(LX("addObserverInExecutor").m("Duplicate observer.")); - } - }); -} - -void TemplateRuntime::removeObserver( - std::shared_ptr observer) { - ACSDK_DEBUG5(LX("removeObserver")); - if (!observer) { - ACSDK_ERROR(LX("removeObserver").m("Observer is null.")); - return; - } - m_executor.submit([this, observer]() { - ACSDK_DEBUG5(LX("removeObserverInExecutor")); - if (m_observers.erase(observer) == 0) { - ACSDK_WARN(LX("removeObserverInExecutor").m("Nonexistent observer.")); - } - }); -} - -TemplateRuntime::TemplateRuntime( - const std::unordered_set>& - renderPlayerInfoCardsInterfaces, - std::shared_ptr focusManager, - std::shared_ptr exceptionSender) : - CapabilityAgent{NAMESPACE, exceptionSender}, - RequiresShutdown{"TemplateRuntime"}, - m_isRenderTemplateLastReceived{false}, - m_focus{FocusState::NONE}, - m_state{TemplateRuntime::State::IDLE}, - m_renderPlayerInfoCardsInterfaces{renderPlayerInfoCardsInterfaces}, - m_focusManager{focusManager} { - m_capabilityConfigurations.insert(getTemplateRuntimeCapabilityConfiguration()); -} - -std::shared_ptr getTemplateRuntimeCapabilityConfiguration() { - std::unordered_map configMap; - configMap.insert({CAPABILITY_INTERFACE_TYPE_KEY, TEMPLATERUNTIME_CAPABILITY_INTERFACE_TYPE}); - configMap.insert({CAPABILITY_INTERFACE_NAME_KEY, TEMPLATERUNTIME_CAPABILITY_INTERFACE_NAME}); - configMap.insert({CAPABILITY_INTERFACE_VERSION_KEY, TEMPLATERUNTIME_CAPABILITY_INTERFACE_VERSION}); - - return std::make_shared(configMap); -} - -void TemplateRuntime::doShutdown() { - m_executor.shutdown(); - m_focusManager.reset(); - m_observers.clear(); - m_activeRenderPlayerInfoCardsProvider.reset(); - m_audioItemsInExecution.clear(); - m_audioPlayerInfo.clear(); - for (const auto& renderPlayerInfoCardsInterface : m_renderPlayerInfoCardsInterfaces) { - renderPlayerInfoCardsInterface->setObserver(nullptr); - } - m_renderPlayerInfoCardsInterfaces.clear(); -} - -void TemplateRuntime::removeDirective(std::shared_ptr info) { - /* - * Check result too, to catch cases where DirectiveInfo was created locally, without a nullptr result. - * In those cases there is no messageId to remove because no result was expected. - */ - if (info->directive && info->result) { - CapabilityAgent::removeDirective(info->directive->getMessageId()); - } -} - -void TemplateRuntime::displayCardCleared() { - m_executor.submit([this]() { executeCardClearedEvent(); }); -} - -void TemplateRuntime::setHandlingCompleted(std::shared_ptr info) { - if (info && info->result) { - info->result->setCompleted(); - } - removeDirective(info); -} - -void TemplateRuntime::handleRenderTemplateDirective(std::shared_ptr info) { - ACSDK_DEBUG5(LX("handleRenderTemplateDirective")); - - m_executor.submit([this, info]() { - ACSDK_DEBUG5(LX("handleRenderTemplateDirectiveInExecutor")); - m_isRenderTemplateLastReceived = true; - executeDisplayCardEvent(info); - setHandlingCompleted(info); - }); -} - -void TemplateRuntime::handleRenderPlayerInfoDirective(std::shared_ptr info) { - ACSDK_DEBUG5(LX("handleRenderPlayerInfoDirective")); - - m_executor.submit([this, info]() { - ACSDK_DEBUG5(LX("handleRenderPlayerInfoDirectiveInExecutor")); - m_isRenderTemplateLastReceived = false; - - rapidjson::Document payload; - rapidjson::ParseResult result = payload.Parse(info->directive->getPayload()); - if (!result) { - ACSDK_ERROR(LX("handleRenderPlayerInfoDirectiveInExecutorParseFailed") - .d("reason", rapidjson::GetParseError_En(result.Code())) - .d("offset", result.Offset()) - .d("messageId", info->directive->getMessageId())); - sendExceptionEncounteredAndReportFailed( - info, "Unable to parse payload", ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); - return; - } - - std::string audioItemId; - if (!jsonUtils::retrieveValue(payload, AUDIO_ITEM_ID_TAG, &audioItemId)) { - ACSDK_ERROR(LX("handleRenderPlayerInfoDirective") - .d("reason", "missingAudioItemId") - .d("messageId", info->directive->getMessageId())); - sendExceptionEncounteredAndReportFailed(info, "missing audioItemId"); - return; - } - - size_t found = std::string::npos; - for (auto& executionMap : m_audioItemsInExecution) { - if (!executionMap.second.audioItemId.empty()) { - found = audioItemId.find(executionMap.second.audioItemId); - } - if (found != std::string::npos) { - ACSDK_DEBUG3(LX("handleRenderPlayerInfoDirectiveInExecutor") - .d("audioItemId", audioItemId) - .m("Matching audioItemId in execution.")); - executionMap.second.directive = info; - m_activeRenderPlayerInfoCardsProvider = executionMap.first; - m_audioPlayerInfo[m_activeRenderPlayerInfoCardsProvider].offset = - executionMap.first->getAudioItemOffset(); - executeStopTimer(); - executeDisplayCardEvent(info); - // Since there'a match, we can safely empty m_audioItems. - m_audioItems.clear(); - break; - } - } - - if (std::string::npos == found) { - ACSDK_DEBUG3(LX("handleRenderPlayerInfoDirectiveInExecutor") - .d("audioItemId", audioItemId) - .m("Not matching audioItemId in execution.")); - - AudioItemPair itemPair{audioItemId, info}; - if (m_audioItems.size() == MAXIMUM_QUEUE_SIZE) { - // Something is wrong, so we pop the back of the queue and log an error. - auto discardedAudioItem = m_audioItems.back(); - m_audioItems.pop_back(); - ACSDK_ERROR(LX("handleRenderPlayerInfoDirective") - .d("reason", "queueIsFull") - .d("discardedAudioItemId", discardedAudioItem.audioItemId)); - } - m_audioItems.push_front(itemPair); - } - - setHandlingCompleted(info); - }); -} - -void TemplateRuntime::handleUnknownDirective(std::shared_ptr info) { - ACSDK_ERROR(LX("handleDirectiveFailed") - .d("reason", "unknownDirective") - .d("namespace", info->directive->getNamespace()) - .d("name", info->directive->getName())); - - m_executor.submit([this, info] { - const std::string exceptionMessage = - "unexpected directive " + info->directive->getNamespace() + ":" + info->directive->getName(); - - sendExceptionEncounteredAndReportFailed( - info, exceptionMessage, ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); - }); -} - -void TemplateRuntime::executeAudioPlayerInfoUpdates(avsCommon::avs::PlayerActivity state, const Context& context) { - ACSDK_DEBUG5(LX("executeAudioPlayerInfoUpdates") - .d("audioItemId", context.audioItemId) - .d("offset", context.offset.count()) - .d("audioPlayerState", state) - .d("isRenderTemplatelastReceived", m_isRenderTemplateLastReceived)); - - if (avsCommon::avs::PlayerActivity::IDLE == state || avsCommon::avs::PlayerActivity::BUFFER_UNDERRUN == state) { - /* - * The TemplateRuntime Capability Agent is not interested in the IDLE nor BUFFER_UNDERRUN state, so we just - * ignore the callback. - */ - return; - } - - if (!context.mediaProperties) { - ACSDK_ERROR(LX("executeAudioPlayerInfoUpdatesFailed").d("reason", "nullRenderPlayerInfoCardsInterface")); - return; - } - - const auto& currentRenderPlayerInfoCardsProvider = context.mediaProperties; - if (m_audioPlayerInfo[currentRenderPlayerInfoCardsProvider].audioPlayerState == state && - m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].audioItemId == context.audioItemId) { - /* - * The AudioPlayer notification is chatty during audio playback as it will frequently toggle between - * BUFFER_UNDERRUN and PLAYER state. So we filter out the callbacks if the notification are with the - * same state and audioItemId. - */ - return; - } - - auto isStateUpdated = (m_audioPlayerInfo[currentRenderPlayerInfoCardsProvider].audioPlayerState != state); - m_audioPlayerInfo[currentRenderPlayerInfoCardsProvider].audioPlayerState = state; - m_audioPlayerInfo[currentRenderPlayerInfoCardsProvider].offset = context.offset; - if (m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].audioItemId != context.audioItemId) { - m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].audioItemId = context.audioItemId; - m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].directive.reset(); - // iterate from front to back (front is most recent) - for (auto it = m_audioItems.begin(); it != m_audioItems.end(); ++it) { - auto found = it->audioItemId.find(context.audioItemId); - if (std::string::npos != found) { - ACSDK_DEBUG3(LX("executeAudioPlayerInfoUpdates") - .d("audioItemId", context.audioItemId) - .m("Found matching audioItemId in queue.")); - m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].directive = it->directive; - m_activeRenderPlayerInfoCardsProvider = currentRenderPlayerInfoCardsProvider; - // We are erasing items older than the current found, as well as the current item. - m_audioItems.erase(it, m_audioItems.end()); - break; - } - } - } - if (m_isRenderTemplateLastReceived && state != avsCommon::avs::PlayerActivity::PLAYING) { - /* - * If RenderTemplate is the last directive received and the AudioPlayer is not notifying a PLAY, - * we shouldn't be notifying the observer to render a PlayerInfo display card. - */ - return; - } - m_isRenderTemplateLastReceived = false; - - /* - * If the AudioPlayer notifies a PLAYING state before the RenderPlayerInfo with the corresponding - * audioItemId is received, this function will also be called but the m_audioItemsInExecution.directive - * will be set to nullptr. So we need to do a nullptr check here to make sure there is a RenderPlayerInfo - * displayCard to display.. - */ - if (m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].directive) { - if (isStateUpdated) { - executeAudioPlayerStartTimer(state); - } - executeDisplayCardEvent(m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].directive); - } else { - // The RenderTemplateCard is cleared before it's displayed, so we should release the focus. - if (TemplateRuntime::State::ACQUIRING == m_state) { - m_state = TemplateRuntime::State::RELEASING; - } - } -} - -void TemplateRuntime::executeAudioPlayerStartTimer(avsCommon::avs::PlayerActivity state) { - if (avsCommon::avs::PlayerActivity::PLAYING == state) { - executeStopTimer(); - } else if (avsCommon::avs::PlayerActivity::PAUSED == state || avsCommon::avs::PlayerActivity::STOPPED == state) { - executeStartTimer(m_audioPlaybackStoppedPausedTimeout); - } else if (avsCommon::avs::PlayerActivity::FINISHED == state) { - executeStartTimer(m_audioPlaybackFinishedTimeout); - } -} - -void TemplateRuntime::executeRenderPlayerInfoCallbacks(bool isClearCard) { - ACSDK_DEBUG3(LX("executeRenderPlayerInfoCallbacks").d("isClearCard", isClearCard ? "True" : "False")); - if (isClearCard) { - for (auto& observer : m_observers) { - observer->clearPlayerInfoCard(); - } - } else { - if (!m_activeRenderPlayerInfoCardsProvider) { - ACSDK_ERROR( - LX("executeRenderPlayerInfoCallbacksFailed").d("reason", "nullActiveRenderPlayerInfoCardsProvider")); - return; - } - if (!m_audioItemsInExecution[m_activeRenderPlayerInfoCardsProvider].directive) { - ACSDK_ERROR(LX("executeRenderPlayerInfoCallbacksFailed").d("reason", "nullAudioItemInExecution")); - return; - } - auto payload = - m_audioItemsInExecution[m_activeRenderPlayerInfoCardsProvider].directive->directive->getPayload(); - for (auto& observer : m_observers) { - observer->renderPlayerInfoCard(payload, m_audioPlayerInfo[m_activeRenderPlayerInfoCardsProvider], m_focus); - } - } -} - -void TemplateRuntime::executeRenderTemplateCallbacks(bool isClearCard) { - ACSDK_DEBUG3(LX("executeRenderTemplateCallbacks").d("isClear", isClearCard ? "True" : "False")); - for (auto& observer : m_observers) { - if (isClearCard) { - observer->clearTemplateCard(); - } else { - observer->renderTemplateCard(m_lastDisplayedDirective->directive->getPayload(), m_focus); - } - } -} - -void TemplateRuntime::executeDisplayCard() { - if (m_lastDisplayedDirective) { - if (m_lastDisplayedDirective->directive->getName() == RENDER_TEMPLATE) { - executeStopTimer(); - executeRenderTemplateCallbacks(false); - } else { - executeRenderPlayerInfoCallbacks(false); - } - } -} - -void TemplateRuntime::executeClearCard() { - if (m_lastDisplayedDirective) { - if (m_lastDisplayedDirective->directive->getName() == RENDER_TEMPLATE) { - executeRenderTemplateCallbacks(true); - } else { - executeRenderPlayerInfoCallbacks(true); - } - } -} - -void TemplateRuntime::executeStartTimer(std::chrono::milliseconds timeout) { - if (TemplateRuntime::State::DISPLAYING == m_state) { - ACSDK_DEBUG3(LX("executeStartTimer").d("timeoutInMilliseconds", timeout.count())); - m_clearDisplayTimer.start(timeout, [this] { m_executor.submit([this] { executeTimerEvent(); }); }); - } -} - -void TemplateRuntime::executeStopTimer() { - ACSDK_DEBUG3(LX("executeStopTimer")); - m_clearDisplayTimer.stop(); -} - -/* - * A state machine is used to acquire and release the visual channel from the visual @c FocusManager. The state machine - * has five @c State, and four events as listed below: - * - * displayCard - This event happens when the TempateRuntime is ready to notify its observers to display a - * displayCard. - * - * focusChanged - This event happens when the @c FocusManager notifies a change in @c FocusState in the visual - * channel. - * - * timer - This event happens when m_clearDisplayTimer expires and needs to notify its observers to clear the - * displayCard. - * - * cardCleared - This event happens when @c displayCardCleared() is called to notify @c TemplateRuntime the device has - * cleared the screen. - * - * Each state transition may result in one or more of the following actions: - * (A) Acquire channel - * (B) Release channel - * (C) Notify observers to display displayCard - * (D) Notify observers to clear displayCard - * (E) Log error about unexpected focusChanged event. - * - * Below is the state table illustrating the state transition and its action. NC means no change in state. - * - * E V E N T S - * ----------------------------------------------------------------------------------------- - * Current State | displayCard | timer | focusChanged::NONE | focusChanged::FG/BG | cardCleared - * -------------------------------------------------------------------------------------------------------- - * | IDLE | ACQUIRING(A) | NC | NC | RELEASING(B&E) | NC - * | ACQUIRING | NC | NC | IDLE(E) | DISPLAYING(C) | NC - * | DISPLAYING | NC(C) | RELEASING(B&D) | IDLE(D) | DISPLAYING(C) | RELEASING(B) - * | RELEASING | REACQUIRING | NC | IDLE | NC(B&E) | NC - * | REACQUIRING | NC | NC | ACQUIRING(A) | RELEASING(B&E) | NC - * -------------------------------------------------------------------------------------------------------- - * - */ - -std::string TemplateRuntime::stateToString(const TemplateRuntime::State state) { - switch (state) { - case TemplateRuntime::State::IDLE: - return "IDLE"; - case TemplateRuntime::State::ACQUIRING: - return "ACQUIRING"; - case TemplateRuntime::State::DISPLAYING: - return "DISPLAYING"; - case TemplateRuntime::State::RELEASING: - return "RELEASING"; - case TemplateRuntime::State::REACQUIRING: - return "REACQUIRING"; - } - return "UNKNOWN"; -} - -void TemplateRuntime::executeTimerEvent() { - State nextState = m_state; - - switch (m_state) { - case TemplateRuntime::State::DISPLAYING: - executeClearCard(); - m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); - nextState = TemplateRuntime::State::RELEASING; - break; - - case TemplateRuntime::State::IDLE: - case TemplateRuntime::State::ACQUIRING: - case TemplateRuntime::State::RELEASING: - case TemplateRuntime::State::REACQUIRING: - // Do Nothing. - break; - } - ACSDK_DEBUG3( - LX("executeTimerEvent").d("prevState", stateToString(m_state)).d("nextState", stateToString(nextState))); - m_state = nextState; -} - -void TemplateRuntime::executeOnFocusChangedEvent(avsCommon::avs::FocusState newFocus) { - ACSDK_DEBUG5(LX("executeOnFocusChangedEvent").d("prevFocus", m_focus).d("newFocus", newFocus)); - - bool weirdFocusState = false; - State nextState = m_state; - m_focus = newFocus; - - switch (m_state) { - case TemplateRuntime::State::IDLE: - // This is weird. We shouldn't be getting any focus updates in Idle. - switch (newFocus) { - case FocusState::FOREGROUND: - case FocusState::BACKGROUND: - weirdFocusState = true; - break; - case FocusState::NONE: - // Do nothing. - break; - } - break; - case TemplateRuntime::State::ACQUIRING: - switch (newFocus) { - case FocusState::FOREGROUND: - case FocusState::BACKGROUND: - executeDisplayCard(); - nextState = TemplateRuntime::State::DISPLAYING; - break; - case FocusState::NONE: - ACSDK_ERROR(LX("executeOnFocusChangedEvent") - .d("prevState", stateToString(m_state)) - .d("nextFocus", newFocus) - .m("Unexpected focus state event.")); - nextState = TemplateRuntime::State::IDLE; - break; - } - break; - case TemplateRuntime::State::DISPLAYING: - switch (newFocus) { - case FocusState::FOREGROUND: - case FocusState::BACKGROUND: - executeDisplayCard(); - break; - case FocusState::NONE: - executeClearCard(); - nextState = TemplateRuntime::State::IDLE; - break; - } - break; - case TemplateRuntime::State::RELEASING: - switch (newFocus) { - case FocusState::FOREGROUND: - case FocusState::BACKGROUND: - m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); - nextState = TemplateRuntime::State::RELEASING; - break; - case FocusState::NONE: - nextState = TemplateRuntime::State::IDLE; - break; - } - break; - case TemplateRuntime::State::REACQUIRING: - switch (newFocus) { - case FocusState::FOREGROUND: - case FocusState::BACKGROUND: - weirdFocusState = true; - break; - case FocusState::NONE: - m_focusManager->acquireChannel(CHANNEL_NAME, shared_from_this(), NAMESPACE); - nextState = TemplateRuntime::State::ACQUIRING; - break; - } - break; - } - if (weirdFocusState) { - ACSDK_ERROR(LX("executeOnFocusChangedEvent") - .d("prevState", stateToString(m_state)) - .d("nextFocus", newFocus) - .m("Unexpected focus state event.")); - m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); - nextState = TemplateRuntime::State::RELEASING; - } - ACSDK_DEBUG3(LX("executeOnFocusChangedEvent") - .d("prevState", stateToString(m_state)) - .d("nextState", stateToString(nextState))); - m_state = nextState; -} - -void TemplateRuntime::executeDisplayCardEvent( - const std::shared_ptr info) { - State nextState = m_state; - m_lastDisplayedDirective = info; - - switch (m_state) { - case TemplateRuntime::State::IDLE: - m_focusManager->acquireChannel(CHANNEL_NAME, shared_from_this(), NAMESPACE); - nextState = TemplateRuntime::State::ACQUIRING; - break; - case TemplateRuntime::State::ACQUIRING: - // Do Nothing. - break; - case TemplateRuntime::State::DISPLAYING: - executeDisplayCard(); - nextState = TemplateRuntime::State::DISPLAYING; - break; - case TemplateRuntime::State::RELEASING: - nextState = TemplateRuntime::State::REACQUIRING; - break; - case TemplateRuntime::State::REACQUIRING: - // Do Nothing. - break; - } - ACSDK_DEBUG3( - LX("executeDisplayCardEvent").d("prevState", stateToString(m_state)).d("nextState", stateToString(nextState))); - m_state = nextState; -} - -void TemplateRuntime::executeCardClearedEvent() { - State nextState = m_state; - switch (m_state) { - case TemplateRuntime::State::IDLE: - case TemplateRuntime::State::ACQUIRING: - // Do Nothing. - break; - case TemplateRuntime::State::DISPLAYING: - m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this()); - nextState = TemplateRuntime::State::RELEASING; - break; - case TemplateRuntime::State::RELEASING: - case TemplateRuntime::State::REACQUIRING: - // Do Nothing. - break; - } - ACSDK_DEBUG3( - LX("executeCardClearedEvent").d("prevState", stateToString(m_state)).d("nextState", stateToString(nextState))); - m_state = nextState; -} - -std::unordered_set> TemplateRuntime:: - getCapabilityConfigurations() { - return m_capabilityConfigurations; -} - -void TemplateRuntime::addRenderPlayerInfoCardsProvider( - std::shared_ptr cardsProvider) { - ACSDK_DEBUG5(LX("addRenderPlayerInfoCardsProvider")); - - if (!cardsProvider) { - ACSDK_ERROR( - LX("addRenderPlayerInfoCardsProviderFailed").d("reason", "nullRenderPlayerInfoCardsProviderInterface")); - return; - } - cardsProvider->setObserver(shared_from_this()); - m_renderPlayerInfoCardsInterfaces.insert(cardsProvider); -} - -} // namespace templateRuntime -} // namespace capabilityAgents -} // namespace alexaClientSDK diff --git a/CapabilityAgents/TemplateRuntime/test/CMakeLists.txt b/CapabilityAgents/TemplateRuntime/test/CMakeLists.txt deleted file mode 100644 index 8529d325e6..0000000000 --- a/CapabilityAgents/TemplateRuntime/test/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) - -set(INCLUDE_PATH - "${TemplateRuntime_INCLUDE_DIR}" - "${RAPIDJSON_INCLUDE_DIR}" - "${AVSCommon_SOURCE_DIR}/AVS/test") - -discover_unit_tests("${INCLUDE_PATH}" "TemplateRuntime;SDKInterfacesTests") diff --git a/CapabilityAgents/ToggleController/src/ToggleControllerAttributeBuilder.cpp b/CapabilityAgents/ToggleController/src/ToggleControllerAttributeBuilder.cpp index 53d367266e..1fd0540cc4 100644 --- a/CapabilityAgents/ToggleController/src/ToggleControllerAttributeBuilder.cpp +++ b/CapabilityAgents/ToggleController/src/ToggleControllerAttributeBuilder.cpp @@ -27,7 +27,7 @@ using namespace avsCommon::sdkInterfaces::toggleController; using namespace avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG{"ToggleControllerAttributeBuilder"}; +#define TAG "ToggleControllerAttributeBuilder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CapabilityAgents/ToggleController/src/ToggleControllerCapabilityAgent.cpp b/CapabilityAgents/ToggleController/src/ToggleControllerCapabilityAgent.cpp index 5d9de1abce..3b3cd91491 100644 --- a/CapabilityAgents/ToggleController/src/ToggleControllerCapabilityAgent.cpp +++ b/CapabilityAgents/ToggleController/src/ToggleControllerCapabilityAgent.cpp @@ -29,7 +29,7 @@ using namespace avsCommon::utils; using namespace avsCommon::utils::configuration; /// String to identify log entries originating from this file. -static const std::string TAG{"ToggleControllerCapabilityAgent"}; +#define TAG "ToggleControllerCapabilityAgent" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -202,7 +202,7 @@ void ToggleControllerCapabilityAgent::handleDirective(std::shared_ptrdirective->getName(); if (!info->directive->getEndpoint().hasValue() || @@ -242,7 +242,7 @@ void ToggleControllerCapabilityAgent::provideState( ACSDK_DEBUG5( LX(__func__).d("contextRequestToken", contextRequestToken).sensitive("stateProviderName", stateProviderName)); - m_executor.submit([this, stateProviderName, contextRequestToken] { + m_executor.execute([this, stateProviderName, contextRequestToken] { ACSDK_DEBUG5(LX("provideStateInExecutor")); executeProvideState(stateProviderName, contextRequestToken); }); @@ -316,7 +316,7 @@ void ToggleControllerCapabilityAgent::onToggleStateChanged( return; } - m_executor.submit([this, toggleState, cause] { + m_executor.execute([this, toggleState, cause] { m_contextManager->reportStateChange( CapabilityTag(NAMESPACE, TOGGLESTATE_PROPERTY_NAME, m_endpointId, m_instance), buildCapabilityState(toggleState), diff --git a/Captions/Component/src/CaptionsComponent.cpp b/Captions/Component/src/CaptionsComponent.cpp index d942de1485..e413ce1488 100644 --- a/Captions/Component/src/CaptionsComponent.cpp +++ b/Captions/Component/src/CaptionsComponent.cpp @@ -30,7 +30,7 @@ using namespace acsdkManufactory; using namespace acsdkShutdownManagerInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("CaptionsComponent"); +#define TAG "CaptionsComponent" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/Captions/Implementation/src/CaptionManager.cpp b/Captions/Implementation/src/CaptionManager.cpp index ab59778983..9982d8e77c 100644 --- a/Captions/Implementation/src/CaptionManager.cpp +++ b/Captions/Implementation/src/CaptionManager.cpp @@ -243,7 +243,7 @@ void CaptionManager::onParsed(const CaptionFrame& captionFrame) { // Attempt to split at the first available break between words before the requested wrap point. for (size_t i = wrapIndex; i > 0; i--) { if (' ' == lineText[i]) { - wrapIndex = i; + wrapIndex = static_cast(i); break; } } diff --git a/Captions/Implementation/src/LibwebvttParserAdapter.cpp b/Captions/Implementation/src/LibwebvttParserAdapter.cpp index 3ebf5fedde..a16b24a1aa 100644 --- a/Captions/Implementation/src/LibwebvttParserAdapter.cpp +++ b/Captions/Implementation/src/LibwebvttParserAdapter.cpp @@ -31,7 +31,7 @@ namespace captions { using namespace alexaClientSDK::avsCommon::utils::logger; /// String to identify log entries originating from this file. -static const std::string TAG("LibwebvttParserAdapter"); +#define TAG "LibwebvttParserAdapter" /// Return value indicating an error occurred during parsing. static const int WEBVTT_CALLBACK_ERROR = -1; diff --git a/Captions/Interface/src/CaptionLine.cpp b/Captions/Interface/src/CaptionLine.cpp index eae9ec339d..72d89623eb 100644 --- a/Captions/Interface/src/CaptionLine.cpp +++ b/Captions/Interface/src/CaptionLine.cpp @@ -29,7 +29,7 @@ using namespace alexaClientSDK::avsCommon; using namespace alexaClientSDK::avsCommon::utils::logger; /// String to identify log entries originating from this file. -static const std::string TAG("CaptionLine"); +#define TAG "CaptionLine" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CertifiedSender/src/CertifiedSender.cpp b/CertifiedSender/src/CertifiedSender.cpp index 840d314729..e1972327b2 100644 --- a/CertifiedSender/src/CertifiedSender.cpp +++ b/CertifiedSender/src/CertifiedSender.cpp @@ -33,7 +33,7 @@ using namespace avsCommon::utils::configuration; using namespace avsCommon::utils::power; /// String to identify log entries originating from this file. -static const std::string TAG("CertifiedSender"); +#define TAG "CertifiedSender" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CertifiedSender/src/SQLiteMessageStorage.cpp b/CertifiedSender/src/SQLiteMessageStorage.cpp index 697c571107..3bd3f3a377 100644 --- a/CertifiedSender/src/SQLiteMessageStorage.cpp +++ b/CertifiedSender/src/SQLiteMessageStorage.cpp @@ -31,7 +31,7 @@ using namespace avsCommon::utils::logger; using namespace alexaClientSDK::storage::sqliteStorage; /// String to identify log entries originating from this file. -static const std::string TAG("SQLiteMessageStorage"); +#define TAG "SQLiteMessageStorage" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/CertifiedSender/test/CertifiedSenderTest.cpp b/CertifiedSender/test/CertifiedSenderTest.cpp index 4dd454e707..99464d7df5 100644 --- a/CertifiedSender/test/CertifiedSenderTest.cpp +++ b/CertifiedSender/test/CertifiedSenderTest.cpp @@ -325,7 +325,7 @@ TEST_F(CertifiedSenderTest, testTimer_SendMessageWithURI) { /** * Tests if messages are re-submitted when the response is a re-tryable response. */ -TEST_F(CertifiedSenderTest, testSlow_retryableResponsesAreRetried) { +TEST_F(CertifiedSenderTest, testTimer_retryableResponsesAreRetried) { std::static_pointer_cast(m_certifiedSender) ->onConnectionStatusChanged( avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::Status::CONNECTED, @@ -376,7 +376,7 @@ TEST_F(CertifiedSenderTest, testSlow_retryableResponsesAreRetried) { /** * Tests if messages are discarded when the response is a non-retryable response. */ -TEST_F(CertifiedSenderTest, testSlow_nonRetryableResponsesAreNotRetried) { +TEST_F(CertifiedSenderTest, testTimer_nonRetryableResponsesAreNotRetried) { std::static_pointer_cast(m_certifiedSender) ->onConnectionStatusChanged( avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::Status::CONNECTED, diff --git a/CertifiedSender/test/Common/MockCertifiedSender.cpp b/CertifiedSender/test/Common/MockCertifiedSender.cpp index 0e49410fe2..373a248250 100644 --- a/CertifiedSender/test/Common/MockCertifiedSender.cpp +++ b/CertifiedSender/test/Common/MockCertifiedSender.cpp @@ -25,7 +25,6 @@ using namespace avsCommon::sdkInterfaces; using namespace avsCommon::sdkInterfaces::test; using namespace avsCommon::utils; using namespace certifiedSender; -using namespace certifiedSender::test; using namespace registrationManager; using namespace ::testing; diff --git a/ContextManager/include/ContextManager/ContextManager.h b/ContextManager/include/ContextManager/ContextManager.h index a00d0592f0..2fdb8d97f9 100644 --- a/ContextManager/include/ContextManager/ContextManager.h +++ b/ContextManager/include/ContextManager/ContextManager.h @@ -186,16 +186,11 @@ class ContextManager : public avsCommon::sdkInterfaces::ContextManagerInterface */ struct RequestTracker { /// The token returned by the @c MultiTimer. - avsCommon::utils::timing::MultiTimer::Token timerToken; + const avsCommon::utils::timing::MultiTimer::Token timerToken; /// The context requester. - std::shared_ptr contextRequester; + const std::shared_ptr contextRequester; /// If Reportable Properties should be skipped for this request. - bool skipReportableStateProperties; - - /** - * Default Constructor. - */ - RequestTracker(); + const bool skipReportableStateProperties; /** * Constructor. diff --git a/ContextManager/src/ContextManager.cpp b/ContextManager/src/ContextManager.cpp index 6631034a55..3abb219644 100644 --- a/ContextManager/src/ContextManager.cpp +++ b/ContextManager/src/ContextManager.cpp @@ -33,7 +33,7 @@ using namespace avsCommon::utils; using namespace avsCommon::utils::metrics; /// String to identify log entries originating from this file. -static const std::string TAG("ContextManager"); +#define TAG "ContextManager" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -87,12 +87,6 @@ static void NoopCallback() { // No-op } -ContextManager::RequestTracker::RequestTracker() : - timerToken{0}, - contextRequester{nullptr}, - skipReportableStateProperties{false} { -} - ContextManager::RequestTracker::RequestTracker( avsCommon::utils::timing::MultiTimer::Token timerToken, std::shared_ptr contextRequester, @@ -140,10 +134,10 @@ SetStateResult ContextManager::setState( const std::string& jsonState, const StateRefreshPolicy& refreshPolicy, const ContextRequestToken stateRequestToken) { - ACSDK_DEBUG5(LX(__func__).sensitive("capability", capabilityIdentifier)); + ACSDK_DEBUG5(LX(__func__).d("token", stateRequestToken).sensitive("capability", capabilityIdentifier)); if (EMPTY_TOKEN == stateRequestToken) { - m_executor.submit([this, capabilityIdentifier, jsonState, refreshPolicy] { + m_executor.execute([this, capabilityIdentifier, jsonState, refreshPolicy] { updateCapabilityState(capabilityIdentifier, jsonState, refreshPolicy); }); return SetStateResult::SUCCESS; @@ -169,7 +163,7 @@ SetStateResult ContextManager::setState( return SetStateResult::STATE_PROVIDER_NOT_REGISTERED; } - m_executor.submit([this, capabilityIdentifier, jsonState, refreshPolicy, stateRequestToken] { + m_executor.execute([this, capabilityIdentifier, jsonState, refreshPolicy, stateRequestToken] { updateCapabilityState(capabilityIdentifier, jsonState, refreshPolicy); if (jsonState.empty() && (StateRefreshPolicy::ALWAYS == refreshPolicy)) { ACSDK_ERROR(LX("setStateFailed") @@ -218,7 +212,7 @@ void ContextManager::reportStateChange( AlexaStateChangeCauseType cause) { ACSDK_DEBUG5(LX(__func__).sensitive("capability", capabilityIdentifier)); - m_executor.submit([this, capabilityIdentifier, capabilityState, cause] { + m_executor.execute([this, capabilityIdentifier, capabilityState, cause] { updateCapabilityState(capabilityIdentifier, capabilityState); std::lock_guard observerMutex{m_observerMutex}; for (auto& observer : m_observers) { @@ -233,7 +227,7 @@ void ContextManager::provideStateResponse( ContextRequestToken stateRequestToken) { ACSDK_DEBUG5(LX(__func__).sensitive("capability", capabilityIdentifier)); - m_executor.submit([this, capabilityIdentifier, capabilityState, stateRequestToken] { + m_executor.execute([this, capabilityIdentifier, capabilityState, stateRequestToken] { std::function contextAvailableCallback = NoopCallback; { std::lock_guard requestsLock{m_requestsMutex}; @@ -274,7 +268,7 @@ void ContextManager::provideStateUnavailableResponse( bool isEndpointUnreachable) { ACSDK_DEBUG5(LX(__func__).sensitive("capability", capabilityIdentifier)); - m_executor.submit([this, capabilityIdentifier, stateRequestToken, isEndpointUnreachable] { + m_executor.execute([this, capabilityIdentifier, stateRequestToken, isEndpointUnreachable] { std::function contextAvailableCallback = NoopCallback; std::function contextFailureCallback = NoopCallback; { @@ -359,59 +353,75 @@ ContextRequestToken ContextManager::getContextInternal( const std::string& endpointId, const std::chrono::milliseconds& timeout, bool bSkipReportableStateProperties) { - ACSDK_DEBUG5(LX(__func__).sensitive("endpointId", endpointId)); auto token = generateToken(); - m_executor.submit([this, contextRequester, endpointId, token, timeout, bSkipReportableStateProperties] { - auto timerToken = m_multiTimer->submitTask(timeout, [this, token] { - // Cancel request after timeout. - m_executor.submit([this, token] { - std::function contextFailureCallback = NoopCallback; - { - std::lock_guard lock{m_requestsMutex}; - contextFailureCallback = - getContextFailureCallbackLocked(token, ContextRequestError::STATE_PROVIDER_TIMEDOUT); - } - contextFailureCallback(); - }); + ACSDK_DEBUG5(LX(__func__) + .d("token", token) + .d("timeout(ms)", timeout.count()) + .d("skipReportableStateProperties", bSkipReportableStateProperties) + .sensitive("endpointId", endpointId)); + + auto timerToken = m_multiTimer->submitTask(timeout, [this, token] { + // Cancel request after timeout. + m_executor.execute([this, token] { + std::function contextFailureCallback = NoopCallback; + { + std::lock_guard lock{m_requestsMutex}; + contextFailureCallback = + getContextFailureCallbackLocked(token, ContextRequestError::STATE_PROVIDER_TIMEDOUT); + } + contextFailureCallback(); }); + }); - std::lock_guard requestsLock{m_requestsMutex}; - auto& requestEndpointId = endpointId.empty() ? m_defaultEndpointId : endpointId; - m_pendingRequests.emplace(token, RequestTracker(timerToken, contextRequester, bSkipReportableStateProperties)); - + m_executor.execute([this, contextRequester, endpointId, token, timerToken, bSkipReportableStateProperties] { std::function contextAvailableCallback = NoopCallback; { - std::lock_guard statesLock{m_endpointsStateMutex}; - - for (auto& capability : m_endpointsState[requestEndpointId]) { - auto stateInfo = capability.second; - auto stateProvider = capability.second.stateProvider; - - if (stateProvider) { - bool requestState = false; - if (stateInfo.legacyCapability && stateInfo.refreshPolicy != StateRefreshPolicy::NEVER) { - requestState = true; - } else if ( - !stateInfo.legacyCapability && stateProvider->canStateBeRetrieved() && - stateProvider->shouldQueryState()) { - if (stateProvider->hasReportableStateProperties()) { - /// Check if the reportable state properties should be skipped. - if (!bSkipReportableStateProperties) { + std::lock_guard requestsLock{m_requestsMutex}; + auto& requestEndpointId = endpointId.empty() ? m_defaultEndpointId : endpointId; + m_pendingRequests.emplace( + token, RequestTracker(timerToken, contextRequester, bSkipReportableStateProperties)); + + { + std::lock_guard statesLock{m_endpointsStateMutex}; + + auto endpointsStateIt = m_endpointsState.find(requestEndpointId); + if (m_endpointsState.end() == endpointsStateIt) { + ACSDK_WARN(LX(__func__) + .d("reason", "requestEndpointIdNotFound") + .d("token", token) + .sensitive("endpointId", requestEndpointId)); + } else { + for (auto& capability : endpointsStateIt->second) { + auto stateInfo = capability.second; + auto stateProvider = capability.second.stateProvider; + + if (stateProvider) { + bool requestState = false; + if (stateInfo.legacyCapability && stateInfo.refreshPolicy != StateRefreshPolicy::NEVER) { requestState = true; + } else if ( + !stateInfo.legacyCapability && stateProvider->canStateBeRetrieved() && + stateProvider->shouldQueryState()) { + if (stateProvider->hasReportableStateProperties()) { + /// Check if the reportable state properties should be skipped. + if (!bSkipReportableStateProperties) { + requestState = true; + } + } else { + requestState = true; + } } - } else { - requestState = true; - } - } - if (requestState) { - stateProvider->provideState(capability.first, token); - m_pendingStateRequest[token].emplace(capability.first); + if (requestState) { + m_pendingStateRequest[token].emplace(capability.first); + stateProvider->provideState(capability.first, token); + } + } } } - } - contextAvailableCallback = getContextAvailableCallbackIfReadyLocked(token, requestEndpointId); + contextAvailableCallback = getContextAvailableCallbackIfReadyLocked(token, requestEndpointId); + } } /// Callback method should be called outside the lock. contextAvailableCallback(); @@ -434,27 +444,36 @@ std::function ContextManager::getContextFailureCallbackLocked( m_pendingStateRequest.erase(requestToken); }}; - auto& request = m_pendingRequests[requestToken]; - if (!request.contextRequester) { + auto requestIt = m_pendingRequests.find(requestToken); + if (m_pendingRequests.end() == requestIt) { + ACSDK_ERROR(LX(__func__).d("result", "tokenNotFound").d("token", requestToken)); + return NoopCallback; + } + auto& request = requestIt->second; + std::shared_ptr contextRequester = request.contextRequester; + if (!contextRequester) { ACSDK_DEBUG0(LX(__func__).d("result", "nullRequester").d("token", requestToken)); return NoopCallback; } - for (auto& pendingState : m_pendingStateRequest[requestToken]) { - ACSDK_ERROR(LX(__func__) - .d("pendingStateProviderName", pendingState.name) - .d("pendingStateProviderNamespace", pendingState.nameSpace)); - auto metricName = STATE_PROVIDER_TIMEOUT_METRIC_PREFIX + pendingState.nameSpace; - recordMetric( - m_metricRecorder, - MetricEventBuilder{} - .setActivityName("CONTEXT_MANAGER-" + metricName) - .addDataPoint(DataPointCounterBuilder{}.setName(metricName).increment(1).build()) - .build()); + auto stateRequestIt = m_pendingStateRequest.find(requestToken); + if (m_pendingStateRequest.end() != stateRequestIt) { + for (auto& pendingState : stateRequestIt->second) { + ACSDK_ERROR(LX(__func__) + .d("pendingStateProviderName", pendingState.name) + .d("pendingStateProviderNamespace", pendingState.nameSpace)); + auto metricName = STATE_PROVIDER_TIMEOUT_METRIC_PREFIX + pendingState.nameSpace; + recordMetric( + m_metricRecorder, + MetricEventBuilder{} + .setActivityName("CONTEXT_MANAGER-" + metricName) + .addDataPoint(DataPointCounterBuilder{}.setName(metricName).increment(1).build()) + .build()); + } } - auto contextRequester = request.contextRequester; return [contextRequester, error, requestToken]() { if (contextRequester) { + ACSDK_ERROR(LX(__func__).d("reason", "invokeOnContextFailure").d("token", requestToken)); contextRequester->onContextFailure(error, requestToken); } }; @@ -463,10 +482,16 @@ std::function ContextManager::getContextFailureCallbackLocked( std::function ContextManager::getContextAvailableCallbackIfReadyLocked( unsigned int requestToken, const EndpointIdentifier& endpointId) { - auto& pendingStates = m_pendingStateRequest[requestToken]; - if (!pendingStates.empty()) { - ACSDK_DEBUG5(LX(__func__).d("result", "stateNotAvailableYet").d("pendingStates", pendingStates.size())); - return NoopCallback; + auto stateRequestIt = m_pendingStateRequest.find(requestToken); + if (m_pendingStateRequest.end() != stateRequestIt) { + auto& pendingStates = stateRequestIt->second; + if (!pendingStates.empty()) { + ACSDK_DEBUG5(LX(__func__) + .d("result", "stateNotAvailableYet") + .d("token", requestToken) + .d("pendingStates", pendingStates.size())); + return NoopCallback; + } } ACSDK_DEBUG5(LX(__func__).sensitive("endpointId", endpointId).d("token", requestToken)); @@ -480,8 +505,14 @@ std::function ContextManager::getContextAvailableCallbackIfReadyLocked( m_pendingStateRequest.erase(requestToken); }}; - auto& request = m_pendingRequests[requestToken]; - if (!request.contextRequester) { + auto requestIt = m_pendingRequests.find(requestToken); + if (m_pendingRequests.end() == requestIt) { + ACSDK_ERROR(LX(__func__).d("result", "tokenNotFound").d("token", requestToken)); + return NoopCallback; + } + auto& request = requestIt->second; + std::shared_ptr contextRequester = request.contextRequester; + if (!contextRequester) { ACSDK_ERROR( LX("getContextAvailableCallbackIfReadyLockedFailed").d("reason", "nullRequester").d("token", requestToken)); return NoopCallback; @@ -489,40 +520,44 @@ std::function ContextManager::getContextAvailableCallbackIfReadyLocked( AVSContext context; auto& requestEndpointId = endpointId.empty() ? m_defaultEndpointId : endpointId; - for (auto& capability : m_endpointsState[requestEndpointId]) { - auto stateProvider = capability.second.stateProvider; - auto stateInfo = capability.second; - bool addState = false; - - if (stateInfo.legacyCapability) { - // Ignore if the state is not available for legacy SOMETIMES refresh policy. - if ((stateInfo.refreshPolicy == StateRefreshPolicy::SOMETIMES) && !stateInfo.capabilityState.hasValue()) { - ACSDK_DEBUG5(LX(__func__).d("skipping state for legacy capabilityIdentifier", capability.first)); + auto endpointStateIt = m_endpointsState.find(requestEndpointId); + if (endpointStateIt != m_endpointsState.end()) { + for (auto& capability : endpointStateIt->second) { + auto stateProvider = capability.second.stateProvider; + auto stateInfo = capability.second; + bool addState = false; + + if (stateInfo.legacyCapability) { + // Ignore if the state is not available for legacy SOMETIMES refresh policy. + if ((stateInfo.refreshPolicy == StateRefreshPolicy::SOMETIMES) && + !stateInfo.capabilityState.hasValue()) { + ACSDK_DEBUG5(LX(__func__).d("skipping state for legacy capabilityIdentifier", capability.first)); + } else { + addState = true; + } } else { - addState = true; - } - } else { - if (stateProvider && stateProvider->canStateBeRetrieved()) { - /// Check if the reportable state properties should be skipped. - if (stateProvider->hasReportableStateProperties()) { - if (!request.skipReportableStateProperties) { + if (stateProvider && stateProvider->canStateBeRetrieved()) { + /// Check if the reportable state properties should be skipped. + if (stateProvider->hasReportableStateProperties()) { + if (!request.skipReportableStateProperties) { + addState = true; + } + } else { addState = true; } - } else { - addState = true; } } - } - if (addState) { - ACSDK_DEBUG5(LX(__func__).sensitive("addState", capability.first)); - context.addState(capability.first, stateInfo.capabilityState.value()); + if (addState) { + ACSDK_DEBUG5(LX(__func__).sensitive("addState", capability.first)); + context.addState(capability.first, stateInfo.capabilityState.value()); + } } } - auto contextRequester = request.contextRequester; return [contextRequester, context, endpointId, requestToken]() { if (contextRequester) { + ACSDK_DEBUG5(LX(__func__).d("reason", "invokeOnContextAvailable").d("token", requestToken)); contextRequester->onContextAvailable(endpointId, context, requestToken); } }; diff --git a/ContextManager/test/ContextManagerTest.cpp b/ContextManager/test/ContextManagerTest.cpp index 54a6b4b1da..de987cd5b8 100644 --- a/ContextManager/test/ContextManagerTest.cpp +++ b/ContextManager/test/ContextManagerTest.cpp @@ -32,7 +32,7 @@ using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; /// String to identify log entries originating from this file. -static const std::string TAG("ContextManagerTest"); +#define TAG "ContextManagerTest" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/Diagnostics/src/AudioInjectorMicrophone.cpp b/Diagnostics/src/AudioInjectorMicrophone.cpp index 2da9ed25bb..d330c2e32c 100644 --- a/Diagnostics/src/AudioInjectorMicrophone.cpp +++ b/Diagnostics/src/AudioInjectorMicrophone.cpp @@ -24,7 +24,7 @@ using avsCommon::avs::AudioInputStream; using namespace avsCommon::utils::timing; /// String to identify log entries originating from this file. -static const std::string TAG("AudioInjectorMicrophone"); +#define TAG "AudioInjectorMicrophone" /// The timeout to use for writing to the SharedDataStream. static const std::chrono::milliseconds TIMEOUT_FOR_WRITING{500}; diff --git a/Diagnostics/src/DevicePropertyAggregator.cpp b/Diagnostics/src/DevicePropertyAggregator.cpp index f6a2001092..099a69622f 100644 --- a/Diagnostics/src/DevicePropertyAggregator.cpp +++ b/Diagnostics/src/DevicePropertyAggregator.cpp @@ -25,7 +25,7 @@ using namespace alexaClientSDK::avsCommon::sdkInterfaces; using namespace alexaClientSDK::avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG{"DevicePropertyAggregator"}; +#define TAG "DevicePropertyAggregator" /// String to identify invalid property value. static const std::string INVALID_VALUE{"INVALID"}; @@ -127,7 +127,7 @@ void DevicePropertyAggregator::onAuthStateChange( break; } - m_executor.submit([this, registered]() { + m_executor.execute([this, registered]() { m_asyncPropertyMap[DevicePropertyAggregatorInterface::REGISTRATION_STATUS] = toString(registered); }); } @@ -260,7 +260,7 @@ Optional DevicePropertyAggregator::getDeviceContextJson() { void DevicePropertyAggregator::onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, alertInfo]() { + m_executor.execute([this, alertInfo]() { std::stringstream ss; ss << alertInfo.type << ":" << alertInfo.state; m_asyncPropertyMap[DevicePropertyAggregatorInterface::ALERT_TYPE_AND_STATE] = ss.str(); @@ -271,7 +271,7 @@ void DevicePropertyAggregator::onPlayerActivityChanged( PlayerActivity state, const AudioPlayerObserverInterface::Context& context) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, state, context]() { + m_executor.execute([this, state, context]() { std::string playerActivityState = avsCommon::avs::playerActivityToString(state); m_asyncPropertyMap[DevicePropertyAggregatorInterface::AUDIO_PLAYER_STATE] = playerActivityState; m_asyncPropertyMap[DevicePropertyAggregatorInterface::CONTENT_ID] = context.audioItemId; @@ -280,7 +280,7 @@ void DevicePropertyAggregator::onPlayerActivityChanged( void DevicePropertyAggregator::onConnectionStatusChanged(const Status status, const ChangedReason reason) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, status]() { + m_executor.execute([this, status]() { std::stringstream ss; ss << status; m_asyncPropertyMap[DevicePropertyAggregatorInterface::CONNECTION_STATE] = ss.str(); @@ -289,7 +289,7 @@ void DevicePropertyAggregator::onConnectionStatusChanged(const Status status, co void DevicePropertyAggregator::onSetIndicator(IndicatorState state) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, state]() { + m_executor.execute([this, state]() { std::stringstream ss; ss << state; m_asyncPropertyMap[DevicePropertyAggregatorInterface::NOTIFICATION_INDICATOR] = ss.str(); @@ -302,7 +302,7 @@ void DevicePropertyAggregator::onNotificationReceived() { void DevicePropertyAggregator::onDialogUXStateChanged(DialogUXStateObserverInterface::DialogUXState newState) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, newState]() { + m_executor.execute([this, newState]() { m_asyncPropertyMap[DevicePropertyAggregatorInterface::TTS_PLAYER_STATE] = DialogUXStateObserverInterface::stateToString(newState); }); @@ -313,7 +313,7 @@ void DevicePropertyAggregator::onSpeakerSettingsChanged( const ChannelVolumeInterface::Type& type, const SpeakerInterface::SpeakerSettings& settings) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, type, settings]() { updateSpeakerSettingsInPropertyMap(type, settings); }); + m_executor.execute([this, type, settings]() { updateSpeakerSettingsInPropertyMap(type, settings); }); } void DevicePropertyAggregator::updateSpeakerSettingsInPropertyMap( @@ -338,7 +338,7 @@ void DevicePropertyAggregator::updateSpeakerSettingsInPropertyMap( void DevicePropertyAggregator::onRangeChanged(const RangeState& rangeState, const AlexaStateChangeCauseType cause) { ACSDK_DEBUG5(LX(__func__).d("range value", rangeState.value)); - m_executor.submit([this, rangeState]() { + m_executor.execute([this, rangeState]() { std::stringstream ss; ss << rangeState.value; m_asyncPropertyMap[DevicePropertyAggregatorInterface::RANGE_CONTROLLER_STATUS] = ss.str(); @@ -349,7 +349,7 @@ void DevicePropertyAggregator::onPowerStateChanged( const PowerState& powerState, const AlexaStateChangeCauseType cause) { ACSDK_DEBUG5(LX(__func__).d("power state", powerState.powerState)); - m_executor.submit([this, powerState]() { + m_executor.execute([this, powerState]() { std::stringstream ss; ss << powerState.powerState; m_asyncPropertyMap[DevicePropertyAggregatorInterface::POWER_CONTROLLER_STATUS] = ss.str(); diff --git a/Diagnostics/src/DeviceProtocolTracer.cpp b/Diagnostics/src/DeviceProtocolTracer.cpp index 4d84df6886..ab2500c335 100644 --- a/Diagnostics/src/DeviceProtocolTracer.cpp +++ b/Diagnostics/src/DeviceProtocolTracer.cpp @@ -30,7 +30,7 @@ using namespace avsCommon::utils::json; using namespace rapidjson; /// String to identify log entries originating from this file. -static const std::string TAG("DeviceProtocolTracer"); +#define TAG "DeviceProtocolTracer" /// Maximum number of trace messages stored in the device protocol tracer. static const unsigned int DEFAULT_MAX_MESSAGES = 1; diff --git a/Diagnostics/src/DiagnosticsUtils.cpp b/Diagnostics/src/DiagnosticsUtils.cpp index 8d6375f63f..0057e5dab1 100644 --- a/Diagnostics/src/DiagnosticsUtils.cpp +++ b/Diagnostics/src/DiagnosticsUtils.cpp @@ -23,7 +23,7 @@ namespace diagnostics { namespace utils { /// String to identify log entries originating from this file. -static const std::string TAG("DiagnosticsUtils"); +#define TAG "DiagnosticsUtils" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/Diagnostics/src/FileBasedAudioInjector.cpp b/Diagnostics/src/FileBasedAudioInjector.cpp index 8202c49d78..134c63f7d0 100644 --- a/Diagnostics/src/FileBasedAudioInjector.cpp +++ b/Diagnostics/src/FileBasedAudioInjector.cpp @@ -25,7 +25,7 @@ namespace alexaClientSDK { namespace diagnostics { /// String to identify log entries originating from this file. -static const std::string TAG("FileBasedAudioInjector"); +#define TAG "FileBasedAudioInjector" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/Endpoints/src/DefaultEndpointBuilder.cpp b/Endpoints/src/DefaultEndpointBuilder.cpp index 7f56656529..27ea6d8188 100644 --- a/Endpoints/src/DefaultEndpointBuilder.cpp +++ b/Endpoints/src/DefaultEndpointBuilder.cpp @@ -31,7 +31,7 @@ using namespace avsCommon::sdkInterfaces::endpoints; using namespace avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("DefaultEndpointBuilder"); +#define TAG "DefaultEndpointBuilder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/Endpoints/src/Endpoint.cpp b/Endpoints/src/Endpoint.cpp index 79a896e6ba..302be3294c 100644 --- a/Endpoints/src/Endpoint.cpp +++ b/Endpoints/src/Endpoint.cpp @@ -26,7 +26,7 @@ using namespace avsCommon::sdkInterfaces::endpoints; using namespace avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("Endpoint"); +#define TAG "Endpoint" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/Endpoints/src/EndpointBuilder.cpp b/Endpoints/src/EndpointBuilder.cpp index ca2e6e29b2..f2e4eb182f 100644 --- a/Endpoints/src/EndpointBuilder.cpp +++ b/Endpoints/src/EndpointBuilder.cpp @@ -44,7 +44,7 @@ using namespace avsCommon::sdkInterfaces::endpoints; using namespace avsCommon::utils; /// String to identify log entries originating from this file. -static const std::string TAG("EndpointBuilder"); +#define TAG "EndpointBuilder" /** * Create a LogEntry using this file's TAG and the specified event string. diff --git a/Endpoints/src/EndpointRegistrationManager.cpp b/Endpoints/src/EndpointRegistrationManager.cpp index ba02676377..28ee17b4bc 100644 --- a/Endpoints/src/EndpointRegistrationManager.cpp +++ b/Endpoints/src/EndpointRegistrationManager.cpp @@ -25,7 +25,7 @@ namespace alexaClientSDK { namespace endpoints { /// String to identify log entries originating from this file. -static const std::string TAG("EndpointRegistrationManager"); +#define TAG "EndpointRegistrationManager" /** * Create a LogEntry using this file's TAG and the specified event string. @@ -117,7 +117,7 @@ std::future EndpointRegistratio return promise.get_future(); } - m_executor.submit([this, endpoint]() { executeRegisterEndpoint(endpoint); }); + m_executor.execute([this, endpoint]() { executeRegisterEndpoint(endpoint); }); auto& pending = m_pendingRegistrations[endpointId]; pending.first = std::move(endpoint); @@ -160,7 +160,7 @@ std::future EndpointRegistrationManag } auto endpoint = m_endpoints[endpointId]; - m_executor.submit( + m_executor.execute( [this, endpoint, endpointModificationData]() { executeUpdateEndpoint(endpoint, endpointModificationData); }); auto& pending = m_pendingUpdates[endpointId]; pending.first = std::move(endpoint); @@ -219,7 +219,7 @@ std::future EndpointRegistrat auto endpoint = m_endpoints[endpointId]; - m_executor.submit([this, endpoint]() { executeDeregisterEndpoint(endpoint); }); + m_executor.execute([this, endpoint]() { executeDeregisterEndpoint(endpoint); }); auto& pending = m_pendingDeregistrations[endpointId]; pending.first = std::move(endpoint); @@ -472,7 +472,7 @@ void EndpointRegistrationManager::onCapabilityRegistrationStatusChanged( const std::pair>& addedOrUpdatedEndpoints, const std::pair>& deletedEndpoints) { ACSDK_DEBUG5(LX(__func__)); - m_executor.submit([this, addedOrUpdatedEndpoints, deletedEndpoints] { + m_executor.execute([this, addedOrUpdatedEndpoints, deletedEndpoints] { updateAddedOrUpdatedEndpoints(addedOrUpdatedEndpoints); removeDeletedEndpoints(deletedEndpoints); }); diff --git a/Endpoints/test/EndpointRegistrationManagerTest.cpp b/Endpoints/test/EndpointRegistrationManagerTest.cpp index c2b0a35e7e..df8a8a85e3 100644 --- a/Endpoints/test/EndpointRegistrationManagerTest.cpp +++ b/Endpoints/test/EndpointRegistrationManagerTest.cpp @@ -113,6 +113,8 @@ void EndpointRegistrationManagerTest::SetUp() { void EndpointRegistrationManagerTest::TearDown() { m_manager.reset(); m_capabilitiesDelegate.reset(); + m_capabilitiesObserver.reset(); + m_registrationObserver.reset(); m_sequencer->shutdown(); m_sequencer.reset(); @@ -214,14 +216,14 @@ TEST_F(EndpointRegistrationManagerTest, test_registerEndpointSucceeds) { EXPECT_CALL(*m_sequencer, addDirectiveHandler(handler)).WillOnce(Return(true)); EXPECT_CALL(*m_capabilitiesDelegate, addOrUpdateEndpoint(_, configurations)).WillOnce(Return(true)); - // Check that register endpoint was enqueued. - auto result = m_manager->registerEndpoint(endpoint); - ASSERT_EQ(result.wait_for(std::chrono::milliseconds::zero()), std::future_status::timeout); - // Expect that the observer will be notified that the endpoint was registered. EXPECT_CALL(*m_registrationObserver, onEndpointRegistration(endpointId, _, RegistrationResult::SUCCEEDED)); EXPECT_CALL(*m_registrationObserver, onPendingEndpointRegistrationOrUpdate(endpointId, _, _)); + // Check that register endpoint was enqueued. + auto result = m_manager->registerEndpoint(endpoint); + ASSERT_EQ(result.wait_for(std::chrono::milliseconds::zero()), std::future_status::timeout); + m_capabilitiesObserver->onCapabilitiesStateChange( CapabilitiesDelegateObserverInterface::State::SUCCESS, CapabilitiesDelegateObserverInterface::Error::SUCCESS, @@ -251,11 +253,13 @@ TEST_F(EndpointRegistrationManagerTest, test_deregisterEndpointSucceeds) { EXPECT_CALL(*m_capabilitiesDelegate, addOrUpdateEndpoint(_, configurations)).WillOnce(Return(true)); EXPECT_CALL(*m_capabilitiesDelegate, deleteEndpoint(_, configurations)).WillOnce(Return(true)); + // set expectation + EXPECT_CALL(*m_registrationObserver, onEndpointRegistration(endpointId, _, RegistrationResult::SUCCEEDED)); + EXPECT_CALL(*m_registrationObserver, onPendingEndpointRegistrationOrUpdate(endpointId, _, _)); + // Add an endpoint so we can test delete. auto addResult = m_manager->registerEndpoint(endpoint); - EXPECT_CALL(*m_registrationObserver, onEndpointRegistration(endpointId, _, RegistrationResult::SUCCEEDED)); - EXPECT_CALL(*m_registrationObserver, onPendingEndpointRegistrationOrUpdate(endpointId, _, _)); m_capabilitiesObserver->onCapabilitiesStateChange( CapabilitiesDelegateObserverInterface::State::SUCCESS, CapabilitiesDelegateObserverInterface::Error::SUCCESS, @@ -312,11 +316,13 @@ TEST_F(EndpointRegistrationManagerTest, test_updateEndpointSucceeds) { EXPECT_CALL(*m_sequencer, addDirectiveHandler(handler1)).WillOnce(Return(true)); EXPECT_CALL(*m_sequencer, addDirectiveHandler(handler2)).WillOnce(Return(true)); EXPECT_CALL(*m_capabilitiesDelegate, addOrUpdateEndpoint(_, _)).WillRepeatedly(Return(true)); - // Add an endpoint so we can test update - auto addResult = m_manager->registerEndpoint(endpoint); EXPECT_CALL(*m_registrationObserver, onEndpointRegistration(endpointId, _, RegistrationResult::SUCCEEDED)); EXPECT_CALL(*m_registrationObserver, onPendingEndpointRegistrationOrUpdate(endpointId, _, _)); + + // Add an endpoint so we can test update + auto addResult = m_manager->registerEndpoint(endpoint); + m_capabilitiesObserver->onCapabilitiesStateChange( CapabilitiesDelegateObserverInterface::State::SUCCESS, CapabilitiesDelegateObserverInterface::Error::SUCCESS, @@ -330,9 +336,9 @@ TEST_F(EndpointRegistrationManagerTest, test_updateEndpointSucceeds) { EXPECT_CALL(*m_sequencer, addDirectiveHandler(addedHandler)).WillOnce(Return(true)); EXPECT_CALL(*m_sequencer, removeDirectiveHandler(handler2)).WillOnce(Return(true)); EXPECT_CALL(*endpoint, update(_)).WillOnce(Return(true)); - auto updateResult = m_manager->updateEndpoint(endpointId, std::make_shared(updatedData)); EXPECT_CALL(*m_registrationObserver, onEndpointUpdate(endpointId, _, UpdateResult::SUCCEEDED)); EXPECT_CALL(*m_registrationObserver, onPendingEndpointRegistrationOrUpdate(endpointId, _, _)); + auto updateResult = m_manager->updateEndpoint(endpointId, std::make_shared(updatedData)); m_capabilitiesObserver->onCapabilitiesStateChange( CapabilitiesDelegateObserverInterface::State::SUCCESS, CapabilitiesDelegateObserverInterface::Error::SUCCESS, @@ -417,15 +423,15 @@ TEST_F(EndpointRegistrationManagerTest, test_registerEndpointWhenCapabilityRegis EXPECT_CALL(*m_sequencer, removeDirectiveHandler(handler)).WillOnce(Return(true)); EXPECT_CALL(*m_capabilitiesDelegate, addOrUpdateEndpoint(_, configurations)).WillOnce(Return(true)); - // Check that register endpoint was enqueued. - auto result = m_manager->registerEndpoint(endpoint); - ASSERT_EQ(result.wait_for(std::chrono::milliseconds::zero()), std::future_status::timeout); - // Expect that the observer will be notified that the endpoint was registered. EXPECT_CALL( *m_registrationObserver, onEndpointRegistration(endpointId, _, RegistrationResult::CONFIGURATION_ERROR)); EXPECT_CALL(*m_registrationObserver, onPendingEndpointRegistrationOrUpdate(endpointId, _, _)); + // Check that register endpoint was enqueued. + auto result = m_manager->registerEndpoint(endpoint); + ASSERT_EQ(result.wait_for(std::chrono::milliseconds::zero()), std::future_status::timeout); + m_capabilitiesObserver->onCapabilitiesStateChange( CapabilitiesDelegateObserverInterface::State::FATAL_ERROR, CapabilitiesDelegateObserverInterface::Error::UNKNOWN_ERROR, @@ -716,9 +722,9 @@ TEST_F(EndpointRegistrationManagerTest, test_deregisterEndpointWhileUpdateInProg ASSERT_EQ(result.get(), RegistrationResult::SUCCEEDED); // Check that update endpoint enqueued. - auto updateResult = m_manager->updateEndpoint(endpointId, updatedData); EXPECT_CALL(*m_registrationObserver, onEndpointUpdate(endpointId, _, _)).Times(1); EXPECT_CALL(*m_registrationObserver, onPendingEndpointRegistrationOrUpdate(endpointId, _, _)).Times(1); + auto updateResult = m_manager->updateEndpoint(endpointId, updatedData); ASSERT_EQ(updateResult.wait_for(std::chrono::milliseconds::zero()), std::future_status::timeout); // Check that the deregistration fails. @@ -852,9 +858,9 @@ TEST_F(EndpointRegistrationManagerTest, test_updateEndpointWhileUpdateInProgress ASSERT_EQ(result.get(), RegistrationResult::SUCCEEDED); // Check that update endpoint enqueued. - auto deleteResult = m_manager->updateEndpoint(endpointId, updatedData); EXPECT_CALL(*m_registrationObserver, onEndpointUpdate(endpointId, _, _)).Times(1); EXPECT_CALL(*m_registrationObserver, onPendingEndpointRegistrationOrUpdate(endpointId, _, _)).Times(1); + auto deleteResult = m_manager->updateEndpoint(endpointId, updatedData); ASSERT_EQ(deleteResult.wait_for(std::chrono::milliseconds::zero()), std::future_status::timeout); // Check that the redundant update fails. diff --git a/Integration/AlexaClientSDKConfig.json b/Integration/AlexaClientSDKConfig.json index da213f9dde..d64f42a441 100644 --- a/Integration/AlexaClientSDKConfig.json +++ b/Integration/AlexaClientSDKConfig.json @@ -4,9 +4,6 @@ /// See https://developer.amazon.com/en-US/docs/alexa/avs-device-sdk/alexa-client-sdk-config-json.html /// to learn more about configuration options and default behaviours. { - "cblAuthDelegate":{ - "databaseFilePath":"${SDK_CBL_AUTH_DELEGATE_DATABASE_FILE_PATH}" - }, "deviceInfo":{ "deviceSerialNumber":"${SDK_CONFIG_DEVICE_SERIAL_NUMBER}", "clientId":"${SDK_CONFIG_CLIENT_ID}", @@ -27,6 +24,7 @@ "databaseFilePath":"${SDK_LWA_AUTHORIZATION_ADAPTER_DATABASE_FILE_PATH}" }, "speakerManagerCapabilityAgent":{ + "persistentStorage":true, "defaultSpeakerVolume":40, "defaultAlertsVolume":40, "restoreMuteState":true @@ -42,6 +40,12 @@ "locales":["en-US","en-GB","de-DE","en-IN","en-CA","ja-JP","en-AU","fr-FR","it-IT","es-ES","es-MX","fr-CA", "es-US", "hi-IN", "pt-BR", "ar-SA"], "defaultLocale":"en-US", + "davs": { + "artifactDirectory": "${SDK_DAVS_ARTIFACT_DIR}", + "segmentId": "${SDK_DAVS_SEGMENT_ID}", + "artifactBudgetSizeInMB": 100, + "pollingIntervalInSeconds": 0 + }, "localeCombinations":[ ["en-US", "es-US"], ["es-US", "en-US"], @@ -58,7 +62,21 @@ ["en-IN", "hi-IN"], ["hi-IN", "en-IN"], ["fr-CA", "en-CA"], - ["en-CA", "fr-CA"] + ["en-CA", "fr-CA"], + ["en-GB", "fr-FR"], + ["fr-FR", "en-GB"], + ["en-GB", "es-ES"], + ["es-ES", "en-GB"], + ["en-GB", "de-DE"], + ["de-DE", "en-GB"], + ["en-GB", "it-IT"], + ["it-IT", "en-GB"], + ["hi-IN", "en-US"], + ["en-US", "hi-IN"], + ["pt-BR", "en-US"], + ["en-US", "pt-BR"], + ["es-MX", "en-US"], + ["en-US", "es-MX"] ], "defaultTimezone":"America/Vancouver" }, @@ -75,9 +93,6 @@ "agentString": "CQCAFYNYDC" }, "sampleApp": { - "displayCardsSupported":true, - "sensory": { - "modelFilePath": "${SDK_SENSORY_MODEL_FILE_PATH}" - } + "displayCardsSupported":true } } diff --git a/Integration/include/Integration/ACLTestContext.h b/Integration/include/Integration/ACLTestContext.h deleted file mode 100644 index c01ec0165b..0000000000 --- a/Integration/include/Integration/ACLTestContext.h +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_ACLTESTCONTEXT_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_ACLTESTCONTEXT_H_ - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "Integration/AuthDelegateTestContext.h" -#include "Integration/ConnectionStatusObserver.h" -#include "Integration/SDKTestContext.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -/** - * Class providing lifecycle management of resources needed for testing ACL or functionality that - * requires ACL for testing. - */ -class ACLTestContext { -public: - /** - * Create an ACLTestContext. - * - * @note Only one instance of this class should exist at a time - but it is okay (and expected) - * that multiple instances of this class will be created (and destroyed) during one execution - * of the application using this class. - * - * Creating an instance of this class provides: - *
  • Initialization of the @c Alexa @c Client @c SDK (includes @ libcurl and @c ConfigurationNode.
  • - *
  • A @c CustomerDataManager instance.
  • - *
  • An @c AuthDelegateInterface instance.
  • - *
  • An @c AttachmentManager instance.
  • - *
  • A @c ConnectionStatusObserver instance.
  • - *
  • A @c ContextManager instance.
  • - *
  • Initialization of @c PostConnect.
  • - * - * @param filePath The path to a config file. - * @param overlay A @c JSON string containing values to overlay on the contents of the configuration file. - * @return An ACLTestContext instance or nullptr if the operation failed. - */ - static std::unique_ptr create(const std::string& filePath, const std::string& overlay = ""); - - /** - * Destructor. de-initializes all resources acquired during construction. - */ - ~ACLTestContext(); - - /** - * Get the instance of @c AuthDelegateInterface to use for the test. - * - * @return The instance of @c AuthDelegateInterface to use for the test. - */ - std::shared_ptr getAuthDelegate() const; - - /** - * Get the instance of @c CustomerDataManager to use for the test. - * - * @return The instance of @c CustomerDataManager to use for the test. - */ - std::shared_ptr getCustomerDataManager() const; - - /** - * Get the instance of @c AttachmentManager to use for the test. - * - * @return The instance of @c AttachmentManager to use for the test. - */ - std::shared_ptr getAttachmentManager() const; - - /** - * Get the @c MessageRouter instance to use for the test. - * - * @return The @c MessageRouter instance to use for the test. - */ - std::shared_ptr getMessageRouter() const; - - /** - * Get the @c ConnectionStatusObserver instance to use for the test. - * - * @return The @c ConnectionStatusObserver instance to use for the test. - */ - std::shared_ptr getConnectionStatusObserver() const; - - /** - * Get the @c ContextManager instance to use for the test. - * - * @return The @c ContextManager instance to use for the test. - */ - std::shared_ptr getContextManager() const; - - /** - * Wait for the @c ConnectionStatusObserver to be notified that the client has successfully connected to @c AVS. - */ - void waitForConnected(); - - /** - * Wait for the @c ConnectionStatusObserver to be notified that the client has successfully disconnected - * from @c AVS. - */ - void waitForDisconnected(); - -private: - /** - * Constructor. - * - * @param filePath The path to a config file. - * @param overlay A @c JSON string containing values to overlay on the contents of the configuration file. - */ - ACLTestContext(const std::string& filePath, const std::string& overlay = ""); - - /// Provide and AuthDelegate implementation suitable for testing. - std::unique_ptr m_authDelegateTestContext; - - /// The Object to use to manage attachments. - std::shared_ptr m_attachmentManager; - - /// Object that routs messages from @c AVS. - std::shared_ptr m_messageRouter; - - /// Object to monitor the status of the connection with @c AVS. - std::shared_ptr m_connectionStatusObserver; - - /// Object to acquire SDK context. - std::shared_ptr m_contextManager; -}; - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_ACLTESTCONTEXT_H_ diff --git a/Integration/include/Integration/AipStateObserver.h b/Integration/include/Integration/AipStateObserver.h deleted file mode 100644 index 38378b7ccd..0000000000 --- a/Integration/include/Integration/AipStateObserver.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_AIPSTATEOBSERVER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_AIPSTATEOBSERVER_H_ - -#include -#include -#include -#include - -#include - -namespace alexaClientSDK { -namespace integration { - -class AipStateObserver : public avsCommon::sdkInterfaces::AudioInputProcessorObserverInterface { -public: - AipStateObserver(); - void onStateChanged(avsCommon::sdkInterfaces::AudioInputProcessorObserverInterface::State newState) override; - bool checkState( - const avsCommon::sdkInterfaces::AudioInputProcessorObserverInterface::State expectedState, - const std::chrono::seconds duration = std::chrono::seconds(10)); - avsCommon::sdkInterfaces::AudioInputProcessorObserverInterface::State waitForNext( - const std::chrono::seconds duration); - -private: - avsCommon::sdkInterfaces::AudioInputProcessorObserverInterface::State m_state; - std::mutex m_mutex; - std::condition_variable m_wakeTrigger; - std::deque m_queue; -}; - -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_AIPSTATEOBSERVER_H_ diff --git a/Integration/include/Integration/AuthDelegateTestContext.h b/Integration/include/Integration/AuthDelegateTestContext.h deleted file mode 100644 index 139c053580..0000000000 --- a/Integration/include/Integration/AuthDelegateTestContext.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_AUTHDELEGATETESTCONTEXT_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_AUTHDELEGATETESTCONTEXT_H_ - -#include -#include - -#include - -#include -#include -#include -#ifdef ACSDK_ACS_UTILS -#include -#else -#include -#endif -#include -#include - -#include "Integration/ConnectionStatusObserver.h" -#include "Integration/SDKTestContext.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -/** - * Class providing lifecycle management of resources needed for testing instances of AuthDelegateInterface or - * functionality that requires such instances for testing. - */ -class AuthDelegateTestContext { -public: - /** - * Create an AuthDelegateTestContext - * - * @note Only one instance of this class should exist at a time - but it is okay (and expected) - * that multiple instances of this class will be created (and destroyed) during one execution - * of the application using this class. - * - * Creating an instance of this class provides: - *
  • A @c CustomerDataManager instance.
  • - *
  • An @c AuthDelegateInterface instance.
  • - * - * @param filePath The path to a config file. - * @param overlay A @c JSON string containing values to overlay on the contents of the configuration file. - */ - static std::unique_ptr create( - const std::string& filePath, - const std::string& overlay = ""); - - /** - * Destructor. de-initializes all resources acquired during construction. - */ - ~AuthDelegateTestContext(); - - /** - * Determine whether or not this instance was properly initialized. - * - * @return Whether or not this instance was properly initialized. - */ - bool isValid() const; - - /** - * Get the instance of @c AuthDelegateInterface to use for the test. - * - * @return The instance of @c AuthDelegateInterface to use for the test. - */ - std::shared_ptr getAuthDelegate() const; - - /** - * Get the instance of @c CustomerDataManager to use for the test. - * - * @return The instance of @c CustomerDataManager to use for the test. - */ - std::shared_ptr getCustomerDataManager() const; - -private: -#ifdef ACSDK_ACS_UTILS - class AuthRequester : public acsdkffsAuthDelegate::FFSAuthRequesterInterface { - public: - void onRequestFFS() override; - }; -#else - /** - * Implementation of @c CBLAuthRequesterInterface used to detect the case where the user - * still needs to authorize access to @c AVS. - */ - class AuthRequester : public authorization::cblAuthDelegate::CBLAuthRequesterInterface { - public: - /// @name CBLAuthRequesterInterface methods - /// @{ - void onRequestAuthorization(const std::string& url, const std::string& code) override; - void onCheckingForAuthorization() override; - /// @} - }; -#endif - /** - * Constructor. - * - * @param filePath The path to a config file. - * @param overlay A @c JSON string containing values to overlay on the contents of the configuration file. - */ - AuthDelegateTestContext(const std::string& filePath, const std::string& overlay = ""); - - /// Provide and SDK initialization suitable for testing. - std::unique_ptr m_sdkTestContext; - - /// The AuthDelegate to use for authorizing with LWA and AVS. - std::shared_ptr m_authDelegate; - - /// Object to manage customer specific data. - std::shared_ptr m_customerDataManager; -}; - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_AUTHDELEGATETESTCONTEXT_H_ diff --git a/Integration/include/Integration/AuthObserver.h b/Integration/include/Integration/AuthObserver.h deleted file mode 100644 index a0c4ed4e4e..0000000000 --- a/Integration/include/Integration/AuthObserver.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_AUTHOBSERVER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_AUTHOBSERVER_H_ - -#include -#include -#include - -#include - -namespace alexaClientSDK { -namespace integration { - -class AuthObserver : public avsCommon::sdkInterfaces::AuthObserverInterface { -public: - AuthObserver(); - void onAuthStateChange( - const avsCommon::sdkInterfaces::AuthObserverInterface::State, - const avsCommon::sdkInterfaces::AuthObserverInterface::Error = - avsCommon::sdkInterfaces::AuthObserverInterface::Error::SUCCESS) override; - AuthObserverInterface::State getAuthState() const; - bool waitFor( - const avsCommon::sdkInterfaces::AuthObserverInterface::State, - const std::chrono::seconds = std::chrono::seconds(20)); - -private: - avsCommon::sdkInterfaces::AuthObserverInterface::State m_authState; - avsCommon::sdkInterfaces::AuthObserverInterface::Error m_authError; - std::mutex m_mutex; - std::condition_variable m_wakeTrigger; -}; - -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_AUTHOBSERVER_H_ diff --git a/Integration/include/Integration/ClientMessageHandler.h b/Integration/include/Integration/ClientMessageHandler.h deleted file mode 100644 index 233b7ea186..0000000000 --- a/Integration/include/Integration/ClientMessageHandler.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_CLIENTMESSAGEHANDLER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_CLIENTMESSAGEHANDLER_H_ - -#include -#include -#include -#include - -#include - -#include - -namespace alexaClientSDK { -namespace integration { - -using namespace alexaClientSDK::avsCommon::sdkInterfaces; - -/// Minimal implementation of a message observer for integration tests. -class ClientMessageHandler : public MessageObserverInterface { -public: - /// Constructor. - ClientMessageHandler(std::shared_ptr attachmentManager); - - /** - * Implementation of the interface's receive function. - * - * For the purposes of these integration tests, this function simply logs and counts messages. - */ - void receive(const std::string& contextId, const std::string& message) override; - - /** - * Wait for a message to be received. - * - * This function waits for a specified number of seconds for a message to arrive. - * @param duration Number of seconds to wait before giving up. - * @return true if a message was received within the specified duration, else false. - */ - bool waitForNext(const std::chrono::seconds duration = std::chrono::seconds(2)); - -private: - /// Mutex to protect m_count. - std::mutex m_mutex; - /// Trigger to wake up waitForNext calls. - std::condition_variable m_wakeTrigger; - /// Count of received messages that have not been waited on. - unsigned int m_count; - - std::shared_ptr m_attachmentManager; -}; - -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_CLIENTMESSAGEHANDLER_H_ diff --git a/Integration/include/Integration/ConnectionStatusObserver.h b/Integration/include/Integration/ConnectionStatusObserver.h deleted file mode 100644 index db81005f0f..0000000000 --- a/Integration/include/Integration/ConnectionStatusObserver.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_CONNECTIONSTATUSOBSERVER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_CONNECTIONSTATUSOBSERVER_H_ - -#include -#include -#include -#include -#include - -namespace alexaClientSDK { -namespace integration { - -/** - * The class implements ConnectionStatusObserverInterface for testing. - */ -class ConnectionStatusObserver : public avsCommon::sdkInterfaces::ConnectionStatusObserverInterface { -public: - /** - * ConnectionStatusObserver constructor. - */ - ConnectionStatusObserver(); - - void onConnectionStatusChanged( - const avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::Status connectionStatus, - const avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override; - /** - * The utility function to get the connection status. - * @return Status The @c connectionStatus for the connection. - */ - avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::Status getConnectionStatus() const; - - /** - * Function to allow waiting for an expected status when a connection or disconnection is done. - * @param connectionStatus The expected connection status for which the waiting is done. - * @param duration The maximum time waiting for the expected connectionStatus. - * @return true if expected connectionStatus is received within @c duration else false. - */ - bool waitFor( - const avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::Status connectionStatus, - const std::chrono::seconds duration = std::chrono::seconds(15)); - - /** - * Function to check if the connection is broken due to Server side Disconnect. - * @return true if the disconnect happens due to SERVER_SIDE_DISCONNECT else false. - */ - bool checkForServerSideDisconnect(); - -private: - /// Mutex used internally to enforce thread safety and serialize read/write access to @c m_statusChanges. - mutable std::mutex m_mutex; - /// The cv used when waiting for a particular status of a connection - std::condition_variable m_wakeTrigger; - /// The queue of values of the pair (Connection status, ChangedReason) throughout the connection. - std::deque> - m_statusChanges; -}; - -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_CONNECTIONSTATUSOBSERVER_H_ diff --git a/Integration/include/Integration/JsonHeader.h b/Integration/include/Integration/JsonHeader.h deleted file mode 100644 index ae736d0cb5..0000000000 --- a/Integration/include/Integration/JsonHeader.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_JSONHEADER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_JSONHEADER_H_ - -#include - -// Todo ACSDK-443: Move the JSON to text file -/// This is a basic synchronize JSON message which may be used to initiate a connection with AVS. -// clang-format off -static const std::string SYNCHRONIZE_STATE_JSON = - "{" - "\"context\":[{" - "\"header\":{" - "\"name\":\"SpeechState\"," - "\"namespace\":\"SpeechSynthesizer\"" - "}," - "\"payload\":{" - "\"playerActivity\":\"FINISHED\"," - "\"offsetInMilliseconds\":0," - "\"token\":\"\"" - "}" - "}]," - "\"event\":{" - "\"header\":{" - "\"messageId\":\"00000000-0000-0000-0000-000000000000\"," - "\"name\":\"SynchronizeState\"," - "\"namespace\":\"System\"" - "}," - "\"payload\":{" - "}" - "}" - "}"; -// clang-format on - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_JSONHEADER_H_ diff --git a/Integration/include/Integration/ObservableMessageRequest.h b/Integration/include/Integration/ObservableMessageRequest.h deleted file mode 100644 index 8f72cda42b..0000000000 --- a/Integration/include/Integration/ObservableMessageRequest.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_OBSERVABLEMESSAGEREQUEST_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_OBSERVABLEMESSAGEREQUEST_H_ - -#include -#include -#include -#include - -#include - -namespace alexaClientSDK { -namespace integration { - -class ObservableMessageRequest : public avsCommon::avs::MessageRequest { -public: - /** - * Constructor. - */ - ObservableMessageRequest( - const std::string& jsonContent, - std::shared_ptr attachmentReader = nullptr); - - void sendCompleted(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status status) override; - - void exceptionReceived(const std::string& exceptionMessage) override; - - /** - * Utility function to get the status once the message has been sent. - */ - avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status getSendMessageStatus() const; - - /** - * Function to allow waiting for a particular status back from the component sending the message to AVS. - */ - bool waitFor( - const avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status, - const std::chrono::seconds = std::chrono::seconds(10)); - /// Function indicating if sendCompleted has been called - bool hasSendCompleted(); - /// Function indicating if exceptionReceived has been called - bool wasExceptionReceived(); - -private: - /// The status of whether the message was sent to AVS ok. - avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status m_sendMessageStatus; - /// Mutex used internally to enforce thread safety. - mutable std::mutex m_mutex; - /// The cv used when waiting for a particular status of a message being sent. - std::condition_variable m_wakeTrigger; - /// The variable that gets set when send is completed. - std::atomic m_sendCompleted; - /// The variable that gets set when exception is received. - std::atomic m_exceptionReceived; -}; - -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_OBSERVABLEMESSAGEREQUEST_H_ diff --git a/Integration/include/Integration/SDKTestContext.h b/Integration/include/Integration/SDKTestContext.h deleted file mode 100644 index 013e92bf82..0000000000 --- a/Integration/include/Integration/SDKTestContext.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_SDKTESTCONTEXT_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_SDKTESTCONTEXT_H_ - -#include -#include - -namespace alexaClientSDK { -namespace integration { -namespace test { - -/** - * Class providing lifecycle management of resources needed for testing the @c Alexa @c Client @c SDK. - */ -class SDKTestContext { -public: - /** - * Create an SDKTestContext - * - * @note Only one instance of this class should exist at a time - but it is okay (and expected) - * that multiple instances of this class will be created (and destroyed) during one execution - * of the application using this class. - * - * Creating an instance of this class provides initialization of the @c Alexa @c Client @c SDK - * (which includes initialization of @ libcurl and @c ConfigurationNode. - * - * @param filePath The path to a config file. - * @param overlay A @c JSON string containing values to overlay on the contents of the configuration file. - * @return An SDKTestContext instance or nullptr if the operation failed. - */ - static std::unique_ptr create(const std::string& filePath, const std::string& overlay = ""); - - /** - * Destructor de-initializes the @c Alexa @c Client @c SDK. - */ - ~SDKTestContext(); - -private: - /** - * Constructor. - * - * @param filePath The path to a config file. - * @param overlay A @c JSON string containing values to overlay on the contents of the configuration file. - */ - SDKTestContext(const std::string& filePath, const std::string& overlay); -}; - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_SDKTESTCONTEXT_H_ diff --git a/Integration/include/Integration/TestAlertObserver.h b/Integration/include/Integration/TestAlertObserver.h deleted file mode 100644 index b0f9c39d7a..0000000000 --- a/Integration/include/Integration/TestAlertObserver.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_TESTALERTOBSERVER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTALERTOBSERVER_H_ - -#include -#include -#include -#include -#include - -#include - -namespace alexaClientSDK { -namespace integration { -namespace test { - -class TestAlertObserver : public acsdkAlertsInterfaces::AlertObserverInterface { -public: - void onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) override; - - class changedAlert { - public: - State state; - }; - - changedAlert waitForNext(const std::chrono::seconds duration); - -private: - std::mutex m_mutex; - std::condition_variable m_wakeTrigger; - std::deque m_queue; - State currentState; -}; - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTALERTOBSERVER_H_ diff --git a/Integration/include/Integration/TestDirectiveHandler.h b/Integration/include/Integration/TestDirectiveHandler.h deleted file mode 100644 index 604bca98bc..0000000000 --- a/Integration/include/Integration/TestDirectiveHandler.h +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_TESTDIRECTIVEHANDLER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTDIRECTIVEHANDLER_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "AVSCommon/SDKInterfaces/DirectiveHandlerInterface.h" -#include "AVSCommon/AVS/Attachment/AttachmentManager.h" -#include "AVSCommon/Utils/JSON/JSONUtils.h" - -using namespace alexaClientSDK::avsCommon; - -namespace alexaClientSDK { -namespace integration { -namespace test { - -/** - * TestDirectiveHandler is a mock of the @c DirectiveHandlerInterface and allows tests - * to wait for invocations upon those interfaces and inspect the parameters of those invocations. - */ -class TestDirectiveHandler : public avsCommon::sdkInterfaces::DirectiveHandlerInterface { -public: - /** - * Constructor. - * - * @param config The @c avsCommon::avs::DirectiveHandlerConfiguration for the directive handler for registering - * with a directive sequencer. - */ - TestDirectiveHandler(avsCommon::avs::DirectiveHandlerConfiguration config); - - void handleDirectiveImmediately(std::shared_ptr directive) override; - - void preHandleDirective( - std::shared_ptr directive, - std::unique_ptr result) override; - - bool handleDirective(const std::string& messageId) override; - - void cancelDirective(const std::string& messageId) override; - - avsCommon::avs::DirectiveHandlerConfiguration getConfiguration() const override; - - void onDeregistered() override; - - /** - * Class defining the parameters to calls to the mocked interfaces. - */ - class DirectiveParams { - public: - /** - * Constructor. - */ - DirectiveParams(); - - /** - * Return whether this DirectiveParams is of type 'UNSET'. - * - * @return Whether this DirectiveParams is of type 'UNSET'. - */ - bool isUnset() const { - return Type::UNSET == type; - } - - /** - * Return whether this DirectiveParams is of type 'HANDLE_IMMEDIATELY'. - * - * @return Whether this DirectiveParams is of type 'HANDLE_IMMEDIATELY'. - */ - bool isHandleImmediately() const { - return Type::HANDLE_IMMEDIATELY == type; - } - - /** - * Return whether this DirectiveParams is of type 'PREHANDLE'. - * - * @return Whether this DirectiveParams is of type 'PREHANDLE'. - */ - bool isPreHandle() const { - return Type::PREHANDLE == type; - } - - /** - * Return whether this DirectiveParams is of type 'HANDLE'. - * - * @return Whether this DirectiveParams is of type 'HANDLE'. - */ - bool isHandle() const { - return Type::HANDLE == type; - } - - /** - * Return whether this DirectiveParams is of type 'CANCEL'. - * - * @return Whether this DirectiveParams is of type 'CANCEL'. - */ - bool isCancel() const { - return Type::CANCEL == type; - } - - /** - * Return whether this DirectiveParams is of type 'TIMEOUT'. - * - * @return Whether this DirectiveParams is of type 'TIMEOUT'. - */ - bool isTimeout() const { - return Type::TIMEOUT == type; - } - - // Enum for the way the directive was passed to DirectiveHandler. - enum class Type { - // Not yet set. - UNSET, - // Set when handleDirectiveImmediately is called. - HANDLE_IMMEDIATELY, - // Set when preHandleDirective is called. - PREHANDLE, - // Set when handleDirective is called. - HANDLE, - // Set when cancelDirective is called. - CANCEL, - // Set when waitForNext times out waiting for a directive. - TIMEOUT - }; - - // Type of how the directive was passed to DirectiveHandler. - Type type; - // AVSDirective passed from the Directive Sequencer to the DirectiveHandler. - std::shared_ptr directive; - // DirectiveHandlerResult to inform the Directive Sequencer a directive has either successfully or - // unsuccessfully handled. - std::shared_ptr result; - }; - - /** - * Function to retrieve the next DirectiveParams in the test queue or time out if the queue is empty. Takes a - * duration in seconds to wait before timing out. - */ - DirectiveParams waitForNext(const std::chrono::seconds duration); - -private: - /// Mutex to protect m_queue. - std::mutex m_mutex; - /// Trigger to wake up waitForNext calls. - std::condition_variable m_wakeTrigger; - /// Queue of received directives that have not been waited on. - std::deque m_queue; - /// map of message IDs to result handlers. - std::unordered_map> - m_results; - /// map of message IDs to result handlers. - std::unordered_map> m_directives; - /// The @c avsCommon::avs::DirectiveHandlerConfiguration of the handler. - avsCommon::avs::DirectiveHandlerConfiguration m_configuration; -}; -} // namespace test -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTDIRECTIVEHANDLER_H_ diff --git a/Integration/include/Integration/TestExceptionEncounteredSender.h b/Integration/include/Integration/TestExceptionEncounteredSender.h deleted file mode 100644 index 0df39f10a3..0000000000 --- a/Integration/include/Integration/TestExceptionEncounteredSender.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_TESTEXCEPTIONENCOUNTEREDSENDER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTEXCEPTIONENCOUNTEREDSENDER_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "AVSCommon/SDKInterfaces/DirectiveHandlerInterface.h" -#include "AVSCommon/SDKInterfaces/ExceptionEncounteredSenderInterface.h" -#include "AVSCommon/AVS/Attachment/AttachmentManager.h" -#include "AVSCommon/Utils/JSON/JSONUtils.h" - -using namespace alexaClientSDK::avsCommon; - -namespace alexaClientSDK { -namespace integration { -namespace test { - -/** - * TestExceptionEncounteredSender is a mock of the @c ExceptionEncounteredSenderInterface and allows tests - * to wait for invocations upon those interfaces and inspect the parameters of those invocations. - */ -class TestExceptionEncounteredSender : public avsCommon::sdkInterfaces::ExceptionEncounteredSenderInterface { -public: - void sendExceptionEncountered( - const std::string& unparsedDirective, - avsCommon::avs::ExceptionErrorType error, - const std::string& message) override; - /** - * Parse an @c AVSDirective from a JSON string. - * - * @param rawJSON The JSON to parse. - * @param attachmentManager The @c AttachmentManager to initialize the new @c AVSDirective with. - * @return A new @c AVSDirective, or nullptr if parsing the JSON fails. - */ - std::shared_ptr parseDirective( - const std::string& rawJSON, - std::shared_ptr attachmentManager); - - /** - * Class defining the parameters to calls to the mocked interfaces. - */ - class ExceptionParams { - public: - /** - * Constructor. - */ - ExceptionParams(); - - // Enum for the way the directive was passed. - enum class Type { - // Not yet set. - UNSET, - // Set when sendExceptionEncountered is called. - EXCEPTION, - // Set when waitForNext times out waiting for a directive. - TIMEOUT - }; - - // Type of how the directive was passed. - Type type; - // AVSDirective passed from the Directive Sequencer. - std::shared_ptr directive; - // Unparsed directive string passed to sendExceptionEncountered. - std::string exceptionUnparsedDirective; - // Error type passed to sendExceptionEncountered. - avsCommon::avs::ExceptionErrorType exceptionError; - // Additional information passed to sendExceptionEncountered. - std::string exceptionMessage; - }; - - /** - * Function to retrieve the next DirectiveParams in the test queue or time out if the queue is empty. Takes a - * duration in seconds to wait before timing out. - */ - ExceptionParams waitForNext(const std::chrono::seconds duration); - -private: - /// Mutex to protect m_queue. - std::mutex m_mutex; - /// Trigger to wake up waitForNext calls. - std::condition_variable m_wakeTrigger; - /// Queue of received directives that have not been waited on. - std::deque m_queue; -}; - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTEXCEPTIONENCOUNTEREDSENDER_H_ diff --git a/Integration/include/Integration/TestMediaPlayer.h b/Integration/include/Integration/TestMediaPlayer.h deleted file mode 100644 index e445af4ff4..0000000000 --- a/Integration/include/Integration/TestMediaPlayer.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_TESTMEDIAPLAYER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTMEDIAPLAYER_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "AVSCommon/Utils/MediaType.h" -#include "AVSCommon/Utils/Timing/Timer.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -/** - * A Mock MediaPlayer that attempts to alert the observer of playing and stopping without - * actually playing audio. This removes the dependancy on an audio player to run tests with - * SpeechSynthesizer - */ -class TestMediaPlayer : public avsCommon::utils::mediaPlayer::MediaPlayerInterface { -public: - // Destructor. - ~TestMediaPlayer(); - - avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId setSource( - std::shared_ptr attachmentReader, - const avsCommon::utils::AudioFormat* audioFormat = nullptr, - const avsCommon::utils::mediaPlayer::SourceConfig& config = - avsCommon::utils::mediaPlayer::emptySourceConfig()) override; - - avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId setSource( - std::shared_ptr attachmentReader, - std::chrono::milliseconds offsetAdjustment, - const avsCommon::utils::AudioFormat* audioFormat = nullptr, - const avsCommon::utils::mediaPlayer::SourceConfig& config = - avsCommon::utils::mediaPlayer::emptySourceConfig()) override; - - avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId setSource( - std::shared_ptr stream, - bool repeat, - const avsCommon::utils::mediaPlayer::SourceConfig& config = avsCommon::utils::mediaPlayer::emptySourceConfig(), - avsCommon::utils::MediaType format = avsCommon::utils::MediaType::UNKNOWN) override; - - avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId setSource( - const std::string& url, - std::chrono::milliseconds offset = std::chrono::milliseconds::zero(), - const avsCommon::utils::mediaPlayer::SourceConfig& config = avsCommon::utils::mediaPlayer::emptySourceConfig(), - bool repeat = false, - const avsCommon::utils::mediaPlayer::PlaybackContext& playbackContext = - avsCommon::utils::mediaPlayer::PlaybackContext()) override; - - bool play(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) override; - - bool stop(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) override; - - bool pause(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) override; - - bool resume(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) override; - - std::chrono::milliseconds getOffset(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) override; - - void addObserver( - std::shared_ptr playerObserver) override; - - void removeObserver( - std::shared_ptr playerObserver) override; - - avsCommon::utils::Optional getMediaPlayerState( - avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) override; - - uint64_t getNumBytesBuffered() override; - -private: - /// Observers to notify of state changes. - std::unordered_set> m_observers; - /// Flag to indicate when a playback finished notification has been sent to the observer. - bool m_playbackFinished = false; - /// The AttachmentReader to read audioData from. - std::shared_ptr m_attachmentReader; - /// Timer to wait to send onPlaybackFinished to the observer. - std::shared_ptr m_timer; - // istream for Alerts. - std::shared_ptr m_istream; -}; -} // namespace test -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTMEDIAPLAYER_H_ diff --git a/Integration/include/Integration/TestMessageSender.h b/Integration/include/Integration/TestMessageSender.h deleted file mode 100644 index 66bcc24460..0000000000 --- a/Integration/include/Integration/TestMessageSender.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_TESTMESSAGESENDER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTMESSAGESENDER_H_ - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace alexaClientSDK { -namespace integration { -namespace test { - -class TestMessageSender - : public avsCommon::sdkInterfaces::MessageSenderInterface - , public avsCommon::utils::RequiresShutdown { -public: - /// Destructor. - ~TestMessageSender() = default; - - void sendMessage(std::shared_ptr request) override; - - TestMessageSender( - std::shared_ptr messageRouter, - bool isEnabled, - std::shared_ptr connectionStatusObserver, - std::shared_ptr messageObserver); - - class SendParams { - public: - enum class Type { SEND, TIMEOUT }; - Type type; - std::shared_ptr request; - }; - - SendParams waitForNext(const std::chrono::seconds duration); - - /** - * 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(); - - /** - * Set the gateway URL for the AVS connection. Calling this function with a new value will cause the - * current active connection to be closed, and a new one opened to the new gateway. - * @param avsGateway The URL for the new AVS gateway. - */ - void setAVSGateway(const std::string& avsGateway); - - void addConnectionStatusObserver( - std::shared_ptr observer); - - /** - * Removes an observer from being notified of connection status changes. - * - * @param observer The observer object to remove. - */ - void removeConnectionStatusObserver( - std::shared_ptr observer); - - /** - * 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 doShutdown() override; - - std::shared_ptr getConnectionManager() const; - -private: - /// Mutex to protect m_queue. - std::mutex m_mutex; - /// Trigger to wake up waitForNext calls. - std::condition_variable m_wakeTrigger; - /// Queue of received directives that have not been waited on. - std::deque m_queue; - - std::shared_ptr m_connectionManager; -}; - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTMESSAGESENDER_H_ diff --git a/Integration/include/Integration/TestSpeechSynthesizerObserver.h b/Integration/include/Integration/TestSpeechSynthesizerObserver.h deleted file mode 100644 index 56cb3a5dbc..0000000000 --- a/Integration/include/Integration/TestSpeechSynthesizerObserver.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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_INTEGRATION_INCLUDE_INTEGRATION_TESTSPEECHSYNTHESIZEROBSERVER_H_ -#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTSPEECHSYNTHESIZEROBSERVER_H_ - -#include -#include -#include -#include - -#include - -namespace alexaClientSDK { -namespace integration { -namespace test { - -/** - * Interface for observing a SpeechSynthesizer. - */ -class TestSpeechSynthesizerObserver : public avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface { -public: - TestSpeechSynthesizerObserver(); - - ~TestSpeechSynthesizerObserver() = default; - - void onStateChanged( - avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState state, - const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId mediaSourceId, - const avsCommon::utils::Optional& mediaPlayerState, - const std::vector& audioAnalyzerState) override; - - bool checkState( - const avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState expectedState, - const std::chrono::seconds duration); - - avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState waitForNext( - const std::chrono::seconds duration); - - avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState getCurrentState(); - -private: - avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface::SpeechSynthesizerState m_state; - std::mutex m_mutex; - std::condition_variable m_wakeTrigger; - std::deque m_queue; -}; - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_INTEGRATION_TESTSPEECHSYNTHESIZEROBSERVER_H_ diff --git a/Integration/inputs/alexa_recognize_joke_test.wav b/Integration/inputs/alexa_recognize_joke_test.wav deleted file mode 100755 index 5d67f82b8cbbe4342a03c44ca983bafe3e3983b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 403284 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 b!m3+G5T;j>U?W;C}7 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 z3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36A zpnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim} z0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7j zD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUg zfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7> z3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36A zpnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim} z0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7j zD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUg zfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7> z3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36A zpnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim} z0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7j zD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUg zfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7> z3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36A zpnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim}0tzUgfC36Apnw7jD4>7>3Mim} d0tzUgfC36Apnw7jD4>7>3Mim}0{;&J{{y9g=&t|( diff --git a/Integration/inputs/alexa_recognize_silence_test.wav b/Integration/inputs/alexa_recognize_silence_test.wav deleted file mode 100755 index 06f49fe699864c9cbd37a6bd0457034ed9cbb66c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1690702 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 zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 V2oNAZfB*pk1PBly@c&!j{{cisy9fXP diff --git a/Integration/inputs/alexa_recognize_wiki_test.wav b/Integration/inputs/alexa_recognize_wiki_test.wav deleted file mode 100755 index 6b976e4afc804bc2adac7945da978a40f05c018f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236638 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 z3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbF zC_n)UP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epy zpa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+ zfC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O z0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC z1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo z6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)U zP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZ zKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3cw H|0M8#$>lZ> diff --git a/Integration/inputs/recognize_flashbriefing_test.wav b/Integration/inputs/recognize_flashbriefing_test.wav deleted file mode 100755 index 2719b0e9b28c30ca63372f53c7666e9636c47026..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39134 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%0A?y;3g2~ln!{O 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®yR?C%S>?!+*vf#rNQ!7t9vFhE+0L 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 diff --git a/Integration/inputs/recognize_lions_test.wav b/Integration/inputs/recognize_lions_test.wav deleted file mode 100755 index 1c42a3b45d8d4c00efd779112606d19dee1dea83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196592 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 z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk zH#>A7000000ObGL2nh}xIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!1K6Ph0RR9103iR@Mo4hr zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*= zfddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede z;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQq zIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n? z4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj! z0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^` zz<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!K zaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB) z95`^`z<~n?4jede;J|?c2M!!KaNxj!0|yQqIB?*=fddB)95`^`z<~n?4jede;J|?c z2M!!KaNxj!0|yQqIB?*=fddB)9MBFO$N&HU01WcC{_ar-88BeLfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r T3>YwAz<>b*1`HT5VBmoOwqgvh diff --git a/Integration/inputs/recognize_sing_song_test.wav b/Integration/inputs/recognize_sing_song_test.wav deleted file mode 100755 index 39505d8a0d469e5008ad7438883e998afdf29094..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38124 zcmeEt1#=r&*Yxm=m}OgLW_BEh9cHEtGjle)Va|rLVaClS4l_B4?Zl3mIcCc!%fgW~ zl4f50jPI*@q$)|HDphH^?mf4=PoGI6hYf2!4*=5#P8+&(%_c7)005xlxwpUL!!7`T z03k4J%$P|z0>}0L9{)q&e+c{!f&U@!KLq}V!2b~V9|HeF;C~4G4}t$7@IM6phrs_= z2rOQ(X@TnC|I2?F1ZV~UjEpbP0~pF&V|If-fUV3@rk1G&H!*+FTCf-J7d;h91&-Pd z0yhDYUIDrS{!9!o0^G=4v;PfD2I_5}nG!&6e`_DX?1PrjPwmOje_$eg7TAnhnZwrK zKr6JJhUq3?7&HWMw(4m$w1|F1_k%i8ptTN{Z zhI1{mq$*KW`xwJ5(OB?aTZeswv&=j}>nC~3Zg0C~Bb@)R<#*K*Eb%eTHT`dX2ztVx z(Ys07xQp6f+Gji4t+6di&RXd%Rk|K@yTS09i!s<)um93`S?29bH@s3$@_h;qtjgvT zd8M_5b(lP-vU}DVku2Bc-5%}pol5y58*H|2Pql4KVOL<&j(6%o2S~1YY zXeT<;2)peFyjE0fFI6kx`H}?PqwX?!C3?Fv5J1u}%mTT$JY3Fdo ziq4Cc2B$t$T}LZ_CvR!n95T@Pw*6T99sUw_rTUvqD{i7f+S7%zM9)(be}tC`oH`dVa_L6e+Vvh=CK}Rx z(i+ckp^sTsA>Evt`nSdnT*&@Pdmjhc9FvOvi}+=BqdZxm)G_i;ac}%&`!)L`=g;K( z);eCa{7}b3s;A3z^UBV07<8((TyMN1iIL|u>^3a%;u*l!Ag9YBQP(6}3!6lGn)Iek zE+)v_@kQ`MnAZ{8dBOPywx;$p8tJk|R;vHyd=MVh&#*k@oT9AyeTrb>&e}&dZ;zaw*n`WE|NFKu)x~u$Ei>Rc_p7(Y2QE zwte5gbMvjjTsw?Muv6cono-FXT=}D^z_s zV~O@I2tLJnWP4(s$!bD5h7nLQN}Dz^+j*3s%@QC!t!GG$=nJ`CGgJH;7}>dn%jV)8 zOTgn!(fTm;7};wf4|&|MqJ!pRDZfG{FUF?N%-E`ID7fCgD$)U+B?D&rwU_0@Sh3gU4>kk;u4V~+8QWxU;dC3M)=BIyb`~`ph==}S{v}GmBr=bAh4#eX zTmDu*7AkOFS2OY*7aCsy``Ll?Yzv?M&7J@^=whi|l6kh19e0H>yu|KLwgG}U)~$vp zY&TkoY$sh!Y)jLEbK42Vf$q`Po87$AhGuGj- z95{|%0jSPFk|uC#-Ad^+{EAk@U1!<@vG|+2=9rJ8>-Zh^aq8QG(Foo(ReaPY(4LWO zr~Y67$YSn=7bB^PM_vvvjCl2MC$~sw&NDQcqsf?8w&K}9i=F8BrAo} zPxD-TSiYXeYj3oCm!CrZRek{Y9vgLK&GX%k^H;a>+7jItyKoAw<3`W)wl&rf*iIG& z?otb|C*p;c0meFf6ZH=m%`CDEVSfPcQ4#b-Zn0%!7hgVuJ4nd}U0rqN)Q&Qwrzn%W z-`&FBB;a=aM`j7*(FL8Y^f6v4az!)4Y~tTQqSZ0xVLVqzU?Ra8yeSrUCL1qr4jBx?$^1Dr=t0o~O?gXtZ#gUc}| z(1r9%Xg|FZaHYBS_tTGQv(fn%%_et4-|poY1CI;hv}`TC-BnD zrxI9$*#CAW27z;gPB)qJ;~ZCK_LH+gKkvzJNVse8WopT3!#BVmM?d^h%V}z76!2{-eHS za+kQ%xayqH4OMpSfac?A=4fC(T41|`|F*n<)`Q>7uP`s%$=*nL(VN*Q|9K+<9Ckh0 z_1cswsi3mDVgyO>_wGEPFQx_}zzO7Nd!!)Prd3tri2_&KMx7mwQ|vXLY70Qdx}4EX zY033o!i}r$ga0epXgb`@v*NBaaHTO=^tUueTWfv~_a*#nALw-MABL0q-P~l`4D(j- zPxvz!VI6Fr2<-x^O|kGeI?Z~a{UW#pgGq(K^$ZsVKtf~A(2zL4ru%2MS=T738x)W?|d?S(4atc}P`COHx zy@<~hT%gP?Gvp^Z4fQ&xO2XDnMP8sqk zH0om!*y=?e^mp_>rJD`7@e->)6U|td8B8$}peaWaCAXX7@spx8?Q-5*cxhKSTLOHt zR$|GPaj;By#V}o0$yv#Y(H{f8uumc>wq%npcn$0i&Y=2(XW-vtAiRz3q5Wu4sRT*%V5>r66Z6IyCB;?LSOg_skK%KpRhJI?gQ7kPVCBS z=XiT@Zno$xyWBF7hRSDXOb~4VYiILO{&&5X?x&qA2s5d*W;a*sO64!_DYiGAH~1^f zmpR@%rpzkldU9%>>G~_wf|IK06i3dXmT3dHW9=(-GDs^=p#xg_@@Mf2+Z4udp#nKU z4z>QtXR+?Jq|vmKQ17cd>~@k#RQgF2?8n+|h9!)TCC565=U<=S0*O#Z%Uw6#OhWgVjrNBAIG9k05dn6FRuOiKsc$kmwquX>- zc)vwS`h{w{Q#q5_rkA|IuIbw7814#Mt|^12OW&C%b;QV53Det-YyVUrjCbQik+ZN~ z|Atz_jOMv%pzaqQ@99yhzc?CMi)KUDIl)aLNLy{3AQ7Te`+VLT%Mx9)B+Gb^k%?s1 zFzaaE6SC7J#rbqO^w~C>Jr^%;%c8Cb*030Dp?bErH@fHdXjw4pW&L>8G06#oQhbuv|AEdB>Az#xNSEaMdW&0|n2xTm4S>1j#jrFf^4ybb*&u5=)z6mGQWCkn?Wx z59>khWy3S;dVYld6>x>OO0NfH;(OZ9I=*a#Z4-H$--n4It=0)}2U2C;VSWywtP$3; ztKRxd4U;_A=M> zHhdi18(9KkmQtWE=OcJuHwTzQP?pu^yT}v9$@G&N09^*%j5A0C_XpC<0%#c%gS-SX zA+>Ee^@(+dU9PL4K1oB!R=OW|ihU6I5n3mlYJ8xt6CX1!P`L;s*bSqde9F1Z)~oK5 zJ!D&eO*$L#4NkVd!u;$Kb2NSf8*A0lHOO?%X=b1P2B#Xw$bQ%z*aaL0T%yO*mC!Z2 z(fpC~(Q?N6LR3v<>3vun$U|F>t%o*QPe8qi_m%}FlD!&vX*dV=Hs2-HUvvn_v%M)>!V_#18&-*|ZEKjh}Qjcsfu= z4F-;a<pYB_Cq%S3;iBH14jbM#AcK<9{^0)Jv$AZuRiJA|(z{dA>v+SsUDZaPV}S>IE;wKHr=>aJF0thfK7QVdgVZng*7<=Q-bso`ap zq;rnZrk>Z9s|r@dHcxJ`tJZdVH63WYqCV0xzy4|4k~U%M>(Ynf@km9MSG@mlKkwkJu@9n8h80H5jvN#HJZ5`zb3#qTRDb`V0X|P%<}3EO-Vsk? zTeve>3Z~AQXOAN@yI7`yn_s^;Tpfh_<(-3afawq@#TGg59~F#@9@Mi;xU0^ zz(G~9e}}yEF9_%gk%wOhJnnLm5CW^I)uxSx-_h?x`5-8s$02XP-x-E9Sc2eVf-n-{51_wM!@qE_8bChA4(O4{>Sp zpaQRj$--HYqR4?^Zv(S^rn;V%OcnMNoZw$&onbmOrOIs$b#+n7UhUcKBbC$YKGhMe zM>UI$q%8q8a6P1dOK-^6x>UNJ_30bZKY|~o^;_VYCy$Vym9`2FV>0__1Jzbkd#-$V z!JVvxpUcxEsmd?U-d8;5Jd?jX^m^Kd0pArVpFU{bOnwvbWpDc4oOcCo`EyHb)vwgs zMTFn&z?q)Y6#arFJ#7O&kNi6N+}Pl;$s=A5nw_|=7ah~P=c(SJxXBLds}^4mIfHNL zH?|XGZKqrr+c=_ebNgyNFGrft`1z}!8o0l3uUK7y;y4)3pqd)(y*`6UYMR8n~tWP zPt_+ke+YePd{Xh8dfD+-{B>(;`ltFgL9fLh=^w5+nnJwbWl?hZ$c891%$o>(6>!hR z-M1*Jrgz~`)yQGvGAH79vu#d7lHkFfk`f;kZ zrkPFAE#{`X&4ukDx{dSzC_~bvu z%jm)gZldAL6ROD~F=K|+?fq+>m+dTmSngjN(30ITp?!C|pgp#$-t2{MkaoMf`^5V8 z_x19L4jd6aASy0?dEEC<+`r2$L$XZZ%0{6|+YG~$w!eR4`Qr4jAGXwrl;D($pNih@ ze^dMV^SetQ`+d9nqx4tf&v$868S%gF=7L3^isj`s#hS7^$`1NZKj-k=<5)!f4gh43+P4puJD=bRqy&mI+>S%mIG@n zc}90*ur6GOb^EogZTPGHXe-hcVtisAYB|LS?AMS~!8@_zmmbr+{R7VhZ1-N_)#iEG zYrOvg-+>-hrv&yBXeKzH2CaMav8~H$c9dFjYcgN_T$+BXsh1^>jQ0;#V!xh*~^ggkRvx@Z=RFg9d9Q#c~%N;CE6n2Vt$v4Ut z$e+6OcYW^$x-ay6=`MGED4E00;Qb?fAP{j15V`%L`KP(iFi+duU8aicx~@soEz!@_ zE!N~3p4eK^7+xFaK8w%U$2%z6?&PEBa@*nR=ThYAsd(=4pBxeSO2Wij1vk0>phK7d zb6@>D^`$nbQU3dV<-W3#!hu55w7hncNDu_8eR6gAMog@5Qy*N zo4~X1G1fKC5_|~YX1QVLr7zRsdcY7)!sI?(seYmHh3N&5k9)C4@+R=E@%|PB3!jM{ zhMzQEwnOSI#)UtHHw0X9skE2q9jA|(G|VroaM4e_yQZup zPce@)E6oqdH{IdN`_*{a^%6l*U;&+L$QhY~WH{PT-$lt+zV}J(k>Z<{kTN+fKV!s? zS2@Q@QX0n4SER{qe>u++28t$lG)M05uj{`qzAAEfV28(e?;XA+Uf;ZxUZY)uM01dK z`%s3#cMCv}$WuZCKT$?5H2f z$$gR=QZD_{WPbZK>t|yMD`j+=BL6{^M?*sU2+KC%BydZclh~sziC_xWVx9|dQ47Z+tOBgMPW(Q*nfv5Bf`&w!@wM3WG zc~05Cwys86b)}>vZ)H~2FLCCVOnn-Vx-z9Q8T|1r?Z$WipQWjtzlP)`<_jtW)#KV@ zkwMPmUEe$3lZMK_`Zh&3^y*5;kNGR4-N)|J9DF0l=+o`_r{@9(r#Qg9PSo&?PP61y zyq?Hs>jO(9RLr-@qMVh&0eAx|L%^2R$!n!2oHLypWDi7tvCiTBIP-aV{9Zg7UT=A- zA8j~JZDNkmhs@!6o_>jOiDfo(1v-R`LT$)X496efX)K0&L3mu)lRtw!hZw~EgZGVB z!Hwge;-<0q_yX7!Ok$L_R~D^qxvHvlYBSonubNkOqhN9F>)f)eN5AraOi4MNIw`d! zb#L0aA7I*$pQ5bMIdhAnOT8<}rq$hr#BK%Tyj%80{Kh3OU}U5JV(1~q?>uqxR*qSB)uhBf(%|I*N+!2V&o59?anWy-I5@uO6MPvZ@fm% zM8Tip?V{tH4VVR9g4E(8+4~43FyFF?{AEg|?pZ%uKy$HaHaX0=z#K^rM*89dv0m6j z#DrAhGWJw{jNlQMB!**g*k7zaxp@M$AdLMMlM@?>Q>>*pA07zKpr=?%^zEwZwo2uk z<|p;{YBEaeb0_Dl$@!Uk`&V=7itpJeU(?9%r@r0(T=`{ST2uOj+?c{;1+&X^Ejy4x zmr$1-;#vG2PHO^R$7y;x$2LdY43z{g4Ot(Y;d|HPnimnM_P^@tEm+Gs!Sj;$a(OJ9 z%v**?!87PMLA0zwGD74gxFf2Sq>3){a|IlUShi3i=bvNMp@Ru0ez#y5XBKK^me{lC zXqsj9v1~Mzl6rCpdEWFVm1h6oxMM!{6nPF^!S=DW94%MLTPYa98^Lwqesi#L58euH z441@|@G&R~@nmPPcEY2qtMw-}AM{@hk-D%huVzE7vbN7}PBp92TqG&PigJq|7Li$L zKYpcav*L1R<<$P1@XJ4cTtQIbmi$loys~w*r&V{5;i4(xt-LLqFqe}daDu)6nm#Y% z?nmYacZXh&`5yB)+#2ZNi@QTIM%YLCQ6Y5uE_==SiR8jZ(Q5W;(J4uaa3rUWa8!5R zHQ_tqcX6Eb9e)M;0Jatt67N|JoEfYna5EL_*s*@}8D_3kN}ktBy6d#{`oGQ5wn5B9 zfT6#Ed!Pgi=XLUv1S7aV9VhV(7R-wiyyZ_8#tTw;e{iA*6TXH0jysdrN)S+AYnHLU z!OysWJYl%l^;c7URb{zr#fgd~<%K071>QL?az+*GDNHH6p5K;>79iO>vtDJ{a$*Z# z7QD*){Oi?^O_^?`vKAd2BfBju#NQ%0yasQ-UWW#s?Kd%28Zj&QRJdF3h`v|jTVvKo z@B-d=^jC~g?D69HzxFanW$cyk23Wv*;v&ijM+maAe#z)g$`up11AW&k&-=enl04^6+C zTU!^m{#7@kl$Wo|U7w#)w5@n#UY{)UFDQ%19+wc~#dv%4;pU zR^nTJyz*GZAJtxs2Rc@pc)VPf4+^zh=yJw)e^f%>{)t-#5CeAiGR7pwkLYu>kG!X@ z$BbxuFdUfY{nbYixX1UPORR|Cd9$-wD$Z5jK8}F64yORIPyjp*T92e655U{>bbFnB zAH5Rpk7j^pY-^3r^efHZt$x&N<5^v!_P8cM+pWJ(UNMWz`J~O2xorPA+Ch$6(L+~u6xEsy0s>D~?Q zcNA}(4=T!BK1<8^mkB-IK`g+xA~>+Zsx(iaimm@zXyZ)n4fPB4EH%4pXGc7n+_axWfGC$dM`2ew>=(A`vKZUc1O<_2?6;Hu$ z!S8|Zw8Vah)_^~u%S@8J!m6?IfV0p|=sj@LzL#2VnPv5{pP|N@=bKKLj#%DP!4%t6 zU_515WjtwKLtV95Ec?u_&0OjxqlcW3^T;f;89T|IC&c7R`87GRl2tb zlu=F38a~wBtCiLXYbV$CtbJ1h)vT&9)lIIyRQvL`?e~r9J-@Hi{!=%!?s*Njc2eEp zrrFB=Eq}HgZ8@qO($d!Yu)VG0UYoS7L3yeLZkyau*o~MjK_T2;0zf!KJl&;*mnvA_zT8hQy`2LOh&^|79(2wS9Wxs^xJrqQO} zmUilsRY~tRWH?b zs=$tnmYFU7EgB`zdO^9aXk5Kwzw(>D5G09wGL|e z(6qDB-q5Q_-8@nGp=nnGr=h(0cH8$3al22mswqIZs{L2z=uWV`yQN+Uwdp$+cMs8H zrqA|tWCh!k_gU~*qHuZV@ziIspTK{W-zC4sfS$p#g6{;Z^yB*-4@d~v9Tpt+IMB(b zzvnowmA=KkTfOJFuW{Mp4Ln6u-Z7o7;T(wOg9wj-{?IIiN#5qwh#I2wEU#=&d|B+vokCe+~j3hz4 zR~W#P;kCfAnMUa4i*AFCJEOB#wAtxeOL;KmJ&p3P@kezn^=tsQm=bJmZa&)jylrJmVdH?t%H}~G>onhtAr{Q?*!+ZA#|$Nk#iJDh z*8rC>iWeS#`qc$*j#wM{K5TPnSLDa|?1X^$HL-Eg=OSaH?6Je*-^b367!YzPI4XE+ z;F^G1-)7IHZZ8$56?_-F%v*eq-=BM(^O_@PKg2%4q0kU867Zp$tU(qpbGGRvxz}{s z_(-3qzok8?-Jl(ztvf}y^T-26x#_&w$0D{m+1Jve!PO4# zl7fF`uM}3u#PYt9Y|$9$O=pv$*mbgEjC`?pl(F zpORZ;?o$};7}7IoW~&U>n%onPA5wt?;QI#;T* zyRWJDt4?(MYDWZ96upK1hb}~aA^nkfWGM0% zQjd6|bI|_i7&HrIP%-u|HVB`BAIDGPS8+Z59^ZtY!49CVs2=$YUk8^lX4_0^xfwMj z7*FeVcBiSnba2~)TML^b8~y8E{@z;sPu0VU_R<$c2lJD1zvo!8eX>e3%75y94$c}> zxS?!y#m$OUT`xyU3N>OR{~AM_^JCE$Yh z0Pp62uJD-X#gUIg%KehO_ITg%{}R~dJJQ|BQHTS@LTR(?fMgJ_1FMC1Aq%k^_$zD= zRAL)vDY7(JyKSY`6tmW_TK7~38vB@HOtj&=?vyrGH{4()XP8fzqRAZNQ*yO=26e{z z#M;veJ6>b#Cuop?85OOkPcW^Hbgu>Sf?eTI_-{A?X+*}Md(iFZa&#Yh*Ma8+^a1(< zRiZfNiN#=3u*X;^{tAyHP7x) z_fpQC?1@<^zs6()r>AAK=PWONQLe6dU9qyly(*<{Zu>{wa8s;qcO~vX4$luvgC8P?pgwkvSwQwPEw*f>qAUP;QRlAhrz#&Sn5bpFVS9AFHtqN_ie3f9@O}*?n2E!)r%`flrJiMQk+mU zu3%nnMAqw!=jrb=er1;x2ui@xl#(FQ#IFg)AbjPd6sL;1B~Rdgr9{Q z#hG%6o3r;BzY+d6-$tLAel0=m;Wd$|5t`6@L9+tN1C9q_L0aGW9?M;($#2LEvVpQ< zQ7E?r+l-VV1{B3&5iu~+nrHU5+@$(i1=MiUV8eBtyPhy!A^VUq1}9x#Ev3Z`eMnE! zJkrbf(Qw_k*L2L1L)oaE4i5H>3bXyN*V5CONlYU1hS37Ip$KFUT8MgM=ds;*Bk>oz zmh*$#pZAD2hrdzqURWzSEq)+gBAzUMB#xE*Etw=)A?AtR3yS$G_*&i%ZVr1J0buE{ z8e{{D>@w;ec~T#s4eZWT-E6O{q|vh~Ft1p&Ek*(Wm_GBoL{(>v3n zGktQt<^>m+3xJ}ek_DBw>++SuJ91Qa)fVku(=NIJy~|n7|4VRD6eSnAdHXyL7#EZj z=oZ)()Fb>zRTzdfaM zO7{a@u;H~nOV_SzH(WL!v-vY`nXgPOuniuBTiJbiJbotsneds!EI;hh=GxC);FjR_ zpGSDWgs}K9P0(mRvCk&2=bodz7J9dNt@2W=`Xu_T^p6YZ8RQuZ2YUte@K5)? ztn`$)uh?IjDP1hy&sCw35F1>*ZwQ43 zJ1U>ekWtPLMnkhl)_)8Np*gnP9GA?m<$`kWQnJ91o1Ohn?#bMv*{;9Z z(_W`M|H@9`XU0@y)%rD*l^aUxs?*zD?N5ob+|k4|h=#oRJKT4N{Sh%a__e2x`xKuc zp}S(f#uAbB0gxx^F7fvBC;W!F-jWU!jg*)~O}xd#JY)mB7y1cC1C_QT)FV@u$zbVc zO|+$$N0EDshaK+Wbz7Njp{<$PO0BlOq1S=)K?6P67GZ0!djg5zCZLMmWZ!KEn7+U{ zU5hdypjTH%nm-*4W4$cNPOssZPEeL!|AF}Q;-!q)oM0P#xC}H756w3@H?zPV)_uiDof)|6gpDw$ZnSIdSUaS2Ww zI*h)Kl*pF`8>8(0y%baBOTCm~NQ@-v&*1%j0&j`eHJ`nHYkV)cab@n}HKG}!T&^Es zK#b5F@D9Dkwv0+QeIWaozB@8OJ;?WlQ*&|iU#pf`92R6=$*2Kf%F zVFC$8?C^6q23`UcJCsn7qsuT8fSD6?8@-Jg%}l4S*iYLp(<7Kam@CYCW-%ZK2SJD7 zZOB-M<{pi6SpTxKIZ|#Dw-;X}Ktze6>7osyf#PZ6`J(&6Rl;7vll<45kHkat9^3^? zqs3O(+-gYZ?rxveB57XO@T=xowY?lI*;=5^HRLw_a!r4i=8?MVXMW1gZ~5N>^SJf@ z)I2Q8&i1Lw?jB-uLqgz7j%l7kT&su7htM01cl_#Mnaf{+UMJ?sLTz!6}TLnm$lI)K|i1#_3) z;aF1`_!W==jm#V14Ac!3K%-#<`2*d9rC>YoOGG5QH|GUM%{{^E&0i)MBJ3|>L{CLi zgdT$Zf*GO}LW+YEN+blD3Y?~&TQ?e8)FaxLwCq+UG<~X-mG;U1H?LQYKBGMCWODSE z`sAaZuYNxG@z`fksc+5W!vC^%WPGb_(k)_Uz{hPYayBiMKT&Kby^&wIipxhHuCc=NbY&Ls}UK7&%=Pv$l~$BtW8>yC7`wsp3iY8lkfT(PT2 zp7Ss(JPXXc{A<>yeQz&*Zu-VlimEbxZAks}<5ultHDcUMUeo=jm0Ry}#<(SXJM!_^gB9^ zTBq++|5mqZNrS)9TQ^@jLtCL6KuRq`t>xAdYk(bMoPb;4BbbL0ST3qWzBstO0b7Fu zcqe)hDTlwnGZ72ogf2zOAq<)XX&@Ncg(ShpAqotH-b3HuGGsm?g>OTZ&{;SY@xxYQ zvFHe79&uK(2YV{nlu?p8CNr2ia8$oA*3KIBgEIy-GYM82mKCw<{hZ`B6XN24##nt;2&Z&y^6}Vc`@ns z!R7+(ZS|dQz3!~h-SAL5T{}(NtPe3M9r^6r)<)|+`wV73uo8+#7NQ6?34MpOA`)yo zJ`R6~{ekvDKEmB_65@_p9Qe$H6wpa%A-o2ejSPZ^LRN4lB!XukG|~XSg*HOvP!=4I zF2ah@Fk~rQ1fNB_aW>0>_rVls1CoK&6Qfv4>@0MI-eDU?#{iA?8zxTotB&jKecGH_ z_>EU9b%i(b{c=WRkNkBft@ZoV&-FhaR`K)QGd`t0$>G*VwawQI(L8C{(z%a5&R!}w zD|dCN6%CiYc6%0?8}~kNec)kF2k#sLPd&ffi{d%y0pF~M?)n&?pI$&?>00d(LANawiUv+z-5 zt7G~O#$RJmSOxkNm7t5!M2DmM5lcpTIBH28dKI;yYtVxT4IhJ>U?;Q%;;~^=Uh7G~W&`9JJ^x83703gYT5B!dXC@z&}o9CzzgS!u_OFDKc z&orK|Ia)QdVqwwv9PHPb)X{0lspcP|%2mbfX(ztsq&L(~YKu}qotv9SDv#(MLEXGA z(P(KHua^Bn{wiom)Pj(?0o{R<1NQ`M33(QAF>-CFhhMs4tkVWXj2Ga(NV1=k06Q|K z&;|53I*)#67}C9_`WK`&%-91o9ytl`MVzr9oWx|M2BG~vHAE+Vma{^KZT9K z?qW_vFIGLVh>+tuP&U$q+{LdG$FVXf5Eu(AaGZl4j6WS{Eq0s)g<4csqRHwQqnuQK zvpTVAR#i&*q@uE%)*p)WNtvH=o>YCzy`J*-*M51sn|8O~P=&QmYdY4p$@ClZ737Gu z;t`xPJlJhgSYE`?pbY_yf&T_5g3gD9L@o>85_Hsikz%xq#_geJf%7<_k`;k4P#6+` zE{FYX&ke)1zjQlHJ1kMAqxwXxT)SEyW$I5Yu)eftP3h(c>t*{OW&zL)3{Vqdt0-KYpP8r3{V(U`uf+6&KerTA*Bt--vuH-{&dQ4R8`@E%vt^Zq zOIZOCzd?S*Wvv2tYw~FJVtjIZeF7BW-4UAtYF#s3E_i+NdE`0Cb*Y@<-)7m_r}&NB zax4rw1hmm}Y&L3$^^9$<QMM{(@cS z{KuY-Jf#z?5tb-(xCu8uqY`Y#s97e3>4fMF*CT2vPf>~To_D|Mq_AmBt)>n)T-vz3e1mFQP$A()9$c3aUIZhv~ zS*G5i9;dq3wzhdkQ)lDe+I!U-E1B|tOOgtf=LP3+vd?EEr`4yAt61HlY058JkX=~G z=~7wzpm;nEl2I>h-h73(EHKNj&~v?~$tN=4bHK2WSurEx28LX4z3a5v^@ZCb#TA!J z@@CF|%s8tMcmcP7FYR+JX>w1;>d8X z0hYjZs2A>mso*9s9>9QDM@o7myay@+HZiN{PxM@%)sdPy?BI9|v=qYO&8VE1&V9y9 z;jVMIaRr<$yvM@1;<+MR0PsAx3mqQaApV9A_B|tY|1>>O^ zkT0x-NhB8Mv)-{II9u4xoPnHL&LwUH_aG;a-OduR_7Hx=J)#1ifL%qmVAJpmct1Q8 z*W$zQEBIaFZ{iF-8#5!j;cJfReTtcE+ifm3?9nb(&+i=H;oGvmes9IL(siXxg$r`$ zWKR1vG5z1~&0oN8!@oABeyr;5K4yq&{83t|9AjS2{^}tMn-QGnA`mZj+ZVR5*XYD& z3DWS_{s(<*ZiH*2@68~M`v7(!Bn4}!k>m?gp>=|(pL$+E|OgG&MsDO%v-F58lZjaqvM{GR`c6S`Ry9*NqL`u4An0`9G{r>)q&$%v@ z%fT7;?0v_&*IJveFGyl2q=Rltd6G_^4c6%@l#B$TA;@niRmu_vLDSJ_ya#a5&PDQcRJilloTR9Sp<+uxZF+ zX+8JL&DxgNoT+GAnpQA0_kPZptikCwQw%>UzK#CY;JfNa_tZ71t&)3wpO^eNZA0om znKI?CE!0002hh0?jU6?AXmYdL%yzTG$6G#IdIZHq-EZEq(ShJqz6PTW?ksgzX6qUg zaoiiBJ$J<^)^4!=bVRv^Q%$(v+*US|?(ME(ejs|yMqRbKQgdI^SS2fbwK+!2a^1LI zm!hs9DNGG&_+6lWONM($7Cwa8OD(6;nd8Dbd8w4kpI}#TvxHev61)dpi8g}o%gf{o z(o`u?3X@mJYowi06KRD6!SmoPXfoJYKU7ibbJ}d(MtzoXp~dL?)X(md=%e+1V(D)g zZ~AS#s=ub4s_vk7*AD+`8{loY?o3jjHMcmCUd2rO(wYy9S1#-ddOp-39t#u6neAebPO|@ ztK!=WKsPJ)lLDni(tGI)z*Qf^Hlz`ni}V9buFv8J;kCF*UI0CZZEy+l65WEn#m*C( z0q<+6?wTRnJm1pWOq#>I2KuCU)p?tIuXr~!hwGEUl(fIDR@IoiirqpI!7F;PH{7|7 z3l5Kivu&!`QL(~(!D;FpB!tHXP4sKyx6o^pG98_wvMW15 zRxS~#1x$qHfMIyu{jVdz?Z?g#no2>^d=BQHLOgj=HBLQ4m#MXAE~;;9Kk7E$f@KuQm?$Ptgl|8jW(_|k2TLS3ntif z!MMYiW$0r_)$i0t=>}=0sLF^!)CS*?8}O}}q4YoQ1&#!OL?5i`SJtTHc~MY)PHw+k zEGIUz(eLl+!%~?an|^Hi{_X43FB?85e7un|w7MhR(sPjtK%R!Um;vJOBEr5CG zad!x{ojW1##3n1sl#5j!(Dgkdu8~(%W=#*x0qrw=fv%705UxNt>4xx7I3m6gdy54^ z58)zrh`B?hd(x>^>;itV*hwA*ZGrAV!{H+Mf6;s?whvDt7LXr_@7PY%jW)tB0L{-j z#R_FJ<#WX*P#>DeIe=~0NxM;R)sNF}(Y*$!^c1ZZK%Mt$@-(frN^P(@oqUbWK<^@J zVOj1Ysf18=AYhc9x6Q46ST(USu+qPBQ{|!xwyeCwRn)i8k)M=5BcIIOklj0LPj=Jn zby>^7OzB|O& zH^pbOcdnT>H?(Ziy~2M>mxUm4x0owVl%9#_xjTT#o(rmhBsW9N=dOsUP$=3T`;5uh z8$6%bN=_ynij``$)}S?M-l*0qf{4-B8gvHgK&~TyBk}NS`ET(GKa(5Fb>zSD41ZT> zC=L?;0^M32v=}lVgRoTGKyFodt7oc{)vylM$(lKuCfW>5jJBiJrrM@@tGGyv#2aCI z(H>B!++6mOTL^R50HKB%#JuPF(JSa>%mhymlgK`yuh8eHJ?tgwu_uc$P>(zx>3n*g zJCn9~_PA_dLXJ_%u5HvhD%N%11JQGU0{Eq)i6_T1*@k-BQS0r0YztjV+fi#@=W(ag z)q}P(xl9dzm%quUi*Mw7bP+reSx$r~o)A58CvciLKrScVkc?)S@)P+|<5723jZ&Ob z+*bK1XDIF~C#VwC@02%*4$4eL8omZEA$SF;{EXwu6{>RVFZ`$C4jzs<2`8C?ogpS8 zc~}Nn4;qc$$FosiP;n<0U2 zp-tsPph&BhDntq1j@V@rw+mfO_NDXXCs=ECgWOyADI@_s{BU-o8 zf9QJndiPOk2(AWsu`l|Wr>N0j0=$79iJIlhOr}i2PiT(&553NLC?2~l&vSP{Qm`w$ zp8prW4Bg}};tR2l>_&Kia*+%PYm|xbM#%%}j@!s(X}zWy6oFh&#LBoL9Iz|9E0kyg zu29^P=MY=*A!ut7Lw72Cp(W5V;(&Y~?SoB24~HC2398M$>*sP-vb%q&n@;cR*<2j>hp`0vW;%agc^psge zI^k20k#`ZlkvK6G?<2=?vC8H019}O`Lf2U<8mst4H-&Ey=Ukt#siF^hK|aR+EA^8$ zis|e#WQf$1)#E85A-eJI+%I~h;<|9aBap`hv->M?3rcX+!s*yr=RxT`v7Jc}&!XSO zTn{2aM8$OI?lNm4WA za?T3tqWGTjzekqz0C03AJfy zLS}=0WvbXidX2^*(X0{~D9@Dcpy|SXzAtf2&ftd21@JXAnZE%gp# ziOV5^@s7+y*{%o`;`#byWBxmIO|g_+$9EwvNq)>VX(8e-Oy-7z*qtxWL;CO@fCg-5 zX99Md8`QM(#92@xiioR(EI0z0D*S~uAz^+Z^b)%){R@wR>KHTmH{zngkVC3GdNvoR zSc;7DEJCBnza1Zl!wR$gx}2xH&IM8r@!g8COaO0Eg}}|(MG%h^NDsKDcr$pVFb{f- zcSRdevk-__!cCV(5#0rsbPImY{U?8i_CnR7kJuRvKu$|BGK;o`Be~x2AVlONQM-Ii zI)RLZo{Ep8bBL8sKnp~VxC1#O3Ghs*C;OgsNiWtIr^*2-pa6=_DhZMQVz%${ua1RTCHwZ0B9lx9Ft{5(z z=0|}k#7uDwG!H%jE#>qOBU<1d@_nWRYeSxPzXeVmh16;&04L?g(o5t5)B)Qr?vU1s zV~7lREz?Wk3!f51*(uZ5M4>a=1$}|p*^g2o+RnWXuZKQ$Gw?OI2Q-tPBv#=?l8##^ zuY#kI95xDFMI4s0xWS@9qZM(c1u+k5Bu+sVph0rFkj#&jwW^n#NR3o1M&dm~l@ah0 zSE2F;T+3XSmJ)H8)wM=?s$VPqc6$+>a9TPf&c~Y)q0D=HC1K!hN{^xMNFnlsYL5KW z7#u37rDnHdu((s-l)>G@m7}2Rd<*mieH8fwm~+X5%uSce6#k-1I12LTR>lf-QXc2V zz|mrT%n$q9bqUEr!_ab`)$U&18+RsI6p z*BruXXtJCFqNqyl2yGTF!dl60z%@>A-O zLJgaF1a2w4Kt{-Yk=9%r^r}kf1bN$B#C~;N0IKgnEDUBV|3ckho`tb45)8A%1y4gP zO4**Slq`f2*Sb513U#@-moCM1csH;Er%2oIYUrx`ioc6wD{ioADH98Z#&P|TWyF2z zo#@5_5Kd_IS`PQd<}!hOoiVCM8Dy#c8+1%eM%G4l{JX*DmVTQe+<6vnyqxGnmSSCDrvOmxn{UrUbh1KOl)Ay z+!PHa#?eOAJM2356P%XEe2pQ8gPr-R4#)&njjsi}H4<*lxiBwiq&x}x;JJjn(uPPr z&R%RsLw8~W%)=OdVckOx+b}c~~k;9>8u$q2|e8DfUw|JAhhb)A8@cV)P zUmpJ%jfd)xrSKfjYsDz!7Cl@!f*t@O!E9+gJX74S%x1SRcU3!~EWSN|84nV7$k|ei z_yqNZkH}|X53?KEpj*f$iEo*OIv2#(rDBPy9oPUz1nePRW1`f>s#mGux55BbBd3v! z!@sy5D<4vS$%oa4X%!*K@%&MEt$Yt@CTycW<6Ft)_V366-7se_;hUsY*QmcZ$55Y? z@tUd3-}GTFN?%3PV{@HXkeSN0c*;T9kpslkeS?3xmlx|N;SP9eA zCG|1r5_eSY0+k|*85C(uzQwi+t@u6Ye7u^T$BZ%!#6H-Ar5vmcp@VWg-Jp%6fzt4+ zw5{POlm(_WrS4p(PQ6v}Hy_0QA#%9haxSrzJ1<>AyNQ1gBatAo$UT0A_?1|PmGRx3 zD|CaDv#q3b%-9QU?%F|*B|d6~*4=WyFs_4(Y>U)u*)D87&0jFa-E~gZUBMqv|430p zByo)yFFjO`7PWk5`~$jJ?n~Tc3m`wmIcESe1ojfYtNTJd>o!Oo)NPPZM?21}iXhLq zYM`N56UbMw#WS47!XqfU+b(*^qt2t$`Oo9}yn;Be=K__22ADv5k5bHA1uzkW( z@nvEcb{f12FxG}iyBrs>C)%@~NVYpNQgcBt+efM%X>IIo507?)N3ss&fMEkO)-_Tc zs#!n>xid&NdIuZF(c)8bgP4H(BFRAAmh`su~z5?daE* zLrPakrPRxufcAA?6%Hw*us9I=8bi(TGGd4Pid)SU5pK*To)cU1N8l`cD}PLQfDH$y z?4;{BY&4<7E2n~UX?`FSw}Z~+mnmi{OIh6EMO-v(6F-*+@ss?HN=Ir<+%fzvF&^k( zR!I%89%2+%AZ4INB2X|0Ge}tTk~?WTp|a}#K^{1~g&yX2xEGtqsId>^Pa#!!!+*hB zB6n;zrFir|;h{87S|Ik7KEb|-RR}@?uz0o==s^tV3uE$$ajz>B~H!N}X>6G&?$P5dK*xhIwk z#me8X4@ia(1p1^X#0pJCet_n2GIyW(-|iVAZW3-IUifS2A@3_Y;9wzB{4HOWl4ZNJ zL+mD<0Nk@AcB-^O{)zX(j`MBgD@0d#EqfMp10Td_VWCh7wF0j`4$_H>(dFHjcg0(62Pk8%8fx4SAfCL4uF8SAS=n^aFoyy- zFL^U}zREMud-6N(5Hg8$A>CvpG71fahr^|^j7-CA7=rYXD$(z%UGj3_ z7Mz7%AgcIB!V64^8@^qzmftoEMmFI$9j zx8qfkSI-+_bb=yNd4SALtBRwj@}^aJz_N|!>#SK=Dk2Ir&K(G+}%B38Lc^G5qe`$6|s zTcUlgy{l@iNFqL=tI!&>Jz9-$5G{V;uX6}9%~R#vY9DUfR6Daer?R@Z@XzAx4><+7 zO$&Myd6z_&9xRB%);PklL>)GtY0K){;< zFEuCH#&`Da;%sYg-Ll!32EY8nEX$0ajlccY1O)rGFy128LQm+?ju|ecyF1_tUhu>V zm(cTyJ8FM*H`Q>}8Qm2_nQ6T5dY^UPF+K;p?|T1hzH1PG_rQL|E#f>lOS{onbQ&}Z zU=xePX_8Gk3_V6LY%|eau>j~IymV9byzaP8)OJ;0RGn0QQ#>K#(O1x1d7<1x{v_6l zR-pkulBMWs`XaT^bH&Bl?^&xW!b=txO(?umw4vm3$$&ClMNq}`^513VvQEV}^P~P) zGfIAKNj;m=G$rb1-1oCz{J!O;AF?NC#CogR{Ot0n$K!5n`=M>JVmb#EYTu(V@>#JX zQi6h6FH*zXnTsB`$A>MXL<-@bNjq^$^T2}nE%hDZ<74S=E-`r-n;TAPB2;+_ns`X2 zkxxmUn2M&rGm)2w6>UjWfT;{db|Z%<@2L(eV~K3gMQe~b=x3}op(Y_kit-w$1=gwm zsGq6)m8Zxl#Cjr>*p6d>Z;*`410C^b>A0v7qPPS`NB{IRbZxcGt2tZVw`fD&n7q9F zMn&gJ&@#BZS^3(s#s1k!3vpu&Qv?)X6gEzmyj;pm8ZiC;Ux4i=q!SW|A3A<-+N#n9>rP706ei+X@&lQvA-UOh#*Md1w`176`3ST_0yiH4WT(NZ_@ zGw)%CGBI?TC&YExmQYh!zORVL$MR(EvQ1?tOIGAx&;Il4<&OaH zSo1@h(jnzrT4`2D*){2ikAKXb4nEy-dp7O1r_F?>6T{vb&mkY_Cyqn*689xKn|;e` z#m&$#rMKl8Hr*JUVAk#_+UeKgr~hZ;mfhzK;JfH~m9FU(a#FkVePaKkJd$ zyQt@Yj;ER}t)FLNk#-(!?bND6wFS;dCR8{hry&FIVthDm!S7;ku_oj#wa0kS_i)g# z(Eg#m!T0>ry(5i{R1+|jJeRNI*73z4-`zms0LyB&@+kPJQ&g8#WyXp&%Q{!%d8f9zdM>WvK6m6+| zWN*c^0v(18;K{KnNVy+RLk6OO#4mM2lfgGVaBS%Dus0#vzz<&Yv_AM7P`wy{O4JPW zP_5x+#8V}&X{4{vt!rUL=no!|sz$p0LKwEE9 zrRyAq9P@5VSCgc3sh2395IJZSG*z}qe@XYmKf(jPCAW<^LFdw4*(uyXZXCCijbI8r z;m+;Wyy~x2xs~%OFH|Iz4=SrEc~iWx$erIRr^|28PfPNmZ|A-u-;X5!_iI6JliHTV zhv2f7GrK$cyc=-2PutE-oBr+Jitsp*l4m(va^4i=RAA2aBBsnWwfFt)=i?hC=Nhsb{0>VyozgKyOf~=7pKi^%UHIGiOqm;WyosUh_pU2@7IZ!r zH@abzw+TJq$SAv2kX+Ee{ERJ>Z$nNtx&5XEeDrttj5d{Mo|C?~1#pA3s=NBvUT6HL z2F3=g@zxm5DkQm@>FD0&9O1Uphs82ejAnX1i5Q#s$}^`|$N`^D8x#b86N(X7GT8#EmPb*$J_Hn{X&NqlK->5tMbrQ?cM752=l%i8(7*{?HcoqySXz5Lxc zt82k6$4@fJH$A+5lcB9wwO`Q&Z*jiS?|?oUhdAGLvvx+!XDj49#|07-jI=Ko_$UMm z8sT%z_@8Puwn8?6o@W!xk@0$83*(jP-PSwYbWYt8yD$8vp16{ocU)bllUzJBoruts z8gfkUOofL3w5OE6v4=9tO{blnrqmp&jw%4U-JU!ryaJuhGWZg54lTsm5najoils_H zS*;wVJgXQbEnJ>bXkUVw^r5}S*yMNSL{n&8_2c@04>!tLP(P?DI&rP9lQ=i*#j z538c~dCkMxGuHMXGEA=8RGD58Rq>}Hx#DSAvy%En^$Qd7lXLk$j+~I3+gU3zbQzHa z*^Y+TIMbKF_=aRltS#MUUkjqq=zuDWWK$X)mew6Xp_ZaDorf@Q&?Pp{VVU9a*R5WG2Td{{!zCZ8kv zzT|1Bo4873q|=B+!DxbvHuDazE0z!X^~zi%LGWP$JYC$wJqMWpX(ZZ4aYIwAmyOem zZk?p|Rg|KC%lr7P>=|Y$Lo;o;EM6n>;vC6WRzhc>X!s&f`KBBO)HVX#1w4nw=MhT@2G7RIkx1ySU}gUGOm_~n@3%Fz71#Bz z>u=j)?{EKV>unuZRZ_OTzObrF%=J75`hjv}j2|Y_1|llXEp|bmoMNC%+cv zc-JwA!ty0Jwqa}ww2iHGgBGC;rv^OLK7?9%cq?x`=D^r(Scu`d-}I0}^)A$J7CJfL zwdJzFe~?$!i3qZ z59Q~IxBq9IUZVk(#PaG+HC)Zfno-qjtD-7j0lh<~(lezk%EHRX(i27L1!ehZd2Rog zvb$vt$~c*sm}xJ3;AyX#sUVZpN0utEFh+J@+5GkiE*vti;6$CULV^C%%$S0O!<3 zFpj{e5qKyxAubReA{emVl88RUa(p_r2lPub5Is0C9pOhnZL}HM1C0f`h&s6zxZk{k z3Zb{qe<0fSgSyHO#I5`Xwlg!BQn+V2xVnF9?^QnmYjm$BsV1eSS*^L|O_ihaVuiK* zSlPvr4@Efz_wt|QP0bsVf34s{!L9tP{OtvXf@66nN`vTu%CX+fL-HGuap&4M>98qo zUi7?>ktPnU@A*}m2jUjytdigAxB0t5bqxkYkBsUaDF)usf5(nX+xX5*8GT&pNW^Pm zP4m6F`uyXAct15hP%+q3c@BHuecqYqS`K`lHDJ!xm0>4 z>gRgy-0zHX^>_KYwmYKg_^Nf~qe?pzQTbg9o)+9GRF^C+e^}L|reSU4T5U~I)uW1A zrHu*~|7nytDE)QbW*0@=va}6`qp>!-I`HlFtyV-4p#hdn#2}`?KH2usp32N1MqB0u zGNGd*H#D3Po))~*Ym7pW9PByjFSp8bpRYw1siqs}dhhXz4LI)i#H>?4$Hqvx6y}=b z+~(>|{mc2oGssj;nc;;2)UWCS;9j*+YR{)IjVPlB^FY)hWcp%G9G zbO<&h2a&Dl9H5|p2?H^oI6_1aZ2${?Jm4it$PQ#Y(i3?MKY&-miSQG6BIuQ)fePmy zG!a?@`GR|#kI3}_3nfun2RN~_r6bY<@ruxuFJ?=aVtP1jrqiiol;qjsAw2tC`OeO+ zN3KJzJI;2_eh%C|w(dd=S2-EzwGxZY6^todQ#_&MYw?dFs%T<~spMPXzQW!hj?MhD zx@apu%oG@`3Qui(u3c2SBXKigfsvoz1)UMqQ>(4jjy(54b}CtHjt|%p(xl$I`jtU? zKg!ZXFOT9l3|>$#E8=NA zD0F{-OVbs5gglf6agFG$d>|4;w$iJC1L7$!*=wP3l`;{1BRM>yTu#?<=LXLquCv@2 z>!8fgHqqv(MD;giA=U@JFRo*UQa+U1UFS%49`@9y#(-(iUhyPgK`GI7=t^V&+7&f` z%IJTq2Emn(1<3^b;Y~o*Rf3#CUtutQ3ww%n0och5Y$;Za{)N6oMkC{4FL*n=3C@MP zg0F(W*Kx20-VCKe<_L)^};fK5cimU&MaXbGK-jwY%2Q|RI@Up zU`9~}_ey85ePZ1;Ymv3NEgz_BF4jG>zOlM%2iE4)JgIqFEmsCqt82E`G^)K>v$T3c z}0SiQ=dabHPGgZGxvz{o2z&wfB z?NYnDFQ^E8nqJbFH4W8jA`+ndli7G`wC9cItb3F@8hD%da{o z_oAzT!vT*xLcEZ(a0(O-nEDSuCf+E|0y^{7avwQdR>&dpV);4X9n=RZyaJho_Q+49 z(_*G@7x1OJiW2A*y9%56X&l9j1&JYU{hYj<$iokt5sssBV>2th!WDUVgBmuwqO3s*=G)pKLCL)%$x;dAPD=pAMH= zpKtM|;m)8>Wo>gMx+^BQ5pDSp1d1K~Cll} zjsF98R(94@Xp+p2y~mi^Xb&qEp<9Ld>^b%zeU}+5&W8N4@ybD3AMJ2;gyxXy6mCZH zB|^yOlDT|#4t<|aW0ToaK(W7$-^lk8>PuD97ipo~64Wejq2BOiP}8l39>MqF2-pm} zK<0@8>cuh86}c36t#tu?&_>uFxq_slw=o1bXmXbP4hLtMT+cvoV+ZTr?RI*c zbO=Wa3NY#NpjYvSgp9udX!}_Bf5Q=_BXEO$Q9wWx#*v0Pxzp>?(GL zQv>OZG*6AECVFnT=G$9ZBWrAxrV6N{dDYa~th(lo&aM|Om)qqQ9qsGpRsUNNTH3E@ zcp)&1t~SCi%;sQKn-(fXlkPOQf+`(K85co{)E<}qwzwbx4KGqQLR;sA{eArx-LxM9n3{0g}(vZ z(XQgx6;|>&xl-9v^@F^L3Gf1`u|RPidV{-_OLWD#7rKVIGpS^@v)BPTihRb76YI#i zWGiwTn3*?0{6SQSkuQpGg(7h369I2l4|Fi&LI1N7`U$mxH^Z%vw@50`U2a9gu+5kX zyMtor9H5Q~0D8>7;JHv6$SzazQ#n@dAorL5k+;fiKx8QZ{Ni)a5XcAI**F^H&M2rm zh%d6ZRXitr;;-|q_%?hLf1g{-?O}g2Pnn_2N8pi>1pHh#(Y16e^PI7Pxp)w#W!KQX zD8%#4*}*>1T3FMm_DRi%n&-9QwlR*I4xRJ01GSTNqiZ%)9xcl#Nhmo~5>fK3VwGp5 z;-K%ZdMlg!)i$qF^G@gEdc>>@nP_T+ccj+}TvPrkH6O;x{;?PyK7*)nOO> zZtJTRR`jAYlm86VtUsZb_!eaw)em(m-74)%)ih!n94P+c61WjuIDcK*f<)sU(y9EU zx&U-ja}?9@DB#>MObFmEFzNIJ>b-lrd$OkqwTjwHN3!93h4>Ey^qqiNP2p?t#n^K6 zEpQ}Q0?h%pTPlJ7zyx_JV8sT5$Z~s5$%&?gYHthNF7{mirs)iunQs z$_(@fvJ0rH)xdiw27J$c>84a)su2x9NAX;m59+!f;C~z>f0cd$1!|0ZKx!^cmO4n| z#YMt7eluUo4;GpU(|MY^$eFo~>|ACqsQeW4e0mmeF#3l%2D0oSCXo36yoE1%o&l!u zX6HqR+h(r&X05V%+os#k*;m`!1BaNsz&-o7BgxLyb+T4hf36%_v9+RKb*f9PhzKZ- zNND^a?o}sU$NsJAq8bP7G7Q6f>5JAmH9u|hspj$*^#bqDf!U#{VQ82f9O3s~zm6P- zeuGYl<-#-h6H-HTRU0(R)w9)Fbw9-kv?F8``*Jv!&W#YB%895S0q&YpdX({`9v_FT zKy#ty(%-^LK81_n0@$;3YkB~!rhCzonXa6`zZcs;osl8vTl5wB5$y{qmRS%6x`(Gy zvh+)8C7+U0(%An8ida&ktOow&G)L0@q8oh=LLs*cJufvf5@z@K^1^wGt zXb?0F>JJ{Rfr=>=?0>+aknT$dK>V*SMN0vaMN&yAVxj0Ib(985gmhl)E`Ak82&?&i zpvRsG>X%5yNvF{=eT$jGB3vdHz~kI_W<4diAGnKMD(5YGp}n!Qfh*f}&(*a?=EVl3hW`jmV8BJPDaLw*k(LAQ{Llnux=z-ek9s)TX5i?~FH z5#9>hg*n0rQ6Y}uHZflGX%Fma4wMjEm_Ga}xgC;>1^~6{O;C3)M)IMz(jW1V*jdy_ z!Jt-2gwDe^kdsI%`~g%y-XQK9Ko(vMcL7R;3gjcQ6 z;GVGa*&r5SRctd>%f4p3*yW(V@5gRp*R%gIYH{@ADLR9((@vRJmp680Tik3!A=foHfBZt8S=$h5HP13blBDs`sPO z^Tv~!4{o_B#xG)az*6I7RTreI7{UJ$zQPxXxw=_iq+jpA(!eag(ca%o&on9cN^}yM z4REO5_;zxe;*jQsZmn8W6qBO?Bkcg-9J|E{fX`e9oJ?4x25XCl;_Gn;e~4(|Kk_)> zLe)kX$A9PQ^WOYmkkL;wB6FUJXF}N+?iSx3Oh@L)HS%6aglZuM)S1@-`fv=?C##|6 z0HJsRU4)W>k~R=o47BMN(P!uhbTZllosJeFy^$MmARGYShn7K8!P>6@J<$c=dbkZJ zOWH!;*#Zb%oTh0+pfIbeqeNLrwYEEBJZ7l6*mPplKp2;BuMU&2@LC-|Sd z7F0rNz+N54@8k;Eee8YaD?N$+i;ky%(fyh245%oWyWqr_!2KilJ-s~bJ#n7-9uw7< zKFs{hu4WG~IwplyGHFaFHkN(MgfS7!GP>IR)@idL)&k%|*RFa)bzIG#TFf?%9joaX zRIkCzsKl5nO?pJ{4WASE+}vNg1Ya+okih+xP&-nq5A?|kObaayT@!fFx5}$h?;*FK zcj0VWK6r=ETc&{{zj|MKL{RL4Pi>QGI!BSPS>bMG0VT#3= z1H$DxF|5cK`q`4qoHI;XOXwwwz{yN~}G8pkg_oIW+Y3LPn8EOOl|0{STK$wD|AM$LF znfuG}z>8RrCxcJtJ*bJg0m93n0d z=ZH~&)4NG11U#V6oPnFiwr2gM~L%?W2oc1 z!-q~s-|H{=u)Ys{ZuwRFAM+pOXYd{C_0?d|O6pAYPvt7rer>8z@Sf>!4r~+f%HP{x z?X$%ctox-pPIe=_$vm>PB30Q}GeoNg8qPiH`N~;DV=Na`tjon2!ewEO)CcQP2dsVpNr(`aqYN7&cmJLuM4llPtsL6 z9-0aNfdi2Cp!+%ohrk$(^is8TzF&*KEB zsR+0j`T|ax24GvC!SjbhjQl~qEgzJ7gTBEbp>kv3N7P?#B6pT&$lc`UQY(-@Mv4yr z6K*YE2;S2U?irWL<#SqoI{%2z=ZnB}t{2~ccY@laj6K8-VmZda__G(;!Q5NmHRj}w za1gi$!H1bm|DsAgu|OML<VM)-cEX-8;#5 zqHnJE4X;gJgDu;Qi2jAB%8>Q<^DiuS~CtOj`l$3q|G|3C*C z2FC*=;Un4=+lYl?4rDWYLp~rr;#Y8+*(=OO<{AUDZ`q|>Ht6M!aR<1GU_WwfE&G6d z#kS&dxz)U0cq1$mRnm275O4>p0{xa-7J;wRT*wD@z}Wz6^q@_EA3#^E9R`EGtu5ek z#3DDrXXA!4p*%={{DBvQ0k}<^19{yp?~~ihPU#iM9owa8AR>5y`NwNe!wvx_BwI)n z`hg5W@j3i+{wTkWkK+rtom^K=#eHFSvV+!V28lr0UiCn`i1$G`aJatH*Ylh84nvD8P^&P={9QeR4bH^NF$Mib;P`| z@909naclq_kYYjJEkRZSZ<^KcOVH;YlDdc=`B_{7+aBD>FqM7E{$$Uvk?bBOk8VpR zQ%9+%z%OVD{fwT#M6%D=VD2;*%^%|x!gFD%NP;@GTAD5g0#sLlya#NfYBUq@%HDvA zYY-NJy+%8sCqSn+6q$pp1M$oU;6p(O1n`X3umy%-8^Fp_pd^5nZvqu`YY@fu$#cPZ z=>fbR-$+ZP5Gey>`32$#F-D|>3qo5E!A|o-f&L(kd&*ts&T#*5ow#K7Z?+vcEjyV8 zOga66en8&qcq3Jl!C_JFvljM6#H zo4m)Hrx|*gGAvgtB_@^G&wH6)TtJh+;ejE6p93}p%m{G%8~i$W+bj*dt=`+b0?hLa zZ?vn`Ka^J$9Z5UB1Xtlduw*O*YlAA#184x2flfdh{GTI8GBjGgC0c}7zCPfNcjo;- zrnv&Tttjpr+YQtJb}-w=m?@wR*v9NP<6TmO51o+LjKuWIITcAA^3?Ox_ssG9_QX&N zsreL6Z3dC9wP&Pfre_cEf=KYp@c4T~x6Y&X9Cq(_&2q|)3C>f_dCuF8mG)CM(q7>_ zO-Bo@WCg$;)?f+5SY?7{g}#fiw<*qCWDd2Aw;*2Yy@PzG`AzaS1k4Y}3g{TvG%zt> zm;W}ucfLJ*lYLY^_q=HH6jME;L%&;xX&6_FEtYsfCHS&W8fZQ_`u!kpuwI2^2li(2mF%jS-BnW+lD3EpFs~3P3 z-4_W&4giEJ1XKx|0lvKeUIG6DKGBun-`m1=fKNRGD!4Zw3Lb;5gPLJ8=x-FDmN+2; z?KwDOR_Po-(^pHQC7U=#%mI~tci@il73{PNTqyUHUBd>l@0pVzPK{zTKs)e`YD1NK z&V!X-=8kg{?%S?N*Er`%hmT{A-D=AMYhPk}ZM$H5YU^j8V%ORS+JU32*3aE8>>LEYL0OAT73vL9-(#Bfu`^Nh9^S*ES$NPH4GGk%4E z7eoGpeh*Cuc^)(-Pz(qSyc`hYzu&jeC&S0;;{g8D%gx?~h1xypjo|(ajglu5i6A0} zFaU3m20%fcgtNE-?}I8}wfs%|Eu`@!oRK>SqL{$GW@Fe{%sToz6;Ji06jX}m5pbps zr*2RyXn!UF#JV7EC_ht(5dkbDX~3PoN@Nyt3Lq%gP#Q$J*XRzkGw>IA4crna_#T`9 zPk=?>M`#8!Qy%o_nc^rh1NhDS;QR17pbA;fP2yT`CawuLlN-auauMLq`@r?_5r|@+ zz=;j!4SWl}5jfuuKuo&@PI*tR4X9gEITe2%boaZsK3r?A6-RRoLG_T$6@mMKHVdFD T0DKq?=vh&KKfuucU%&nznnSHn diff --git a/Integration/inputs/recognize_stop_test.wav b/Integration/inputs/recognize_stop_test.wav deleted file mode 100755 index b9802c952cf6f65734d1381ab9ab9de511ba4a56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 220118 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�V)Td{tEZ?_;+AiCBIL6uE*#59yR=jixo1x9} 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 z3ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWA zpa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S> z01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW0 z3ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWA zpa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S> z01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW0 z3ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWA zpa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S> z01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW0 z3ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWA zpa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S> w01BW03ZMWApa2S>01BW03ZMWApa2S>01BW03ZMWApa2S>01BYM|1p981%LP>O#lD@ diff --git a/Integration/inputs/recognize_stop_timer_test.wav b/Integration/inputs/recognize_stop_timer_test.wav deleted file mode 100755 index f278b2f219312afe9a5aef053c726792e3f2dc3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240400 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 diff --git a/Integration/inputs/recognize_test.wav b/Integration/inputs/recognize_test.wav deleted file mode 100755 index 9f0462f5edfdf3fe5c69eb07a5f322c2d6214eef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72382 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 Dk+3iC diff --git a/Integration/inputs/recognize_timer_test.wav b/Integration/inputs/recognize_timer_test.wav deleted file mode 100755 index b7116821cdf124773cd2f6fc0775e6ef5e913e5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 311098 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 zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* LAORBi|1R)jCZmo-!-k9RL8ywyXu&SquOL+(CzK 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 z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs W0RjXF5FkK+009C72oU%m3H%3hf7)CC diff --git a/Integration/inputs/recognize_volume_up_test.wav b/Integration/inputs/recognize_volume_up_test.wav deleted file mode 100755 index 2075dcbab36b78749a10c7ac009f48dfa2bf7f25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32062 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 diff --git a/Integration/inputs/recognize_weather_test.wav b/Integration/inputs/recognize_weather_test.wav deleted file mode 100755 index 80adfced19ab4c5fa7aba9a1aabbd644f243a261..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 220832 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 diff --git a/Integration/inputs/recognize_whats_up_test.wav b/Integration/inputs/recognize_whats_up_test.wav deleted file mode 100755 index 42aa2d4cb51ba5c6e0de5ac891c05e9d99d5f23b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25706 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%0Dea?U%^M;}P8yB)LB_zgYt27n}FTcp%Qrhj^G{) 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 - -#include - -#include -#include -#include -#include - -#include -#include - -#include "Integration/ACLTestContext.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -using namespace acl; -using namespace avsCommon::avs::attachment; -using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::utils::configuration; -using namespace avsCommon::utils::libcurlUtils; -using namespace authorization::cblAuthDelegate; -using namespace contextManager; -using namespace registrationManager; - -std::unique_ptr ACLTestContext::create(const std::string& filePath, const std::string& overlay) { - std::unique_ptr context(new ACLTestContext(filePath, overlay)); - if (context->m_authDelegateTestContext && context->m_attachmentManager && context->m_messageRouter && - context->m_connectionStatusObserver && context->m_contextManager) { - return context; - } - return nullptr; -} - -ACLTestContext::~ACLTestContext() { - m_attachmentManager.reset(); - - if (m_messageRouter) { - m_messageRouter->shutdown(); - } - - m_connectionStatusObserver.reset(); - m_contextManager.reset(); - m_authDelegateTestContext.reset(); -} - -std::shared_ptr ACLTestContext::getAuthDelegate() const { - return m_authDelegateTestContext->getAuthDelegate(); -} - -std::shared_ptr ACLTestContext::getCustomerDataManager() const { - return m_authDelegateTestContext->getCustomerDataManager(); -} - -std::shared_ptr ACLTestContext::getAttachmentManager() const { - return m_attachmentManager; -} - -std::shared_ptr ACLTestContext::getMessageRouter() const { - return m_messageRouter; -} - -std::shared_ptr ACLTestContext::getConnectionStatusObserver() const { - return m_connectionStatusObserver; -} - -std::shared_ptr ACLTestContext::getContextManager() const { - return m_contextManager; -} - -void ACLTestContext::waitForConnected() { - ASSERT_TRUE(m_connectionStatusObserver->waitFor(ConnectionStatusObserverInterface::Status::CONNECTED)) - << "Connecting timed out"; -} - -void ACLTestContext::waitForDisconnected() { - ASSERT_TRUE(m_connectionStatusObserver->waitFor(ConnectionStatusObserverInterface::Status::DISCONNECTED)) - << "Disconnecting timed out"; -} - -/// Default @c AVS gateway to connect to. -static const std::string DEFAULT_AVS_GATEWAY = "https://alexa.na.gateway.devices.a2z.com"; - -ACLTestContext::ACLTestContext(const std::string& filePath, const std::string& overlay) { - m_authDelegateTestContext = AuthDelegateTestContext::create(filePath, overlay); - EXPECT_TRUE(m_authDelegateTestContext); - if (!m_authDelegateTestContext) { - return; - } - - auto config = ConfigurationNode::getRoot(); - EXPECT_TRUE(config); - if (!config) { - return; - } - - auto deviceInfo = - avsCommon::utils::DeviceInfo::createFromConfiguration(std::make_shared(config)); - EXPECT_TRUE(deviceInfo); - if (!deviceInfo) { - return; - } - - m_contextManager = ContextManager::createContextManagerInterface(std::move(deviceInfo)); - EXPECT_TRUE(m_contextManager); - if (!m_contextManager) { - return; - } - - auto synchronizeStateSenderFactory = - synchronizeStateSender::SynchronizeStateSenderFactory::create(m_contextManager); - std::vector> providers; - providers.push_back(synchronizeStateSenderFactory); - auto postConnectFactory = acl::PostConnectSequencerFactory::create(providers); - auto http2ConnectionFactory = std::make_shared(); - auto transportFactory = std::make_shared(http2ConnectionFactory, postConnectFactory); - m_attachmentManager = std::make_shared(AttachmentManager::AttachmentType::IN_PROCESS); - m_messageRouter = - std::make_shared(getAuthDelegate(), m_attachmentManager, transportFactory, DEFAULT_AVS_GATEWAY); - m_connectionStatusObserver = std::make_shared(); -} - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/AipStateObserver.cpp b/Integration/src/AipStateObserver.cpp deleted file mode 100644 index 012a0aa427..0000000000 --- a/Integration/src/AipStateObserver.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/AipStateObserver.h" - -namespace alexaClientSDK { -namespace integration { - -using avsCommon::sdkInterfaces::AudioInputProcessorObserverInterface; - -AipStateObserver::AipStateObserver() : m_state(AudioInputProcessorObserverInterface::State::IDLE) { -} - -void AipStateObserver::onStateChanged(AudioInputProcessorObserverInterface::State newState) { - std::unique_lock lock(m_mutex); - m_queue.push_back(newState); - m_state = newState; - m_wakeTrigger.notify_all(); -} - -bool AipStateObserver::checkState( - const AudioInputProcessorObserverInterface::State expectedState, - const std::chrono::seconds duration) { - AudioInputProcessorObserverInterface::State hold = waitForNext(duration); - return hold == expectedState; -} - -AudioInputProcessorObserverInterface::State AipStateObserver::waitForNext(const std::chrono::seconds duration) { - AudioInputProcessorObserverInterface::State ret; - std::unique_lock lock(m_mutex); - if (!m_wakeTrigger.wait_for(lock, duration, [this]() { return !m_queue.empty(); })) { - ret = AudioInputProcessorObserverInterface::State::IDLE; - return ret; - } - ret = m_queue.front(); - m_queue.pop_front(); - return ret; -} - -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/AuthDelegateTestContext.cpp b/Integration/src/AuthDelegateTestContext.cpp deleted file mode 100644 index 662e36bea4..0000000000 --- a/Integration/src/AuthDelegateTestContext.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 -#ifdef ACSDK_ACS_UTILS -#include -#include -#else -#include -#include -#endif -#include - -#include "Integration/AuthDelegateTestContext.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::utils::configuration; -#ifdef ACSDK_ACS_UTILS -using namespace acsdkffsAuthDelegate; -#else -using namespace authorization::cblAuthDelegate; -#endif -using namespace contextManager; -using namespace registrationManager; -using namespace ::testing; - -std::unique_ptr AuthDelegateTestContext::create( - const std::string& filePath, - const std::string& overlay) { - std::unique_ptr context(new AuthDelegateTestContext(filePath, overlay)); - if (context->m_sdkTestContext && context->m_customerDataManager && context->m_authDelegate) { - return context; - } - return nullptr; -} - -AuthDelegateTestContext::~AuthDelegateTestContext() { - m_authDelegate.reset(); - m_customerDataManager.reset(); - m_sdkTestContext.reset(); -} - -#ifdef ACSDK_ACS_UTILS -void AuthDelegateTestContext::AuthRequester::onRequestFFS() { -} -#else -void AuthDelegateTestContext::AuthRequester::onRequestAuthorization(const std::string& url, const std::string& code) { - ASSERT_FALSE(true) << "FATAL ERROR: Authorization required before running integration test"; -} - -void AuthDelegateTestContext::AuthRequester::onCheckingForAuthorization() { -} -#endif - -bool AuthDelegateTestContext::isValid() const { - return m_customerDataManager && m_authDelegate; -} - -std::shared_ptr AuthDelegateTestContext::getAuthDelegate() const { - return m_authDelegate; -} - -std::shared_ptr AuthDelegateTestContext::getCustomerDataManager() - const { - return m_customerDataManager; -} - -AuthDelegateTestContext::AuthDelegateTestContext(const std::string& filePath, const std::string& overlay) { - m_sdkTestContext = SDKTestContext::create(filePath, overlay); - EXPECT_TRUE(m_sdkTestContext); - if (!m_sdkTestContext) { - return; - } - - auto config = std::make_shared(ConfigurationNode::getRoot()); - EXPECT_TRUE(*config); - if (!*config) { - return; - } - - // TODO: use configuration or parameter here to determine which kind of AuthDelegate to create. - - m_customerDataManager = registrationManager::CustomerDataManagerFactory::createCustomerDataManagerInterface(); - EXPECT_TRUE(m_customerDataManager); - if (!m_customerDataManager) { - return; - } - -#ifdef ACSDK_ACS_UTILS - auto storage = ACSFFSAuthDelegateStorage::createFFSAuthDelegateStorageInterface(); - EXPECT_TRUE(storage); - if (!storage) { - return; - } - - m_authDelegate = FFSAuthDelegate::createFFSAuthDelegateInterface( - config, - m_customerDataManager, - std::move(storage), - std::make_shared(), - avsCommon::utils::libcurlUtils::HttpPost::create(), - avsCommon::utils::DeviceInfo::createFromConfiguration(config)); - - EXPECT_TRUE(m_authDelegate); -#else - auto storage = SQLiteCBLAuthDelegateStorage::createCBLAuthDelegateStorageInterface(config, nullptr, nullptr); - EXPECT_TRUE(storage); - if (!storage) { - return; - } - - m_authDelegate = CBLAuthDelegate::createAuthDelegateInterface( - config, - m_customerDataManager, - std::move(storage), - std::make_shared(), - avsCommon::utils::libcurlUtils::HttpPost::create(), - avsCommon::utils::DeviceInfo::createFromConfiguration(config)); - - EXPECT_TRUE(m_authDelegate); -#endif -} - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/AuthObserver.cpp b/Integration/src/AuthObserver.cpp deleted file mode 100644 index 248a4f481f..0000000000 --- a/Integration/src/AuthObserver.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/AuthObserver.h" -#include - -namespace alexaClientSDK { -namespace integration { - -using avsCommon::sdkInterfaces::AuthObserverInterface; - -AuthObserver::AuthObserver() : - m_authState(AuthObserverInterface::State::UNINITIALIZED), - m_authError(AuthObserverInterface::Error::SUCCESS) { -} - -void AuthObserver::onAuthStateChange( - const AuthObserverInterface::State authState, - const AuthObserverInterface::Error authError) { - m_authState = authState; - m_authError = authError; - m_wakeTrigger.notify_all(); -} - -AuthObserverInterface::State AuthObserver::getAuthState() const { - return m_authState; -} - -bool AuthObserver::waitFor(const AuthObserverInterface::State authState, const std::chrono::seconds duration) { - std::unique_lock lock(m_mutex); - return m_wakeTrigger.wait_for(lock, duration, [this, authState]() { return m_authState == authState; }); -} - -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/CMakeLists.txt b/Integration/src/CMakeLists.txt deleted file mode 100644 index 7aca9a6fb6..0000000000 --- a/Integration/src/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -if(BUILD_TESTING) - add_definitions("-DACSDK_LOG_MODULE=integration") - file(GLOB_RECURSE INTEGRATION_SRC "*.cpp") - add_library(Integration STATIC "${INTEGRATION_SRC}") - target_include_directories(Integration PUBLIC "${ACL_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${CBLAuthDelegate_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${ContextManager_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${Integration_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${CapabilityAgents_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${AIP_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${SpeechSynthesizer_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${AudioPlayer_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${AVSSystem_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${RegistrationManager_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${SQLiteStorage_SOURCE_DIR}/include") - target_include_directories(Integration PUBLIC "${SynchronizeStateSender_SOURCE_DIR}/include") - - target_link_libraries(Integration - ACL - AudioResources - CBLAuthDelegate - ContextManager - gtest - gmock - RegistrationManager - SynchronizeStateSender - acsdkAlerts) - if(ACS_UTILS) - target_link_libraries(Integration acsdkFFSAuthDelegate) - endif() -endif() \ No newline at end of file diff --git a/Integration/src/ClientMessageHandler.cpp b/Integration/src/ClientMessageHandler.cpp deleted file mode 100644 index c6fd245fab..0000000000 --- a/Integration/src/ClientMessageHandler.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/ClientMessageHandler.h" -#include - -namespace alexaClientSDK { -namespace integration { - -using namespace avsCommon::avs::attachment; - -ClientMessageHandler::ClientMessageHandler(std::shared_ptr attachmentManager) : - m_count{0}, - m_attachmentManager{attachmentManager} { -} - -void ClientMessageHandler::receive(const std::string& contextId, const std::string& message) { - std::cout << "ClientMessageHandler::receive: message:" << message << std::endl; - std::unique_lock lock(m_mutex); - ++m_count; - m_wakeTrigger.notify_all(); -} - -bool ClientMessageHandler::waitForNext(const std::chrono::seconds duration) { - std::unique_lock lock(m_mutex); - if (!m_wakeTrigger.wait_for(lock, duration, [this]() { return m_count > 0; })) { - return false; - } - --m_count; - return true; -} - -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/ConnectionStatusObserver.cpp b/Integration/src/ConnectionStatusObserver.cpp deleted file mode 100644 index 9625883025..0000000000 --- a/Integration/src/ConnectionStatusObserver.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/ConnectionStatusObserver.h" - -#include - -namespace alexaClientSDK { -namespace integration { - -using alexaClientSDK::avsCommon::sdkInterfaces::ConnectionStatusObserverInterface; - -ConnectionStatusObserver::ConnectionStatusObserver() : - m_statusChanges({std::make_pair(Status::DISCONNECTED, ChangedReason::ACL_CLIENT_REQUEST)}) { -} - -void ConnectionStatusObserver::onConnectionStatusChanged(const Status connectionStatus, const ChangedReason reason) { - std::lock_guard lock(m_mutex); - m_statusChanges.push_back(std::make_pair(connectionStatus, reason)); - m_wakeTrigger.notify_all(); -} - -bool ConnectionStatusObserver::checkForServerSideDisconnect() { - std::lock_guard lock(m_mutex); - for (auto pairValue : m_statusChanges) { - if (pairValue.first == Status::PENDING && pairValue.second == ChangedReason::SERVER_SIDE_DISCONNECT) { - return true; - } - } - return false; -} - -ConnectionStatusObserverInterface::Status ConnectionStatusObserver::getConnectionStatus() const { - std::lock_guard lock(m_mutex); - return m_statusChanges.back().first; -} - -bool ConnectionStatusObserver::waitFor(const Status connectionStatus, const std::chrono::seconds duration) { - std::unique_lock lock(m_mutex); - return m_wakeTrigger.wait_for( - lock, duration, [this, connectionStatus]() { return m_statusChanges.back().first == connectionStatus; }); -} - -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/ObservableMessageRequest.cpp b/Integration/src/ObservableMessageRequest.cpp deleted file mode 100644 index fd26425ebd..0000000000 --- a/Integration/src/ObservableMessageRequest.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/ObservableMessageRequest.h" - -#include - -namespace alexaClientSDK { -namespace integration { - -/// String to identify log entries originating from this file. -static const std::string TAG("ObservableMessageRequest"); - -/** - * 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::avs; -using namespace avsCommon::avs::attachment; - -/// The field name for the user voice attachment. -static const std::string AUDIO_ATTACHMENT_FIELD_NAME = "audio"; - -ObservableMessageRequest::ObservableMessageRequest( - const std::string& jsonContent, - std::shared_ptr attachmentReader) : - MessageRequest{jsonContent}, - m_sendMessageStatus(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::PENDING), - m_sendCompleted{false}, - m_exceptionReceived{false} { - if (attachmentReader) { - addAttachmentReader(AUDIO_ATTACHMENT_FIELD_NAME, attachmentReader); - } -} - -void ObservableMessageRequest::sendCompleted( - avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status sendMessageStatus) { - std::lock_guard lock(m_mutex); - ACSDK_DEBUG(LX("onSendCompleted").d("status", sendMessageStatus)); - m_sendMessageStatus = sendMessageStatus; - m_sendCompleted = true; - m_wakeTrigger.notify_all(); -} - -avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status ObservableMessageRequest::getSendMessageStatus() - const { - std::lock_guard lock(m_mutex); - return m_sendMessageStatus; -} - -bool ObservableMessageRequest::waitFor( - const avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status sendMessageStatus, - const std::chrono::seconds duration) { - std::unique_lock lock(m_mutex); - return m_wakeTrigger.wait_for( - lock, duration, [this, sendMessageStatus]() { return sendMessageStatus == m_sendMessageStatus; }); -} - -void ObservableMessageRequest::exceptionReceived(const std::string& exceptionMessage) { - ACSDK_DEBUG(LX("onExceptionReceived").d("status", exceptionMessage)); - m_exceptionReceived = true; -} - -bool ObservableMessageRequest::hasSendCompleted() { - return m_sendCompleted; -} - -bool ObservableMessageRequest::wasExceptionReceived() { - return m_exceptionReceived; -} - -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/SDKTestContext.cpp b/Integration/src/SDKTestContext.cpp deleted file mode 100644 index b877f17f32..0000000000 --- a/Integration/src/SDKTestContext.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/SDKTestContext.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -using namespace avsCommon::avs::initialization; - -std::unique_ptr SDKTestContext::create(const std::string& filePath, const std::string& overlay) { - std::unique_ptr context(new SDKTestContext(filePath, overlay)); - if (AlexaClientSDKInit::isInitialized()) { - return context; - } - return nullptr; -} - -SDKTestContext::~SDKTestContext() { - AlexaClientSDKInit::uninitialize(); -} - -SDKTestContext::SDKTestContext(const std::string& filePath, const std::string& overlay) { - std::vector> streams; - - auto infile = std::shared_ptr(new std::ifstream(filePath)); - EXPECT_TRUE(infile->good()); - if (!infile->good()) { - return; - } - streams.push_back(infile); - - auto overlayStream = std::shared_ptr(new std::stringstream()); - if (!overlay.empty()) { - (*overlayStream) << overlay; - streams.push_back(overlayStream); - } - - EXPECT_TRUE(AlexaClientSDKInit::initialize(streams)); -} - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/TestAlertObserver.cpp b/Integration/src/TestAlertObserver.cpp deleted file mode 100644 index 82233cb52b..0000000000 --- a/Integration/src/TestAlertObserver.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/TestAlertObserver.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -void TestAlertObserver::onAlertStateChange(const AlertObserverInterface::AlertInfo& alertInfo) { - std::unique_lock lock(m_mutex); - TestAlertObserver::changedAlert ca; - ca.state = alertInfo.state; - m_queue.push_back(ca); - m_wakeTrigger.notify_all(); -} - -TestAlertObserver::changedAlert TestAlertObserver::waitForNext(const std::chrono::seconds duration) { - TestAlertObserver::changedAlert ret; - std::unique_lock lock(m_mutex); - if (!m_wakeTrigger.wait_for(lock, duration, [this]() { return !m_queue.empty(); })) { - ret.state = currentState; - return ret; - } - ret = m_queue.front(); - m_queue.pop_front(); - return ret; -} - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/TestDirectiveHandler.cpp b/Integration/src/TestDirectiveHandler.cpp deleted file mode 100644 index 7137a88da0..0000000000 --- a/Integration/src/TestDirectiveHandler.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/TestDirectiveHandler.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -TestDirectiveHandler::TestDirectiveHandler(avsCommon::avs::DirectiveHandlerConfiguration config) : - m_configuration{config} { -} - -void TestDirectiveHandler::handleDirectiveImmediately(std::shared_ptr directive) { - std::unique_lock lock(m_mutex); - TestDirectiveHandler::DirectiveParams dp; - dp.type = DirectiveParams::Type::HANDLE_IMMEDIATELY; - dp.directive = directive; - m_queue.push_back(dp); - m_wakeTrigger.notify_all(); -} - -void TestDirectiveHandler::preHandleDirective( - std::shared_ptr directive, - std::unique_ptr result) { - std::unique_lock lock(m_mutex); - TestDirectiveHandler::DirectiveParams dp; - dp.type = TestDirectiveHandler::DirectiveParams::Type::PREHANDLE; - dp.directive = directive; - dp.result = std::move(result); - m_results[directive->getMessageId()] = dp.result; - m_directives[directive->getMessageId()] = directive; - m_queue.push_back(dp); - m_wakeTrigger.notify_all(); -} - -bool TestDirectiveHandler::handleDirective(const std::string& messageId) { - std::unique_lock lock(m_mutex); - TestDirectiveHandler::DirectiveParams dp; - dp.type = DirectiveParams::Type::HANDLE; - auto result = m_results.find(messageId); - if (m_results.end() == result) { - return false; - } - dp.result = result->second; - auto directive = m_directives.find(messageId); - if (m_directives.end() == directive) { - return false; - } - dp.directive = directive->second; - m_queue.push_back(dp); - m_wakeTrigger.notify_all(); - return true; -} - -void TestDirectiveHandler::cancelDirective(const std::string& messageId) { - std::unique_lock lock(m_mutex); - TestDirectiveHandler::DirectiveParams dp; - dp.type = DirectiveParams::Type::CANCEL; - auto result = m_results.find(messageId); - dp.result = result->second; - m_results.erase(result); - auto directive = m_directives.find(messageId); - dp.directive = directive->second; - m_directives.erase(directive); - m_queue.push_back(dp); - m_wakeTrigger.notify_all(); -} - -avsCommon::avs::DirectiveHandlerConfiguration TestDirectiveHandler::getConfiguration() const { - return m_configuration; -} - -void TestDirectiveHandler::onDeregistered() { -} - -TestDirectiveHandler::DirectiveParams TestDirectiveHandler::waitForNext(const std::chrono::seconds duration) { - DirectiveParams ret; - std::unique_lock lock(m_mutex); - if (!m_wakeTrigger.wait_for(lock, duration, [this]() { return !m_queue.empty(); })) { - ret.type = DirectiveParams::Type::TIMEOUT; - return ret; - } - ret = m_queue.front(); - m_queue.pop_front(); - return ret; -} - -TestDirectiveHandler::DirectiveParams::DirectiveParams() : type{Type::UNSET} { -} -} // namespace test -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/TestExceptionEncounteredSender.cpp b/Integration/src/TestExceptionEncounteredSender.cpp deleted file mode 100644 index e0b558d809..0000000000 --- a/Integration/src/TestExceptionEncounteredSender.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/TestExceptionEncounteredSender.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -using namespace avsCommon::utils::json; - -/// JSON key to get the event object of a message. -static const std::string JSON_MESSAGE_EVENT_KEY = "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_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"; - -void TestExceptionEncounteredSender::sendExceptionEncountered( - const std::string& unparsedDirective, - avs::ExceptionErrorType error, - const std::string& message) { - std::unique_lock lock(m_mutex); - ExceptionParams dp; - dp.type = ExceptionParams::Type::EXCEPTION; - dp.directive = parseDirective( - unparsedDirective, - std::make_shared( - avsCommon::avs::attachment::AttachmentManager::AttachmentType::IN_PROCESS)); - dp.exceptionUnparsedDirective = unparsedDirective; - dp.exceptionError = error; - dp.exceptionMessage = message; - m_queue.push_back(dp); - m_wakeTrigger.notify_all(); -} - -std::shared_ptr TestExceptionEncounteredSender::parseDirective( - const std::string& rawJSON, - std::shared_ptr attachmentManager) { - std::string directiveJSON; - std::string headerJSON; - std::string payloadJSON; - std::string nameSpace; - std::string name; - std::string messageId; - std::string dialogRequestId; - - if (!jsonUtils::retrieveValue(rawJSON, JSON_MESSAGE_DIRECTIVE_KEY, &directiveJSON) || - !jsonUtils::retrieveValue(directiveJSON, JSON_MESSAGE_HEADER_KEY, &headerJSON) || - !jsonUtils::retrieveValue(directiveJSON, JSON_MESSAGE_PAYLOAD_KEY, &payloadJSON) || - !jsonUtils::retrieveValue(headerJSON, JSON_MESSAGE_NAMESPACE_KEY, &nameSpace) || - !jsonUtils::retrieveValue(headerJSON, JSON_MESSAGE_NAME_KEY, &name) || - !jsonUtils::retrieveValue(headerJSON, JSON_MESSAGE_MESSAGE_ID_KEY, &messageId)) { - return nullptr; - } - - jsonUtils::retrieveValue(headerJSON, JSON_MESSAGE_NAMESPACE_KEY, &dialogRequestId); - - auto header = std::make_shared(nameSpace, name, messageId, dialogRequestId); - return avsCommon::avs::AVSDirective::create(rawJSON, header, payloadJSON, attachmentManager, ""); -} - -TestExceptionEncounteredSender::ExceptionParams TestExceptionEncounteredSender::waitForNext( - const std::chrono::seconds duration) { - ExceptionParams ret; - std::unique_lock lock(m_mutex); - if (!m_wakeTrigger.wait_for(lock, duration, [this]() { return !m_queue.empty(); })) { - ret.type = ExceptionParams::Type::TIMEOUT; - return ret; - } - ret = m_queue.front(); - m_queue.pop_front(); - return ret; -} - -TestExceptionEncounteredSender::ExceptionParams::ExceptionParams() : - type{Type::UNSET}, - exceptionError{avsCommon::avs::ExceptionErrorType::INTERNAL_ERROR} { -} - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/TestMediaPlayer.cpp b/Integration/src/TestMediaPlayer.cpp deleted file mode 100644 index 67521e02d5..0000000000 --- a/Integration/src/TestMediaPlayer.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/TestMediaPlayer.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -/// String to identify log entries originating from this file. -static const std::string TAG("TestMediaPlayer"); - -/// Default media player state for reporting all playback eventing -static const avsCommon::utils::mediaPlayer::MediaPlayerState DEFAULT_MEDIA_PLAYER_STATE = { - std::chrono::milliseconds(0)}; - -/// A counter used to increment the source id when a new source is set. -static std::atomic g_sourceId{0}; - -/** - * 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) - -TestMediaPlayer::~TestMediaPlayer() { -} - -avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId TestMediaPlayer::setSource( - std::shared_ptr attachmentReader, - const avsCommon::utils::AudioFormat* audioFormat, - const avsCommon::utils::mediaPlayer::SourceConfig& config) { - m_attachmentReader = std::move(attachmentReader); - return ++g_sourceId; -} - -avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId TestMediaPlayer::setSource( - std::shared_ptr attachmentReader, - std::chrono::milliseconds offsetAdjustment, - const avsCommon::utils::AudioFormat* audioFormat, - const avsCommon::utils::mediaPlayer::SourceConfig& config) { - m_attachmentReader = std::move(attachmentReader); - return ++g_sourceId; -} - -avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId TestMediaPlayer::setSource( - std::shared_ptr stream, - bool repeat, - const avsCommon::utils::mediaPlayer::SourceConfig& config, - avsCommon::utils::MediaType format) { - m_istream = stream; - return ++g_sourceId; -} - -avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId TestMediaPlayer::setSource( - const std::string& url, - std::chrono::milliseconds offset, - const avsCommon::utils::mediaPlayer::SourceConfig& config, - bool repeat, - const avsCommon::utils::mediaPlayer::PlaybackContext& playbackContext) { - return ++g_sourceId; -} - -bool TestMediaPlayer::play(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) { - if (m_observers.empty()) { - return false; - } - for (const auto& observer : m_observers) { - observer->onPlaybackStarted(id, DEFAULT_MEDIA_PLAYER_STATE); - } - m_playbackFinished = true; - m_timer = std::unique_ptr(new avsCommon::utils::timing::Timer); - // Wait 600 milliseconds before sending onPlaybackFinished. - m_timer->start(std::chrono::milliseconds(600), [this, id] { - for (const auto& observer : m_observers) { - if (m_playbackFinished) { - observer->onPlaybackFinished(id, DEFAULT_MEDIA_PLAYER_STATE); - } - } - m_playbackFinished = false; - }); - return true; -} - -bool TestMediaPlayer::stop(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) { - if (!m_playbackFinished || m_observers.empty()) { - return false; - } - for (const auto& observer : m_observers) { - if (m_playbackFinished) { - observer->onPlaybackStopped(id, DEFAULT_MEDIA_PLAYER_STATE); - } - } - m_playbackFinished = false; - return true; -} - -// TODO Add implementation -bool TestMediaPlayer::pause(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) { - return true; -} - -// TODO Add implementation -bool TestMediaPlayer::resume(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) { - return true; -} - -std::chrono::milliseconds TestMediaPlayer::getOffset(avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) { - return std::chrono::milliseconds::zero(); -} - -void TestMediaPlayer::addObserver( - std::shared_ptr playerObserver) { - m_observers.insert(playerObserver); -} - -void TestMediaPlayer::removeObserver( - std::shared_ptr playerObserver) { - m_observers.erase(playerObserver); -} - -uint64_t TestMediaPlayer::getNumBytesBuffered() { - return 0; -} - -avsCommon::utils::Optional TestMediaPlayer::getMediaPlayerState( - avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) { - auto offset = getOffset(id); - auto state = avsCommon::utils::mediaPlayer::MediaPlayerState(); - state.offset = offset; - return avsCommon::utils::Optional(state); -} - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/TestMessageSender.cpp b/Integration/src/TestMessageSender.cpp deleted file mode 100644 index 37f88e6e23..0000000000 --- a/Integration/src/TestMessageSender.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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/AVSConnectionManager.h" -#include "Integration/TestMessageSender.h" - -using namespace alexaClientSDK; -using namespace acl; -using namespace avsCommon::avs; -using namespace avsCommon::sdkInterfaces; -using namespace avsCommon::utils; - -namespace alexaClientSDK { -namespace integration { -namespace test { - -TestMessageSender::TestMessageSender( - std::shared_ptr messageRouter, - bool isEnabled, - std::shared_ptr connectionStatusObserver, - std::shared_ptr messageObserver) : - RequiresShutdown{"TestMessageSender"} { - m_connectionManager = - acl::AVSConnectionManager::create(messageRouter, isEnabled, {connectionStatusObserver}, {messageObserver}); -} - -void TestMessageSender::sendMessage(std::shared_ptr request) { - m_connectionManager->sendMessage(request); - SendParams sendState; - std::unique_lock lock(m_mutex); - sendState.type = SendParams::Type::SEND; - sendState.request = request; - m_queue.push_back(sendState); - m_wakeTrigger.notify_all(); -} - -TestMessageSender::SendParams TestMessageSender::waitForNext(const std::chrono::seconds duration) { - SendParams ret; - std::unique_lock lock(m_mutex); - if (!m_wakeTrigger.wait_for(lock, duration, [this]() { return !m_queue.empty(); })) { - ret.type = SendParams::Type::TIMEOUT; - return ret; - } - ret = m_queue.front(); - m_queue.pop_front(); - return ret; -} - -void TestMessageSender::enable() { - m_connectionManager->enable(); -} - -void TestMessageSender::disable() { - m_connectionManager->disable(); -} - -bool TestMessageSender::isEnabled() { - return m_connectionManager->isEnabled(); -} - -void TestMessageSender::reconnect() { - m_connectionManager->reconnect(); -} - -void TestMessageSender::setAVSGateway(const std::string& avsGateway) { - m_connectionManager->setAVSGateway(avsGateway); -} - -void TestMessageSender::addConnectionStatusObserver( - std::shared_ptr observer) { - m_connectionManager->addConnectionStatusObserver(observer); -} - -void TestMessageSender::removeConnectionStatusObserver( - std::shared_ptr observer) { - m_connectionManager->removeConnectionStatusObserver(observer); -} - -void TestMessageSender::addMessageObserver( - std::shared_ptr observer) { - m_connectionManager->addMessageObserver(observer); -} - -void TestMessageSender::doShutdown() { - m_connectionManager->shutdown(); -} - -void TestMessageSender::removeMessageObserver( - std::shared_ptr observer) { - m_connectionManager->removeMessageObserver(observer); -} - -std::shared_ptr TestMessageSender::getConnectionManager() const { - return m_connectionManager; -} - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/src/TestSpeechSynthesizerObserver.cpp b/Integration/src/TestSpeechSynthesizerObserver.cpp deleted file mode 100644 index a2efec6dbb..0000000000 --- a/Integration/src/TestSpeechSynthesizerObserver.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 "Integration/TestSpeechSynthesizerObserver.h" - -namespace alexaClientSDK { -namespace integration { -namespace test { - -using avsCommon::sdkInterfaces::SpeechSynthesizerObserverInterface; - -TestSpeechSynthesizerObserver::TestSpeechSynthesizerObserver() : - m_state(SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED) { -} - -void TestSpeechSynthesizerObserver::onStateChanged( - SpeechSynthesizerObserverInterface::SpeechSynthesizerState state, - const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId mediaSourceId, - const avsCommon::utils::Optional& mediaPlayerState, - const std::vector& audioAnalyzerState) { - std::unique_lock lock(m_mutex); - m_state = state; - m_queue.push_back(state); - m_wakeTrigger.notify_all(); -} - -bool TestSpeechSynthesizerObserver::checkState( - const SpeechSynthesizerObserverInterface::SpeechSynthesizerState expectedState, - const std::chrono::seconds duration) { - // Pull the front of the state queue - SpeechSynthesizerObserverInterface::SpeechSynthesizerState hold = waitForNext(duration); - return (hold == expectedState); -} - -SpeechSynthesizerObserverInterface::SpeechSynthesizerState TestSpeechSynthesizerObserver::waitForNext( - const std::chrono::seconds duration) { - SpeechSynthesizerObserverInterface::SpeechSynthesizerState ret; - std::unique_lock lock(m_mutex); - if (!m_wakeTrigger.wait_for(lock, duration, [this]() { return !m_queue.empty(); })) { - return m_state; - } - - ret = m_queue.front(); - m_queue.pop_front(); - return ret; -} - -SpeechSynthesizerObserverInterface::SpeechSynthesizerState TestSpeechSynthesizerObserver::getCurrentState() { - return m_state; -} - -} // namespace test -} // namespace integration -} // namespace alexaClientSDK diff --git a/Integration/test/AlertsIntegrationTest.cpp b/Integration/test/AlertsIntegrationTest.cpp deleted file mode 100644 index b9e276df52..0000000000 --- a/Integration/test/AlertsIntegrationTest.cpp +++ /dev/null @@ -1,1449 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All 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 AlertsIntegrationTest.cpp - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include IgnoreResult(const A& an_action) { + return internal::IgnoreResultAction(an_action); +} + +// Creates a reference wrapper for the given L-value. If necessary, +// you can explicitly specify the type of the reference. For example, +// suppose 'derived' is an object of type Derived, ByRef(derived) +// would wrap a Derived&. If you want to wrap a const Base& instead, +// where Base is a base class of Derived, just write: +// +// ByRef(derived) +template +inline internal::ReferenceWrapper ByRef(T& l_value) { // NOLINT + return internal::ReferenceWrapper(l_value); +} + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-cardinalities.h b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-cardinalities.h new file mode 100644 index 0000000000..fc315f92ab --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-cardinalities.h @@ -0,0 +1,147 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used cardinalities. More +// cardinalities can be defined by the user implementing the +// CardinalityInterface interface if necessary. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ + +#include +#include // NOLINT +#include "gmock/internal/gmock-port.h" +#include "gtest/gtest.h" + +namespace testing { + +// To implement a cardinality Foo, define: +// 1. a class FooCardinality that implements the +// CardinalityInterface interface, and +// 2. a factory function that creates a Cardinality object from a +// const FooCardinality*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Cardinality objects can now be copied like plain values. + +// The implementation of a cardinality. +class CardinalityInterface { + public: + virtual ~CardinalityInterface() {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + virtual int ConservativeLowerBound() const { return 0; } + virtual int ConservativeUpperBound() const { return INT_MAX; } + + // Returns true iff call_count calls will satisfy this cardinality. + virtual bool IsSatisfiedByCallCount(int call_count) const = 0; + + // Returns true iff call_count calls will saturate this cardinality. + virtual bool IsSaturatedByCallCount(int call_count) const = 0; + + // Describes self to an ostream. + virtual void DescribeTo(::std::ostream* os) const = 0; +}; + +// A Cardinality is a copyable and IMMUTABLE (except by assignment) +// object that specifies how many times a mock function is expected to +// be called. The implementation of Cardinality is just a linked_ptr +// to const CardinalityInterface, so copying is fairly cheap. +// Don't inherit from Cardinality! +class GTEST_API_ Cardinality { + public: + // Constructs a null cardinality. Needed for storing Cardinality + // objects in STL containers. + Cardinality() {} + + // Constructs a Cardinality from its implementation. + explicit Cardinality(const CardinalityInterface* impl) : impl_(impl) {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + int ConservativeLowerBound() const { return impl_->ConservativeLowerBound(); } + int ConservativeUpperBound() const { return impl_->ConservativeUpperBound(); } + + // Returns true iff call_count calls will satisfy this cardinality. + bool IsSatisfiedByCallCount(int call_count) const { + return impl_->IsSatisfiedByCallCount(call_count); + } + + // Returns true iff call_count calls will saturate this cardinality. + bool IsSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count); + } + + // Returns true iff call_count calls will over-saturate this + // cardinality, i.e. exceed the maximum number of allowed calls. + bool IsOverSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count) && + !impl_->IsSatisfiedByCallCount(call_count); + } + + // Describes self to an ostream + void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } + + // Describes the given actual call count to an ostream. + static void DescribeActualCallCountTo(int actual_call_count, + ::std::ostream* os); + + private: + internal::linked_ptr impl_; +}; + +// Creates a cardinality that allows at least n calls. +GTEST_API_ Cardinality AtLeast(int n); + +// Creates a cardinality that allows at most n calls. +GTEST_API_ Cardinality AtMost(int n); + +// Creates a cardinality that allows any number of calls. +GTEST_API_ Cardinality AnyNumber(); + +// Creates a cardinality that allows between min and max calls. +GTEST_API_ Cardinality Between(int min, int max); + +// Creates a cardinality that allows exactly n calls. +GTEST_API_ Cardinality Exactly(int n); + +// Creates a cardinality from its implementation. +inline Cardinality MakeCardinality(const CardinalityInterface* c) { + return Cardinality(c); +} + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-actions.h b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-actions.h new file mode 100644 index 0000000000..b5a889c0c3 --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-actions.h @@ -0,0 +1,2377 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic actions. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ + +#include "gmock/gmock-actions.h" +#include "gmock/internal/gmock-port.h" + +namespace testing { +namespace internal { + +// InvokeHelper knows how to unpack an N-tuple and invoke an N-ary +// function or method with the unpacked values, where F is a function +// type that takes N arguments. +template +class InvokeHelper; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple<>&) { + return function(); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple<>&) { + return (obj_ptr->*method_ptr)(); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args), get<1>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args), get<1>(args), get<2>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), + get<2>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args), get<1>(args), get<2>(args), + get<3>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), + get<2>(args), get<3>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), + get<2>(args), get<3>(args), get<4>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), + get<2>(args), get<3>(args), get<4>(args), get<5>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), + get<2>(args), get<3>(args), get<4>(args), get<5>(args), + get<6>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args), + get<7>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), + get<2>(args), get<3>(args), get<4>(args), get<5>(args), + get<6>(args), get<7>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args), + get<7>(args), get<8>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), + get<2>(args), get<3>(args), get<4>(args), get<5>(args), + get<6>(args), get<7>(args), get<8>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple& args) { + return function(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args), + get<7>(args), get<8>(args), get<9>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple& args) { + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), + get<2>(args), get<3>(args), get<4>(args), get<5>(args), + get<6>(args), get<7>(args), get<8>(args), get<9>(args)); + } +}; + +// An INTERNAL macro for extracting the type of a tuple field. It's +// subject to change without notice - DO NOT USE IN USER CODE! +#define GMOCK_FIELD_(Tuple, N) \ + typename ::testing::tuple_element::type + +// SelectArgs::type is the +// type of an n-ary function whose i-th (1-based) argument type is the +// k{i}-th (0-based) field of ArgumentTuple, which must be a tuple +// type, and whose return type is Result. For example, +// SelectArgs, 0, 3>::type +// is int(bool, long). +// +// SelectArgs::Select(args) +// returns the selected fields (k1, k2, ..., k_n) of args as a tuple. +// For example, +// SelectArgs, 2, 0>::Select( +// ::testing::make_tuple(true, 'a', 2.5)) +// returns tuple (2.5, true). +// +// The numbers in list k1, k2, ..., k_n must be >= 0, where n can be +// in the range [0, 10]. Duplicates are allowed and they don't have +// to be in an ascending or descending order. + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7), + GMOCK_FIELD_(ArgumentTuple, k8), GMOCK_FIELD_(ArgumentTuple, k9), + GMOCK_FIELD_(ArgumentTuple, k10)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args), + get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& /* args */) { + return SelectedArgs(); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args), get(args), get(args), + get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7), + GMOCK_FIELD_(ArgumentTuple, k8)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args), + get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7), + GMOCK_FIELD_(ArgumentTuple, k8), GMOCK_FIELD_(ArgumentTuple, k9)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args), + get(args), get(args)); + } +}; + +#undef GMOCK_FIELD_ + +// Implements the WithArgs action. +template +class WithArgsAction { + public: + explicit WithArgsAction(const InnerAction& action) : action_(action) {} + + template + operator Action() const { return MakeAction(new Impl(action_)); } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const InnerAction& action) : action_(action) {} + + virtual Result Perform(const ArgumentTuple& args) { + return action_.Perform(SelectArgs::Select(args)); + } + + private: + typedef typename SelectArgs::type InnerFunctionType; + + Action action_; + }; + + const InnerAction action_; + + GTEST_DISALLOW_ASSIGN_(WithArgsAction); +}; + +// A macro from the ACTION* family (defined later in this file) +// defines an action that can be used in a mock function. Typically, +// these actions only care about a subset of the arguments of the mock +// function. For example, if such an action only uses the second +// argument, it can be used in any mock function that takes >= 2 +// arguments where the type of the second argument is compatible. +// +// Therefore, the action implementation must be prepared to take more +// arguments than it needs. The ExcessiveArg type is used to +// represent those excessive arguments. In order to keep the compiler +// error messages tractable, we define it in the testing namespace +// instead of testing::internal. However, this is an INTERNAL TYPE +// and subject to change without notice, so a user MUST NOT USE THIS +// TYPE DIRECTLY. +struct ExcessiveArg {}; + +// A helper class needed for implementing the ACTION* macros. +template +class ActionHelper { + public: + static Result Perform(Impl* impl, const ::testing::tuple<>& args) { + return impl->template gmock_PerformImpl<>(args, ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, get<0>(args), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, get<0>(args), + get<1>(args), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, get<0>(args), + get<1>(args), get<2>(args), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, get<0>(args), + get<1>(args), get<2>(args), get<3>(args), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, + get<0>(args), get<1>(args), get<2>(args), get<3>(args), get<4>(args), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, + get<0>(args), get<1>(args), get<2>(args), get<3>(args), get<4>(args), + get<5>(args), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, + get<0>(args), get<1>(args), get<2>(args), get<3>(args), get<4>(args), + get<5>(args), get<6>(args), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::testing::tuple& args) { + return impl->template gmock_PerformImpl(args, get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args), + get<9>(args)); + } +}; + +} // namespace internal + +// Various overloads for Invoke(). + +// WithArgs(an_action) creates an action that passes +// the selected arguments of the mock function to an_action and +// performs it. It serves as an adaptor between actions with +// different argument lists. C++ doesn't support default arguments for +// function templates, so we have to overload it. +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +// Creates an action that does actions a1, a2, ..., sequentially in +// each invocation. +template +inline internal::DoBothAction +DoAll(Action1 a1, Action2 a2) { + return internal::DoBothAction(a1, a2); +} + +template +inline internal::DoBothAction > +DoAll(Action1 a1, Action2 a2, Action3 a3) { + return DoAll(a1, DoAll(a2, a3)); +} + +template +inline internal::DoBothAction > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4) { + return DoAll(a1, DoAll(a2, a3, a4)); +} + +template +inline internal::DoBothAction > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5) { + return DoAll(a1, DoAll(a2, a3, a4, a5)); +} + +template +inline internal::DoBothAction > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6)); +} + +template +inline internal::DoBothAction > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7)); +} + +template +inline internal::DoBothAction > > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7, Action8 a8) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7, a8)); +} + +template +inline internal::DoBothAction > > > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7, Action8 a8, Action9 a9) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7, a8, a9)); +} + +template +inline internal::DoBothAction > > > > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7, Action8 a8, Action9 a9, Action10 a10) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7, a8, a9, a10)); +} + +} // namespace testing + +// The ACTION* family of macros can be used in a namespace scope to +// define custom actions easily. The syntax: +// +// ACTION(name) { statements; } +// +// will define an action with the given name that executes the +// statements. The value returned by the statements will be used as +// the return value of the action. Inside the statements, you can +// refer to the K-th (0-based) argument of the mock function by +// 'argK', and refer to its type by 'argK_type'. For example: +// +// ACTION(IncrementArg1) { +// arg1_type temp = arg1; +// return ++(*temp); +// } +// +// allows you to write +// +// ...WillOnce(IncrementArg1()); +// +// You can also refer to the entire argument tuple and its type by +// 'args' and 'args_type', and refer to the mock function type and its +// return type by 'function_type' and 'return_type'. +// +// Note that you don't need to specify the types of the mock function +// arguments. However rest assured that your code is still type-safe: +// you'll get a compiler error if *arg1 doesn't support the ++ +// operator, or if the type of ++(*arg1) isn't compatible with the +// mock function's return type, for example. +// +// Sometimes you'll want to parameterize the action. For that you can use +// another macro: +// +// ACTION_P(name, param_name) { statements; } +// +// For example: +// +// ACTION_P(Add, n) { return arg0 + n; } +// +// will allow you to write: +// +// ...WillOnce(Add(5)); +// +// Note that you don't need to provide the type of the parameter +// either. If you need to reference the type of a parameter named +// 'foo', you can write 'foo_type'. For example, in the body of +// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type +// of 'n'. +// +// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P10 to support +// multi-parameter actions. +// +// For the purpose of typing, you can view +// +// ACTION_Pk(Foo, p1, ..., pk) { ... } +// +// as shorthand for +// +// template +// FooActionPk Foo(p1_type p1, ..., pk_type pk) { ... } +// +// In particular, you can provide the template type arguments +// explicitly when invoking Foo(), as in Foo(5, false); +// although usually you can rely on the compiler to infer the types +// for you automatically. You can assign the result of expression +// Foo(p1, ..., pk) to a variable of type FooActionPk. This can be useful when composing actions. +// +// You can also overload actions with different numbers of parameters: +// +// ACTION_P(Plus, a) { ... } +// ACTION_P2(Plus, a, b) { ... } +// +// While it's tempting to always use the ACTION* macros when defining +// a new action, you should also consider implementing ActionInterface +// or using MakePolymorphicAction() instead, especially if you need to +// use the action a lot. While these approaches require more work, +// they give you more control on the types of the mock function +// arguments and the action parameters, which in general leads to +// better compiler error messages that pay off in the long run. They +// also allow overloading actions based on parameter types (as opposed +// to just based on the number of parameters). +// +// CAVEAT: +// +// ACTION*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using ACTION*() inside +// a function. +// +// MORE INFORMATION: +// +// To learn more about using these macros, please search for 'ACTION' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +// An internal macro needed for implementing ACTION*(). +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_\ + const args_type& args GTEST_ATTRIBUTE_UNUSED_, \ + arg0_type arg0 GTEST_ATTRIBUTE_UNUSED_, \ + arg1_type arg1 GTEST_ATTRIBUTE_UNUSED_, \ + arg2_type arg2 GTEST_ATTRIBUTE_UNUSED_, \ + arg3_type arg3 GTEST_ATTRIBUTE_UNUSED_, \ + arg4_type arg4 GTEST_ATTRIBUTE_UNUSED_, \ + arg5_type arg5 GTEST_ATTRIBUTE_UNUSED_, \ + arg6_type arg6 GTEST_ATTRIBUTE_UNUSED_, \ + arg7_type arg7 GTEST_ATTRIBUTE_UNUSED_, \ + arg8_type arg8 GTEST_ATTRIBUTE_UNUSED_, \ + arg9_type arg9 GTEST_ATTRIBUTE_UNUSED_ + +// Sometimes you want to give an action explicit template parameters +// that cannot be inferred from its value parameters. ACTION() and +// ACTION_P*() don't support that. ACTION_TEMPLATE() remedies that +// and can be viewed as an extension to ACTION() and ACTION_P*(). +// +// The syntax: +// +// ACTION_TEMPLATE(ActionName, +// HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m), +// AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; } +// +// defines an action template that takes m explicit template +// parameters and n value parameters. name_i is the name of the i-th +// template parameter, and kind_i specifies whether it's a typename, +// an integral constant, or a template. p_i is the name of the i-th +// value parameter. +// +// Example: +// +// // DuplicateArg(output) converts the k-th argument of the mock +// // function to type T and copies it to *output. +// ACTION_TEMPLATE(DuplicateArg, +// HAS_2_TEMPLATE_PARAMS(int, k, typename, T), +// AND_1_VALUE_PARAMS(output)) { +// *output = T(::testing::get(args)); +// } +// ... +// int n; +// EXPECT_CALL(mock, Foo(_, _)) +// .WillOnce(DuplicateArg<1, unsigned char>(&n)); +// +// To create an instance of an action template, write: +// +// ActionName(v1, ..., v_n) +// +// where the ts are the template arguments and the vs are the value +// arguments. The value argument types are inferred by the compiler. +// If you want to explicitly specify the value argument types, you can +// provide additional template arguments: +// +// ActionName(v1, ..., v_n) +// +// where u_i is the desired type of v_i. +// +// ACTION_TEMPLATE and ACTION/ACTION_P* can be overloaded on the +// number of value parameters, but not on the number of template +// parameters. Without the restriction, the meaning of the following +// is unclear: +// +// OverloadedAction(x); +// +// Are we using a single-template-parameter action where 'bool' refers +// to the type of x, or are we using a two-template-parameter action +// where the compiler is asked to infer the type of x? +// +// Implementation notes: +// +// GMOCK_INTERNAL_*_HAS_m_TEMPLATE_PARAMS and +// GMOCK_INTERNAL_*_AND_n_VALUE_PARAMS are internal macros for +// implementing ACTION_TEMPLATE. The main trick we use is to create +// new macro invocations when expanding a macro. For example, we have +// +// #define ACTION_TEMPLATE(name, template_params, value_params) +// ... GMOCK_INTERNAL_DECL_##template_params ... +// +// which causes ACTION_TEMPLATE(..., HAS_1_TEMPLATE_PARAMS(typename, T), ...) +// to expand to +// +// ... GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(typename, T) ... +// +// Since GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS is a macro, the +// preprocessor will continue to expand it to +// +// ... typename T ... +// +// This technique conforms to the C++ standard and is portable. It +// allows us to implement action templates using O(N) code, where N is +// the maximum number of template/value parameters supported. Without +// using it, we'd have to devote O(N^2) amount of code to implement all +// combinations of m and n. + +// Declares the template parameters. +#define GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(kind0, name0) kind0 name0 +#define GMOCK_INTERNAL_DECL_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1) kind0 name0, kind1 name1 +#define GMOCK_INTERNAL_DECL_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2) kind0 name0, kind1 name1, kind2 name2 +#define GMOCK_INTERNAL_DECL_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3) kind0 name0, kind1 name1, kind2 name2, \ + kind3 name3 +#define GMOCK_INTERNAL_DECL_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4) kind0 name0, kind1 name1, \ + kind2 name2, kind3 name3, kind4 name4 +#define GMOCK_INTERNAL_DECL_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5) kind0 name0, \ + kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5 +#define GMOCK_INTERNAL_DECL_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6) kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, \ + kind5 name5, kind6 name6 +#define GMOCK_INTERNAL_DECL_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7) kind0 name0, kind1 name1, kind2 name2, kind3 name3, \ + kind4 name4, kind5 name5, kind6 name6, kind7 name7 +#define GMOCK_INTERNAL_DECL_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7, kind8, name8) kind0 name0, kind1 name1, kind2 name2, \ + kind3 name3, kind4 name4, kind5 name5, kind6 name6, kind7 name7, \ + kind8 name8 +#define GMOCK_INTERNAL_DECL_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6, kind7, name7, kind8, name8, kind9, name9) kind0 name0, \ + kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5, \ + kind6 name6, kind7 name7, kind8 name8, kind9 name9 + +// Lists the template parameters. +#define GMOCK_INTERNAL_LIST_HAS_1_TEMPLATE_PARAMS(kind0, name0) name0 +#define GMOCK_INTERNAL_LIST_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1) name0, name1 +#define GMOCK_INTERNAL_LIST_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2) name0, name1, name2 +#define GMOCK_INTERNAL_LIST_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3) name0, name1, name2, name3 +#define GMOCK_INTERNAL_LIST_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4) name0, name1, name2, name3, \ + name4 +#define GMOCK_INTERNAL_LIST_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5) name0, name1, \ + name2, name3, name4, name5 +#define GMOCK_INTERNAL_LIST_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6) name0, name1, name2, name3, name4, name5, name6 +#define GMOCK_INTERNAL_LIST_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7) name0, name1, name2, name3, name4, name5, name6, name7 +#define GMOCK_INTERNAL_LIST_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7, kind8, name8) name0, name1, name2, name3, name4, name5, \ + name6, name7, name8 +#define GMOCK_INTERNAL_LIST_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6, kind7, name7, kind8, name8, kind9, name9) name0, name1, name2, \ + name3, name4, name5, name6, name7, name8, name9 + +// Declares the types of value parameters. +#define GMOCK_INTERNAL_DECL_TYPE_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DECL_TYPE_AND_1_VALUE_PARAMS(p0) , typename p0##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_2_VALUE_PARAMS(p0, p1) , \ + typename p0##_type, typename p1##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , \ + typename p0##_type, typename p1##_type, typename p2##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type, typename p7##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type, typename p7##_type, typename p8##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9) , typename p0##_type, typename p1##_type, \ + typename p2##_type, typename p3##_type, typename p4##_type, \ + typename p5##_type, typename p6##_type, typename p7##_type, \ + typename p8##_type, typename p9##_type + +// Initializes the value parameters. +#define GMOCK_INTERNAL_INIT_AND_0_VALUE_PARAMS()\ + () +#define GMOCK_INTERNAL_INIT_AND_1_VALUE_PARAMS(p0)\ + (p0##_type gmock_p0) : p0(gmock_p0) +#define GMOCK_INTERNAL_INIT_AND_2_VALUE_PARAMS(p0, p1)\ + (p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), p1(gmock_p1) +#define GMOCK_INTERNAL_INIT_AND_3_VALUE_PARAMS(p0, p1, p2)\ + (p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) +#define GMOCK_INTERNAL_INIT_AND_4_VALUE_PARAMS(p0, p1, p2, p3)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3) +#define GMOCK_INTERNAL_INIT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4) +#define GMOCK_INTERNAL_INIT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) +#define GMOCK_INTERNAL_INIT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6) +#define GMOCK_INTERNAL_INIT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7) +#define GMOCK_INTERNAL_INIT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) +#define GMOCK_INTERNAL_INIT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8), p9(gmock_p9) + +// Declares the fields for storing the value parameters. +#define GMOCK_INTERNAL_DEFN_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DEFN_AND_1_VALUE_PARAMS(p0) p0##_type p0; +#define GMOCK_INTERNAL_DEFN_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0; \ + p1##_type p1; +#define GMOCK_INTERNAL_DEFN_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0; \ + p1##_type p1; p2##_type p2; +#define GMOCK_INTERNAL_DEFN_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0; \ + p1##_type p1; p2##_type p2; p3##_type p3; +#define GMOCK_INTERNAL_DEFN_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \ + p4) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; +#define GMOCK_INTERNAL_DEFN_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \ + p5) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; +#define GMOCK_INTERNAL_DEFN_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; p6##_type p6; +#define GMOCK_INTERNAL_DEFN_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; p6##_type p6; p7##_type p7; +#define GMOCK_INTERNAL_DEFN_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \ + p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8; +#define GMOCK_INTERNAL_DEFN_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \ + p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8; \ + p9##_type p9; + +// Lists the value parameters. +#define GMOCK_INTERNAL_LIST_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_LIST_AND_1_VALUE_PARAMS(p0) p0 +#define GMOCK_INTERNAL_LIST_AND_2_VALUE_PARAMS(p0, p1) p0, p1 +#define GMOCK_INTERNAL_LIST_AND_3_VALUE_PARAMS(p0, p1, p2) p0, p1, p2 +#define GMOCK_INTERNAL_LIST_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0, p1, p2, p3 +#define GMOCK_INTERNAL_LIST_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) p0, p1, \ + p2, p3, p4 +#define GMOCK_INTERNAL_LIST_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) p0, \ + p1, p2, p3, p4, p5 +#define GMOCK_INTERNAL_LIST_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0, p1, p2, p3, p4, p5, p6 +#define GMOCK_INTERNAL_LIST_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0, p1, p2, p3, p4, p5, p6, p7 +#define GMOCK_INTERNAL_LIST_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0, p1, p2, p3, p4, p5, p6, p7, p8 +#define GMOCK_INTERNAL_LIST_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 + +// Lists the value parameter types. +#define GMOCK_INTERNAL_LIST_TYPE_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_LIST_TYPE_AND_1_VALUE_PARAMS(p0) , p0##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_2_VALUE_PARAMS(p0, p1) , p0##_type, \ + p1##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , p0##_type, \ + p1##_type, p2##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \ + p0##_type, p1##_type, p2##_type, p3##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \ + p0##_type, p1##_type, p2##_type, p3##_type, p4##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \ + p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type, \ + p6##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type, p8##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type, p8##_type, p9##_type + +// Declares the value parameters. +#define GMOCK_INTERNAL_DECL_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DECL_AND_1_VALUE_PARAMS(p0) p0##_type p0 +#define GMOCK_INTERNAL_DECL_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0, \ + p1##_type p1 +#define GMOCK_INTERNAL_DECL_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0, \ + p1##_type p1, p2##_type p2 +#define GMOCK_INTERNAL_DECL_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3 +#define GMOCK_INTERNAL_DECL_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \ + p4) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4 +#define GMOCK_INTERNAL_DECL_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \ + p5) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5 +#define GMOCK_INTERNAL_DECL_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5, p6##_type p6 +#define GMOCK_INTERNAL_DECL_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5, p6##_type p6, p7##_type p7 +#define GMOCK_INTERNAL_DECL_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8 +#define GMOCK_INTERNAL_DECL_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9 + +// The suffix of the class template implementing the action template. +#define GMOCK_INTERNAL_COUNT_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_COUNT_AND_1_VALUE_PARAMS(p0) P +#define GMOCK_INTERNAL_COUNT_AND_2_VALUE_PARAMS(p0, p1) P2 +#define GMOCK_INTERNAL_COUNT_AND_3_VALUE_PARAMS(p0, p1, p2) P3 +#define GMOCK_INTERNAL_COUNT_AND_4_VALUE_PARAMS(p0, p1, p2, p3) P4 +#define GMOCK_INTERNAL_COUNT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) P5 +#define GMOCK_INTERNAL_COUNT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) P6 +#define GMOCK_INTERNAL_COUNT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) P7 +#define GMOCK_INTERNAL_COUNT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) P8 +#define GMOCK_INTERNAL_COUNT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) P9 +#define GMOCK_INTERNAL_COUNT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) P10 + +// The name of the class template implementing the action template. +#define GMOCK_ACTION_CLASS_(name, value_params)\ + GTEST_CONCAT_TOKEN_(name##Action, GMOCK_INTERNAL_COUNT_##value_params) + +#define ACTION_TEMPLATE(name, template_params, value_params)\ + template \ + class GMOCK_ACTION_CLASS_(name, value_params) {\ + public:\ + explicit GMOCK_ACTION_CLASS_(name, value_params)\ + GMOCK_INTERNAL_INIT_##value_params {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + explicit gmock_Impl GMOCK_INTERNAL_INIT_##value_params {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(\ + new gmock_Impl(GMOCK_INTERNAL_LIST_##value_params));\ + }\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(GMOCK_ACTION_CLASS_(name, value_params));\ + };\ + template \ + inline GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params> name(\ + GMOCK_INTERNAL_DECL_##value_params) {\ + return GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>(\ + GMOCK_INTERNAL_LIST_##value_params);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>::gmock_Impl::\ + gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION(name)\ + class name##Action {\ + public:\ + name##Action() {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl() {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl());\ + }\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##Action);\ + };\ + inline name##Action name() {\ + return name##Action();\ + }\ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##Action::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P(name, p0)\ + template \ + class name##ActionP {\ + public:\ + explicit name##ActionP(p0##_type gmock_p0) : p0(gmock_p0) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + explicit gmock_Impl(p0##_type gmock_p0) : p0(gmock_p0) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0));\ + }\ + p0##_type p0;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP);\ + };\ + template \ + inline name##ActionP name(p0##_type p0) {\ + return name##ActionP(p0);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P2(name, p0, p1)\ + template \ + class name##ActionP2 {\ + public:\ + name##ActionP2(p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), \ + p1(gmock_p1) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), \ + p1(gmock_p1) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP2);\ + };\ + template \ + inline name##ActionP2 name(p0##_type p0, \ + p1##_type p1) {\ + return name##ActionP2(p0, p1);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP2::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P3(name, p0, p1, p2)\ + template \ + class name##ActionP3 {\ + public:\ + name##ActionP3(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP3);\ + };\ + template \ + inline name##ActionP3 name(p0##_type p0, \ + p1##_type p1, p2##_type p2) {\ + return name##ActionP3(p0, p1, p2);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP3::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P4(name, p0, p1, p2, p3)\ + template \ + class name##ActionP4 {\ + public:\ + name##ActionP4(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP4);\ + };\ + template \ + inline name##ActionP4 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3) {\ + return name##ActionP4(p0, p1, \ + p2, p3);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP4::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P5(name, p0, p1, p2, p3, p4)\ + template \ + class name##ActionP5 {\ + public:\ + name##ActionP5(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, \ + p4##_type gmock_p4) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4) : p0(gmock_p0), \ + p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), p4(gmock_p4) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP5);\ + };\ + template \ + inline name##ActionP5 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4) {\ + return name##ActionP5(p0, p1, p2, p3, p4);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP5::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P6(name, p0, p1, p2, p3, p4, p5)\ + template \ + class name##ActionP6 {\ + public:\ + name##ActionP6(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP6);\ + };\ + template \ + inline name##ActionP6 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3, p4##_type p4, p5##_type p5) {\ + return name##ActionP6(p0, p1, p2, p3, p4, p5);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP6::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P7(name, p0, p1, p2, p3, p4, p5, p6)\ + template \ + class name##ActionP7 {\ + public:\ + name##ActionP7(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), \ + p6(gmock_p6) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP7);\ + };\ + template \ + inline name##ActionP7 name(p0##_type p0, p1##_type p1, \ + p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6) {\ + return name##ActionP7(p0, p1, p2, p3, p4, p5, p6);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP7::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P8(name, p0, p1, p2, p3, p4, p5, p6, p7)\ + template \ + class name##ActionP8 {\ + public:\ + name##ActionP8(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, \ + p7##_type gmock_p7) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7) : p0(gmock_p0), \ + p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), \ + p5(gmock_p5), p6(gmock_p6), p7(gmock_p7) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6, p7));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP8);\ + };\ + template \ + inline name##ActionP8 name(p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6, p7##_type p7) {\ + return name##ActionP8(p0, p1, p2, p3, p4, p5, \ + p6, p7);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP8::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P9(name, p0, p1, p2, p3, p4, p5, p6, p7, p8)\ + template \ + class name##ActionP9 {\ + public:\ + name##ActionP9(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP9);\ + };\ + template \ + inline name##ActionP9 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, \ + p8##_type p8) {\ + return name##ActionP9(p0, p1, p2, \ + p3, p4, p5, p6, p7, p8);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP9::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P10(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)\ + template \ + class name##ActionP10 {\ + public:\ + name##ActionP10(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8, p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8), p9(gmock_p9) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8), p9(gmock_p9) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP10);\ + };\ + template \ + inline name##ActionP10 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9) {\ + return name##ActionP10(p0, \ + p1, p2, p3, p4, p5, p6, p7, p8, p9);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP10::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +namespace testing { + + +// The ACTION*() macros trigger warning C4100 (unreferenced formal +// parameter) in MSVC with -W4. Unfortunately they cannot be fixed in +// the macro definition, as the warnings are generated when the macro +// is expanded and macro expansion cannot contain #pragma. Therefore +// we suppress them here. +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4100) +#endif + +// Various overloads for InvokeArgument(). +// +// The InvokeArgument(a1, a2, ..., a_k) action invokes the N-th +// (0-based) argument, which must be a k-ary callable, of the mock +// function, with arguments a1, a2, ..., a_k. +// +// Notes: +// +// 1. The arguments are passed by value by default. If you need to +// pass an argument by reference, wrap it inside ByRef(). For +// example, +// +// InvokeArgument<1>(5, string("Hello"), ByRef(foo)) +// +// passes 5 and string("Hello") by value, and passes foo by +// reference. +// +// 2. If the callable takes an argument by reference but ByRef() is +// not used, it will receive the reference to a copy of the value, +// instead of the original value. For example, when the 0-th +// argument of the mock function takes a const string&, the action +// +// InvokeArgument<0>(string("Hello")) +// +// makes a copy of the temporary string("Hello") object and passes a +// reference of the copy, instead of the original temporary object, +// to the callable. This makes it easy for a user to define an +// InvokeArgument action from temporary values and have it performed +// later. + +namespace internal { +namespace invoke_argument { + +// Appears in InvokeArgumentAdl's argument list to help avoid +// accidental calls to user functions of the same name. +struct AdlTag {}; + +// InvokeArgumentAdl - a helper for InvokeArgument. +// The basic overloads are provided here for generic functors. +// Overloads for other custom-callables are provided in the +// internal/custom/callback-actions.h header. + +template +R InvokeArgumentAdl(AdlTag, F f) { + return f(); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1) { + return f(a1); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1, A2 a2) { + return f(a1, a2); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1, A2 a2, A3 a3) { + return f(a1, a2, a3); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1, A2 a2, A3 a3, A4 a4) { + return f(a1, a2, a3, a4); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { + return f(a1, a2, a3, a4, a5); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { + return f(a1, a2, a3, a4, a5, a6); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7) { + return f(a1, a2, a3, a4, a5, a6, a7); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7, A8 a8) { + return f(a1, a2, a3, a4, a5, a6, a7, a8); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7, A8 a8, A9 a9) { + return f(a1, a2, a3, a4, a5, a6, a7, a8, a9); +} +template +R InvokeArgumentAdl(AdlTag, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7, A8 a8, A9 a9, A10 a10) { + return f(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); +} +} // namespace invoke_argument +} // namespace internal + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_0_VALUE_PARAMS()) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args)); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(p0)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_2_VALUE_PARAMS(p0, p1)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0, p1); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_3_VALUE_PARAMS(p0, p1, p2)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0, p1, p2); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_4_VALUE_PARAMS(p0, p1, p2, p3)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0, p1, p2, p3); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0, p1, p2, p3, p4); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0, p1, p2, p3, p4, p5); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0, p1, p2, p3, p4, p5, p6); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0, p1, p2, p3, p4, p5, p6, p7); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0, p1, p2, p3, p4, p5, p6, p7, p8); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args), p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); +} + +// Various overloads for ReturnNew(). +// +// The ReturnNew(a1, a2, ..., a_k) action returns a pointer to a new +// instance of type T, constructed on the heap with constructor arguments +// a1, a2, ..., and a_k. The caller assumes ownership of the returned value. +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_0_VALUE_PARAMS()) { + return new T(); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_1_VALUE_PARAMS(p0)) { + return new T(p0); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_2_VALUE_PARAMS(p0, p1)) { + return new T(p0, p1); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_3_VALUE_PARAMS(p0, p1, p2)) { + return new T(p0, p1, p2); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_4_VALUE_PARAMS(p0, p1, p2, p3)) { + return new T(p0, p1, p2, p3); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) { + return new T(p0, p1, p2, p3, p4); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) { + return new T(p0, p1, p2, p3, p4, p5); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) { + return new T(p0, p1, p2, p3, p4, p5, p6); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)) { + return new T(p0, p1, p2, p3, p4, p5, p6, p7); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8)) { + return new T(p0, p1, p2, p3, p4, p5, p6, p7, p8); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { + return new T(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +} // namespace testing + +// Include any custom actions added by the local installation. +// We must include this header at the end to make sure it can use the +// declarations from this file. +#include "gmock/internal/custom/gmock-generated-actions.h" + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-actions.h.pump b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-actions.h.pump new file mode 100644 index 0000000000..66d9f9d551 --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-actions.h.pump @@ -0,0 +1,794 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-actions.h. +$$ +$var n = 10 $$ The maximum arity we support. +$$}} This meta comment fixes auto-indentation in editors. +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic actions. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ + +#include "gmock/gmock-actions.h" +#include "gmock/internal/gmock-port.h" + +namespace testing { +namespace internal { + +// InvokeHelper knows how to unpack an N-tuple and invoke an N-ary +// function or method with the unpacked values, where F is a function +// type that takes N arguments. +template +class InvokeHelper; + + +$range i 0..n +$for i [[ +$range j 1..i +$var types = [[$for j [[, typename A$j]]]] +$var as = [[$for j, [[A$j]]]] +$var args = [[$if i==0 [[]] $else [[ args]]]] +$var gets = [[$for j, [[get<$(j - 1)>(args)]]]] +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::testing::tuple<$as>&$args) { + return function($gets); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::testing::tuple<$as>&$args) { + return (obj_ptr->*method_ptr)($gets); + } +}; + + +]] +// An INTERNAL macro for extracting the type of a tuple field. It's +// subject to change without notice - DO NOT USE IN USER CODE! +#define GMOCK_FIELD_(Tuple, N) \ + typename ::testing::tuple_element::type + +$range i 1..n + +// SelectArgs::type is the +// type of an n-ary function whose i-th (1-based) argument type is the +// k{i}-th (0-based) field of ArgumentTuple, which must be a tuple +// type, and whose return type is Result. For example, +// SelectArgs, 0, 3>::type +// is int(bool, long). +// +// SelectArgs::Select(args) +// returns the selected fields (k1, k2, ..., k_n) of args as a tuple. +// For example, +// SelectArgs, 2, 0>::Select( +// ::testing::make_tuple(true, 'a', 2.5)) +// returns tuple (2.5, true). +// +// The numbers in list k1, k2, ..., k_n must be >= 0, where n can be +// in the range [0, $n]. Duplicates are allowed and they don't have +// to be in an ascending or descending order. + +template +class SelectArgs { + public: + typedef Result type($for i, [[GMOCK_FIELD_(ArgumentTuple, k$i)]]); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + return SelectedArgs($for i, [[get(args)]]); + } +}; + + +$for i [[ +$range j 1..n +$range j1 1..i-1 +template +class SelectArgs { + public: + typedef Result type($for j1, [[GMOCK_FIELD_(ArgumentTuple, k$j1)]]); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& [[]] +$if i == 1 [[/* args */]] $else [[args]]) { + return SelectedArgs($for j1, [[get(args)]]); + } +}; + + +]] +#undef GMOCK_FIELD_ + +$var ks = [[$for i, [[k$i]]]] + +// Implements the WithArgs action. +template +class WithArgsAction { + public: + explicit WithArgsAction(const InnerAction& action) : action_(action) {} + + template + operator Action() const { return MakeAction(new Impl(action_)); } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const InnerAction& action) : action_(action) {} + + virtual Result Perform(const ArgumentTuple& args) { + return action_.Perform(SelectArgs::Select(args)); + } + + private: + typedef typename SelectArgs::type InnerFunctionType; + + Action action_; + }; + + const InnerAction action_; + + GTEST_DISALLOW_ASSIGN_(WithArgsAction); +}; + +// A macro from the ACTION* family (defined later in this file) +// defines an action that can be used in a mock function. Typically, +// these actions only care about a subset of the arguments of the mock +// function. For example, if such an action only uses the second +// argument, it can be used in any mock function that takes >= 2 +// arguments where the type of the second argument is compatible. +// +// Therefore, the action implementation must be prepared to take more +// arguments than it needs. The ExcessiveArg type is used to +// represent those excessive arguments. In order to keep the compiler +// error messages tractable, we define it in the testing namespace +// instead of testing::internal. However, this is an INTERNAL TYPE +// and subject to change without notice, so a user MUST NOT USE THIS +// TYPE DIRECTLY. +struct ExcessiveArg {}; + +// A helper class needed for implementing the ACTION* macros. +template +class ActionHelper { + public: +$range i 0..n +$for i + +[[ +$var template = [[$if i==0 [[]] $else [[ +$range j 0..i-1 + template <$for j, [[typename A$j]]> +]]]] +$range j 0..i-1 +$var As = [[$for j, [[A$j]]]] +$var as = [[$for j, [[get<$j>(args)]]]] +$range k 1..n-i +$var eas = [[$for k, [[ExcessiveArg()]]]] +$var arg_list = [[$if (i==0) | (i==n) [[$as$eas]] $else [[$as, $eas]]]] +$template + static Result Perform(Impl* impl, const ::testing::tuple<$As>& args) { + return impl->template gmock_PerformImpl<$As>(args, $arg_list); + } + +]] +}; + +} // namespace internal + +// Various overloads for Invoke(). + +// WithArgs(an_action) creates an action that passes +// the selected arguments of the mock function to an_action and +// performs it. It serves as an adaptor between actions with +// different argument lists. C++ doesn't support default arguments for +// function templates, so we have to overload it. + +$range i 1..n +$for i [[ +$range j 1..i +template <$for j [[int k$j, ]]typename InnerAction> +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + + +]] +// Creates an action that does actions a1, a2, ..., sequentially in +// each invocation. +$range i 2..n +$for i [[ +$range j 2..i +$var types = [[$for j, [[typename Action$j]]]] +$var Aas = [[$for j [[, Action$j a$j]]]] + +template +$range k 1..i-1 + +inline $for k [[internal::DoBothAction]] + +DoAll(Action1 a1$Aas) { +$if i==2 [[ + + return internal::DoBothAction(a1, a2); +]] $else [[ +$range j2 2..i + + return DoAll(a1, DoAll($for j2, [[a$j2]])); +]] + +} + +]] + +} // namespace testing + +// The ACTION* family of macros can be used in a namespace scope to +// define custom actions easily. The syntax: +// +// ACTION(name) { statements; } +// +// will define an action with the given name that executes the +// statements. The value returned by the statements will be used as +// the return value of the action. Inside the statements, you can +// refer to the K-th (0-based) argument of the mock function by +// 'argK', and refer to its type by 'argK_type'. For example: +// +// ACTION(IncrementArg1) { +// arg1_type temp = arg1; +// return ++(*temp); +// } +// +// allows you to write +// +// ...WillOnce(IncrementArg1()); +// +// You can also refer to the entire argument tuple and its type by +// 'args' and 'args_type', and refer to the mock function type and its +// return type by 'function_type' and 'return_type'. +// +// Note that you don't need to specify the types of the mock function +// arguments. However rest assured that your code is still type-safe: +// you'll get a compiler error if *arg1 doesn't support the ++ +// operator, or if the type of ++(*arg1) isn't compatible with the +// mock function's return type, for example. +// +// Sometimes you'll want to parameterize the action. For that you can use +// another macro: +// +// ACTION_P(name, param_name) { statements; } +// +// For example: +// +// ACTION_P(Add, n) { return arg0 + n; } +// +// will allow you to write: +// +// ...WillOnce(Add(5)); +// +// Note that you don't need to provide the type of the parameter +// either. If you need to reference the type of a parameter named +// 'foo', you can write 'foo_type'. For example, in the body of +// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type +// of 'n'. +// +// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P$n to support +// multi-parameter actions. +// +// For the purpose of typing, you can view +// +// ACTION_Pk(Foo, p1, ..., pk) { ... } +// +// as shorthand for +// +// template +// FooActionPk Foo(p1_type p1, ..., pk_type pk) { ... } +// +// In particular, you can provide the template type arguments +// explicitly when invoking Foo(), as in Foo(5, false); +// although usually you can rely on the compiler to infer the types +// for you automatically. You can assign the result of expression +// Foo(p1, ..., pk) to a variable of type FooActionPk. This can be useful when composing actions. +// +// You can also overload actions with different numbers of parameters: +// +// ACTION_P(Plus, a) { ... } +// ACTION_P2(Plus, a, b) { ... } +// +// While it's tempting to always use the ACTION* macros when defining +// a new action, you should also consider implementing ActionInterface +// or using MakePolymorphicAction() instead, especially if you need to +// use the action a lot. While these approaches require more work, +// they give you more control on the types of the mock function +// arguments and the action parameters, which in general leads to +// better compiler error messages that pay off in the long run. They +// also allow overloading actions based on parameter types (as opposed +// to just based on the number of parameters). +// +// CAVEAT: +// +// ACTION*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using ACTION*() inside +// a function. +// +// MORE INFORMATION: +// +// To learn more about using these macros, please search for 'ACTION' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +$range i 0..n +$range k 0..n-1 + +// An internal macro needed for implementing ACTION*(). +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_\ + const args_type& args GTEST_ATTRIBUTE_UNUSED_ +$for k [[, \ + arg$k[[]]_type arg$k GTEST_ATTRIBUTE_UNUSED_]] + + +// Sometimes you want to give an action explicit template parameters +// that cannot be inferred from its value parameters. ACTION() and +// ACTION_P*() don't support that. ACTION_TEMPLATE() remedies that +// and can be viewed as an extension to ACTION() and ACTION_P*(). +// +// The syntax: +// +// ACTION_TEMPLATE(ActionName, +// HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m), +// AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; } +// +// defines an action template that takes m explicit template +// parameters and n value parameters. name_i is the name of the i-th +// template parameter, and kind_i specifies whether it's a typename, +// an integral constant, or a template. p_i is the name of the i-th +// value parameter. +// +// Example: +// +// // DuplicateArg(output) converts the k-th argument of the mock +// // function to type T and copies it to *output. +// ACTION_TEMPLATE(DuplicateArg, +// HAS_2_TEMPLATE_PARAMS(int, k, typename, T), +// AND_1_VALUE_PARAMS(output)) { +// *output = T(::testing::get(args)); +// } +// ... +// int n; +// EXPECT_CALL(mock, Foo(_, _)) +// .WillOnce(DuplicateArg<1, unsigned char>(&n)); +// +// To create an instance of an action template, write: +// +// ActionName(v1, ..., v_n) +// +// where the ts are the template arguments and the vs are the value +// arguments. The value argument types are inferred by the compiler. +// If you want to explicitly specify the value argument types, you can +// provide additional template arguments: +// +// ActionName(v1, ..., v_n) +// +// where u_i is the desired type of v_i. +// +// ACTION_TEMPLATE and ACTION/ACTION_P* can be overloaded on the +// number of value parameters, but not on the number of template +// parameters. Without the restriction, the meaning of the following +// is unclear: +// +// OverloadedAction(x); +// +// Are we using a single-template-parameter action where 'bool' refers +// to the type of x, or are we using a two-template-parameter action +// where the compiler is asked to infer the type of x? +// +// Implementation notes: +// +// GMOCK_INTERNAL_*_HAS_m_TEMPLATE_PARAMS and +// GMOCK_INTERNAL_*_AND_n_VALUE_PARAMS are internal macros for +// implementing ACTION_TEMPLATE. The main trick we use is to create +// new macro invocations when expanding a macro. For example, we have +// +// #define ACTION_TEMPLATE(name, template_params, value_params) +// ... GMOCK_INTERNAL_DECL_##template_params ... +// +// which causes ACTION_TEMPLATE(..., HAS_1_TEMPLATE_PARAMS(typename, T), ...) +// to expand to +// +// ... GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(typename, T) ... +// +// Since GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS is a macro, the +// preprocessor will continue to expand it to +// +// ... typename T ... +// +// This technique conforms to the C++ standard and is portable. It +// allows us to implement action templates using O(N) code, where N is +// the maximum number of template/value parameters supported. Without +// using it, we'd have to devote O(N^2) amount of code to implement all +// combinations of m and n. + +// Declares the template parameters. + +$range j 1..n +$for j [[ +$range m 0..j-1 +#define GMOCK_INTERNAL_DECL_HAS_$j[[]] +_TEMPLATE_PARAMS($for m, [[kind$m, name$m]]) $for m, [[kind$m name$m]] + + +]] + +// Lists the template parameters. + +$for j [[ +$range m 0..j-1 +#define GMOCK_INTERNAL_LIST_HAS_$j[[]] +_TEMPLATE_PARAMS($for m, [[kind$m, name$m]]) $for m, [[name$m]] + + +]] + +// Declares the types of value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_DECL_TYPE_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j [[, typename p$j##_type]] + + +]] + +// Initializes the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_INIT_AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]])\ + ($for j, [[p$j##_type gmock_p$j]])$if i>0 [[ : ]]$for j, [[p$j(gmock_p$j)]] + + +]] + +// Declares the fields for storing the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_DEFN_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j [[p$j##_type p$j; ]] + + +]] + +// Lists the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_LIST_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j, [[p$j]] + + +]] + +// Lists the value parameter types. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_LIST_TYPE_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j [[, p$j##_type]] + + +]] + +// Declares the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_DECL_AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]]) [[]] +$for j, [[p$j##_type p$j]] + + +]] + +// The suffix of the class template implementing the action template. +$for i [[ + + +$range j 0..i-1 +#define GMOCK_INTERNAL_COUNT_AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]]) [[]] +$if i==1 [[P]] $elif i>=2 [[P$i]] +]] + + +// The name of the class template implementing the action template. +#define GMOCK_ACTION_CLASS_(name, value_params)\ + GTEST_CONCAT_TOKEN_(name##Action, GMOCK_INTERNAL_COUNT_##value_params) + +$range k 0..n-1 + +#define ACTION_TEMPLATE(name, template_params, value_params)\ + template \ + class GMOCK_ACTION_CLASS_(name, value_params) {\ + public:\ + explicit GMOCK_ACTION_CLASS_(name, value_params)\ + GMOCK_INTERNAL_INIT_##value_params {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + explicit gmock_Impl GMOCK_INTERNAL_INIT_##value_params {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template <$for k, [[typename arg$k[[]]_type]]>\ + return_type gmock_PerformImpl(const args_type& args[[]] +$for k [[, arg$k[[]]_type arg$k]]) const;\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(\ + new gmock_Impl(GMOCK_INTERNAL_LIST_##value_params));\ + }\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(GMOCK_ACTION_CLASS_(name, value_params));\ + };\ + template \ + inline GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params> name(\ + GMOCK_INTERNAL_DECL_##value_params) {\ + return GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>(\ + GMOCK_INTERNAL_LIST_##value_params);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>::gmock_Impl::\ + gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +$for i + +[[ +$var template = [[$if i==0 [[]] $else [[ +$range j 0..i-1 + + template <$for j, [[typename p$j##_type]]>\ +]]]] +$var class_name = [[name##Action[[$if i==0 [[]] $elif i==1 [[P]] + $else [[P$i]]]]]] +$range j 0..i-1 +$var ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] +$var param_types_and_names = [[$for j, [[p$j##_type p$j]]]] +$var inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(gmock_p$j)]]]]]] +$var param_field_decls = [[$for j +[[ + + p$j##_type p$j;\ +]]]] +$var param_field_decls2 = [[$for j +[[ + + p$j##_type p$j;\ +]]]] +$var params = [[$for j, [[p$j]]]] +$var param_types = [[$if i==0 [[]] $else [[<$for j, [[p$j##_type]]>]]]] +$var typename_arg_types = [[$for k, [[typename arg$k[[]]_type]]]] +$var arg_types_and_names = [[$for k, [[arg$k[[]]_type arg$k]]]] +$var macro_name = [[$if i==0 [[ACTION]] $elif i==1 [[ACTION_P]] + $else [[ACTION_P$i]]]] + +#define $macro_name(name$for j [[, p$j]])\$template + class $class_name {\ + public:\ + [[$if i==1 [[explicit ]]]]$class_name($ctor_param_list)$inits {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + [[$if i==1 [[explicit ]]]]gmock_Impl($ctor_param_list)$inits {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template <$typename_arg_types>\ + return_type gmock_PerformImpl(const args_type& args, [[]] +$arg_types_and_names) const;\$param_field_decls + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl($params));\ + }\$param_field_decls2 + private:\ + GTEST_DISALLOW_ASSIGN_($class_name);\ + };\$template + inline $class_name$param_types name($param_types_and_names) {\ + return $class_name$param_types($params);\ + }\$template + template \ + template <$typename_arg_types>\ + typename ::testing::internal::Function::Result\ + $class_name$param_types::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const +]] +$$ } // This meta comment fixes auto-indentation in Emacs. It won't +$$ // show up in the generated code. + + +namespace testing { + + +// The ACTION*() macros trigger warning C4100 (unreferenced formal +// parameter) in MSVC with -W4. Unfortunately they cannot be fixed in +// the macro definition, as the warnings are generated when the macro +// is expanded and macro expansion cannot contain #pragma. Therefore +// we suppress them here. +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4100) +#endif + +// Various overloads for InvokeArgument(). +// +// The InvokeArgument(a1, a2, ..., a_k) action invokes the N-th +// (0-based) argument, which must be a k-ary callable, of the mock +// function, with arguments a1, a2, ..., a_k. +// +// Notes: +// +// 1. The arguments are passed by value by default. If you need to +// pass an argument by reference, wrap it inside ByRef(). For +// example, +// +// InvokeArgument<1>(5, string("Hello"), ByRef(foo)) +// +// passes 5 and string("Hello") by value, and passes foo by +// reference. +// +// 2. If the callable takes an argument by reference but ByRef() is +// not used, it will receive the reference to a copy of the value, +// instead of the original value. For example, when the 0-th +// argument of the mock function takes a const string&, the action +// +// InvokeArgument<0>(string("Hello")) +// +// makes a copy of the temporary string("Hello") object and passes a +// reference of the copy, instead of the original temporary object, +// to the callable. This makes it easy for a user to define an +// InvokeArgument action from temporary values and have it performed +// later. + +namespace internal { +namespace invoke_argument { + +// Appears in InvokeArgumentAdl's argument list to help avoid +// accidental calls to user functions of the same name. +struct AdlTag {}; + +// InvokeArgumentAdl - a helper for InvokeArgument. +// The basic overloads are provided here for generic functors. +// Overloads for other custom-callables are provided in the +// internal/custom/callback-actions.h header. + +$range i 0..n +$for i +[[ +$range j 1..i + +template +R InvokeArgumentAdl(AdlTag, F f[[$for j [[, A$j a$j]]]]) { + return f([[$for j, [[a$j]]]]); +} +]] + +} // namespace invoke_argument +} // namespace internal + +$range i 0..n +$for i [[ +$range j 0..i-1 + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]])) { + using internal::invoke_argument::InvokeArgumentAdl; + return InvokeArgumentAdl( + internal::invoke_argument::AdlTag(), + ::testing::get(args)$for j [[, p$j]]); +} + +]] + +// Various overloads for ReturnNew(). +// +// The ReturnNew(a1, a2, ..., a_k) action returns a pointer to a new +// instance of type T, constructed on the heap with constructor arguments +// a1, a2, ..., and a_k. The caller assumes ownership of the returned value. +$range i 0..n +$for i [[ +$range j 0..i-1 +$var ps = [[$for j, [[p$j]]]] + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_$i[[]]_VALUE_PARAMS($ps)) { + return new T($ps); +} + +]] + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +} // namespace testing + +// Include any custom callback actions added by the local installation. +// We must include this header at the end to make sure it can use the +// declarations from this file. +#include "gmock/internal/custom/gmock-generated-actions.h" + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-function-mockers.h b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-function-mockers.h new file mode 100644 index 0000000000..4fa5ca9484 --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-function-mockers.h @@ -0,0 +1,1095 @@ +// This file was GENERATED by command: +// pump.py gmock-generated-function-mockers.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements function mockers of various arities. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-internal-utils.h" + +#if GTEST_HAS_STD_FUNCTION_ +# include +#endif + +namespace testing { +namespace internal { + +template +class FunctionMockerBase; + +// Note: class FunctionMocker really belongs to the ::testing +// namespace. However if we define it in ::testing, MSVC will +// complain when classes in ::testing::internal declare it as a +// friend class template. To workaround this compiler bug, we define +// FunctionMocker in ::testing::internal and import it into ::testing. +template +class FunctionMocker; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With() { + return this->current_spec(); + } + + R Invoke() { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple()); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1) { + this->current_spec().SetMatchers(::testing::make_tuple(m1)); + return this->current_spec(); + } + + R Invoke(A1 a1) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2) { + this->current_spec().SetMatchers(::testing::make_tuple(m1, m2)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3) { + this->current_spec().SetMatchers(::testing::make_tuple(m1, m2, m3)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4) { + this->current_spec().SetMatchers(::testing::make_tuple(m1, m2, m3, m4)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5) { + this->current_spec().SetMatchers(::testing::make_tuple(m1, m2, m3, m4, m5)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6) { + this->current_spec().SetMatchers(::testing::make_tuple(m1, m2, m3, m4, m5, + m6)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7) { + this->current_spec().SetMatchers(::testing::make_tuple(m1, m2, m3, m4, m5, + m6, m7)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7, A8); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7, const Matcher& m8) { + this->current_spec().SetMatchers(::testing::make_tuple(m1, m2, m3, m4, m5, + m6, m7, m8)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7, a8)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7, A8, A9); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7, const Matcher& m8, + const Matcher& m9) { + this->current_spec().SetMatchers(::testing::make_tuple(m1, m2, m3, m4, m5, + m6, m7, m8, m9)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7, a8, a9)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7, const Matcher& m8, + const Matcher& m9, const Matcher& m10) { + this->current_spec().SetMatchers(::testing::make_tuple(m1, m2, m3, m4, m5, + m6, m7, m8, m9, m10)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, + A10 a10) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10)); + } +}; + +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the FunctionMocker class template +// is meant to be defined in the ::testing namespace. The following +// line is just a trick for working around a bug in MSVC 8.0, which +// cannot handle it if we define FunctionMocker in ::testing. +using internal::FunctionMocker; + +// GMOCK_RESULT_(tn, F) expands to the result type of function type F. +// We define this as a variadic macro in case F contains unprotected +// commas (the same reason that we use variadic macros in other places +// in this file). +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_RESULT_(tn, ...) \ + tn ::testing::internal::Function<__VA_ARGS__>::Result + +// The type of argument N of the given function type. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_ARG_(tn, N, ...) \ + tn ::testing::internal::Function<__VA_ARGS__>::Argument##N + +// The matcher type for argument N of the given function type. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MATCHER_(tn, N, ...) \ + const ::testing::Matcher& + +// The variable for mocking the given method. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MOCKER_(arity, constness, Method) \ + GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD0_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + ) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 0), \ + this_method_does_not_take_0_arguments); \ + GMOCK_MOCKER_(0, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(0, constness, Method).Invoke(); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method() constness { \ + GMOCK_MOCKER_(0, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(0, constness, Method).With(); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(0, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD1_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 1), \ + this_method_does_not_take_1_argument); \ + GMOCK_MOCKER_(1, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(1, constness, Method).Invoke(gmock_a1); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1) constness { \ + GMOCK_MOCKER_(1, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(1, constness, Method).With(gmock_a1); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(1, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD2_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 2), \ + this_method_does_not_take_2_arguments); \ + GMOCK_MOCKER_(2, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(2, constness, Method).Invoke(gmock_a1, gmock_a2); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2) constness { \ + GMOCK_MOCKER_(2, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(2, constness, Method).With(gmock_a1, gmock_a2); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(2, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD3_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 3), \ + this_method_does_not_take_3_arguments); \ + GMOCK_MOCKER_(3, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(3, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3) constness { \ + GMOCK_MOCKER_(3, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(3, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(3, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD4_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 4), \ + this_method_does_not_take_4_arguments); \ + GMOCK_MOCKER_(4, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(4, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4) constness { \ + GMOCK_MOCKER_(4, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(4, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(4, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD5_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 5), \ + this_method_does_not_take_5_arguments); \ + GMOCK_MOCKER_(5, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(5, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5) constness { \ + GMOCK_MOCKER_(5, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(5, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(5, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD6_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 6), \ + this_method_does_not_take_6_arguments); \ + GMOCK_MOCKER_(6, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(6, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6) constness { \ + GMOCK_MOCKER_(6, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(6, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(6, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD7_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 7), \ + this_method_does_not_take_7_arguments); \ + GMOCK_MOCKER_(7, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(7, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7) constness { \ + GMOCK_MOCKER_(7, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(7, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(7, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD8_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_ARG_(tn, 8, __VA_ARGS__) gmock_a8) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 8), \ + this_method_does_not_take_8_arguments); \ + GMOCK_MOCKER_(8, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(8, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_MATCHER_(tn, 8, __VA_ARGS__) gmock_a8) constness { \ + GMOCK_MOCKER_(8, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(8, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(8, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD9_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_ARG_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_ARG_(tn, 9, __VA_ARGS__) gmock_a9) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 9), \ + this_method_does_not_take_9_arguments); \ + GMOCK_MOCKER_(9, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(9, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, \ + gmock_a9); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_MATCHER_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_MATCHER_(tn, 9, __VA_ARGS__) gmock_a9) constness { \ + GMOCK_MOCKER_(9, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(9, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, \ + gmock_a9); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(9, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD10_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_ARG_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_ARG_(tn, 9, __VA_ARGS__) gmock_a9, \ + GMOCK_ARG_(tn, 10, __VA_ARGS__) gmock_a10) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 10), \ + this_method_does_not_take_10_arguments); \ + GMOCK_MOCKER_(10, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(10, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9, \ + gmock_a10); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_MATCHER_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_MATCHER_(tn, 9, __VA_ARGS__) gmock_a9, \ + GMOCK_MATCHER_(tn, 10, \ + __VA_ARGS__) gmock_a10) constness { \ + GMOCK_MOCKER_(10, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(10, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9, \ + gmock_a10); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(10, constness, \ + Method) + +#define MOCK_METHOD0(m, ...) GMOCK_METHOD0_(, , , m, __VA_ARGS__) +#define MOCK_METHOD1(m, ...) GMOCK_METHOD1_(, , , m, __VA_ARGS__) +#define MOCK_METHOD2(m, ...) GMOCK_METHOD2_(, , , m, __VA_ARGS__) +#define MOCK_METHOD3(m, ...) GMOCK_METHOD3_(, , , m, __VA_ARGS__) +#define MOCK_METHOD4(m, ...) GMOCK_METHOD4_(, , , m, __VA_ARGS__) +#define MOCK_METHOD5(m, ...) GMOCK_METHOD5_(, , , m, __VA_ARGS__) +#define MOCK_METHOD6(m, ...) GMOCK_METHOD6_(, , , m, __VA_ARGS__) +#define MOCK_METHOD7(m, ...) GMOCK_METHOD7_(, , , m, __VA_ARGS__) +#define MOCK_METHOD8(m, ...) GMOCK_METHOD8_(, , , m, __VA_ARGS__) +#define MOCK_METHOD9(m, ...) GMOCK_METHOD9_(, , , m, __VA_ARGS__) +#define MOCK_METHOD10(m, ...) GMOCK_METHOD10_(, , , m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0(m, ...) GMOCK_METHOD0_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD1(m, ...) GMOCK_METHOD1_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD2(m, ...) GMOCK_METHOD2_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD3(m, ...) GMOCK_METHOD3_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD4(m, ...) GMOCK_METHOD4_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD5(m, ...) GMOCK_METHOD5_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD6(m, ...) GMOCK_METHOD6_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD7(m, ...) GMOCK_METHOD7_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD8(m, ...) GMOCK_METHOD8_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD9(m, ...) GMOCK_METHOD9_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD10(m, ...) GMOCK_METHOD10_(, const, , m, __VA_ARGS__) + +#define MOCK_METHOD0_T(m, ...) GMOCK_METHOD0_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD1_T(m, ...) GMOCK_METHOD1_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD2_T(m, ...) GMOCK_METHOD2_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD3_T(m, ...) GMOCK_METHOD3_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD4_T(m, ...) GMOCK_METHOD4_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD5_T(m, ...) GMOCK_METHOD5_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD6_T(m, ...) GMOCK_METHOD6_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD7_T(m, ...) GMOCK_METHOD7_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD8_T(m, ...) GMOCK_METHOD8_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD9_T(m, ...) GMOCK_METHOD9_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD10_T(m, ...) GMOCK_METHOD10_(typename, , , m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T(m, ...) \ + GMOCK_METHOD0_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T(m, ...) \ + GMOCK_METHOD1_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T(m, ...) \ + GMOCK_METHOD2_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T(m, ...) \ + GMOCK_METHOD3_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T(m, ...) \ + GMOCK_METHOD4_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T(m, ...) \ + GMOCK_METHOD5_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T(m, ...) \ + GMOCK_METHOD6_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T(m, ...) \ + GMOCK_METHOD7_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T(m, ...) \ + GMOCK_METHOD8_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T(m, ...) \ + GMOCK_METHOD9_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T(m, ...) \ + GMOCK_METHOD10_(typename, const, , m, __VA_ARGS__) + +#define MOCK_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD0_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD1_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD2_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD3_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD4_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD5_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD6_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD7_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD8_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD9_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD10_(, , ct, m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD0_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD1_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD2_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD3_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD4_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD5_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD6_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD7_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD8_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD9_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD10_(, const, ct, m, __VA_ARGS__) + +#define MOCK_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD0_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD1_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD2_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD3_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD4_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD5_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD6_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD7_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD8_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD9_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD10_(typename, , ct, m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD0_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD1_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD2_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD3_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD4_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD5_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD6_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD7_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD8_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD9_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD10_(typename, const, ct, m, __VA_ARGS__) + +// A MockFunction class has one mock method whose type is F. It is +// useful when you just want your test code to emit some messages and +// have Google Mock verify the right messages are sent (and perhaps at +// the right times). For example, if you are exercising code: +// +// Foo(1); +// Foo(2); +// Foo(3); +// +// and want to verify that Foo(1) and Foo(3) both invoke +// mock.Bar("a"), but Foo(2) doesn't invoke anything, you can write: +// +// TEST(FooTest, InvokesBarCorrectly) { +// MyMock mock; +// MockFunction check; +// { +// InSequence s; +// +// EXPECT_CALL(mock, Bar("a")); +// EXPECT_CALL(check, Call("1")); +// EXPECT_CALL(check, Call("2")); +// EXPECT_CALL(mock, Bar("a")); +// } +// Foo(1); +// check.Call("1"); +// Foo(2); +// check.Call("2"); +// Foo(3); +// } +// +// The expectation spec says that the first Bar("a") must happen +// before check point "1", the second Bar("a") must happen after check +// point "2", and nothing should happen between the two check +// points. The explicit check points make it easy to tell which +// Bar("a") is called by which call to Foo(). +// +// MockFunction can also be used to exercise code that accepts +// std::function callbacks. To do so, use AsStdFunction() method +// to create std::function proxy forwarding to original object's Call. +// Example: +// +// TEST(FooTest, RunsCallbackWithBarArgument) { +// MockFunction callback; +// EXPECT_CALL(callback, Call("bar")).WillOnce(Return(1)); +// Foo(callback.AsStdFunction()); +// } +template +class MockFunction; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD0_T(Call, R()); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this]() -> R { + return this->Call(); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD1_T(Call, R(A0)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0) -> R { + return this->Call(a0); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD2_T(Call, R(A0, A1)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0, A1 a1) -> R { + return this->Call(a0, a1); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD3_T(Call, R(A0, A1, A2)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0, A1 a1, A2 a2) -> R { + return this->Call(a0, a1, a2); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD4_T(Call, R(A0, A1, A2, A3)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0, A1 a1, A2 a2, A3 a3) -> R { + return this->Call(a0, a1, a2, a3); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD5_T(Call, R(A0, A1, A2, A3, A4)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0, A1 a1, A2 a2, A3 a3, A4 a4) -> R { + return this->Call(a0, a1, a2, a3, a4); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD6_T(Call, R(A0, A1, A2, A3, A4, A5)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) -> R { + return this->Call(a0, a1, a2, a3, a4, a5); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD7_T(Call, R(A0, A1, A2, A3, A4, A5, A6)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) -> R { + return this->Call(a0, a1, a2, a3, a4, a5, a6); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD8_T(Call, R(A0, A1, A2, A3, A4, A5, A6, A7)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) -> R { + return this->Call(a0, a1, a2, a3, a4, a5, a6, a7); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD9_T(Call, R(A0, A1, A2, A3, A4, A5, A6, A7, A8)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, + A8 a8) -> R { + return this->Call(a0, a1, a2, a3, a4, a5, a6, a7, a8); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD10_T(Call, R(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this](A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, + A8 a8, A9 a9) -> R { + return this->Call(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-function-mockers.h.pump b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-function-mockers.h.pump new file mode 100644 index 0000000000..811502d0ce --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-function-mockers.h.pump @@ -0,0 +1,291 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-function-mockers.h. +$$ +$var n = 10 $$ The maximum arity we support. +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements function mockers of various arities. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-internal-utils.h" + +#if GTEST_HAS_STD_FUNCTION_ +# include +#endif + +namespace testing { +namespace internal { + +template +class FunctionMockerBase; + +// Note: class FunctionMocker really belongs to the ::testing +// namespace. However if we define it in ::testing, MSVC will +// complain when classes in ::testing::internal declare it as a +// friend class template. To workaround this compiler bug, we define +// FunctionMocker in ::testing::internal and import it into ::testing. +template +class FunctionMocker; + + +$range i 0..n +$for i [[ +$range j 1..i +$var typename_As = [[$for j [[, typename A$j]]]] +$var As = [[$for j, [[A$j]]]] +$var as = [[$for j, [[a$j]]]] +$var Aas = [[$for j, [[A$j a$j]]]] +$var ms = [[$for j, [[m$j]]]] +$var matchers = [[$for j, [[const Matcher& m$j]]]] +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F($As); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With($matchers) { + +$if i >= 1 [[ + this->current_spec().SetMatchers(::testing::make_tuple($ms)); + +]] + return this->current_spec(); + } + + R Invoke($Aas) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple($as)); + } +}; + + +]] +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the FunctionMocker class template +// is meant to be defined in the ::testing namespace. The following +// line is just a trick for working around a bug in MSVC 8.0, which +// cannot handle it if we define FunctionMocker in ::testing. +using internal::FunctionMocker; + +// GMOCK_RESULT_(tn, F) expands to the result type of function type F. +// We define this as a variadic macro in case F contains unprotected +// commas (the same reason that we use variadic macros in other places +// in this file). +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_RESULT_(tn, ...) \ + tn ::testing::internal::Function<__VA_ARGS__>::Result + +// The type of argument N of the given function type. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_ARG_(tn, N, ...) \ + tn ::testing::internal::Function<__VA_ARGS__>::Argument##N + +// The matcher type for argument N of the given function type. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MATCHER_(tn, N, ...) \ + const ::testing::Matcher& + +// The variable for mocking the given method. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MOCKER_(arity, constness, Method) \ + GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) + + +$for i [[ +$range j 1..i +$var arg_as = [[$for j, \ + [[GMOCK_ARG_(tn, $j, __VA_ARGS__) gmock_a$j]]]] +$var as = [[$for j, [[gmock_a$j]]]] +$var matcher_as = [[$for j, \ + [[GMOCK_MATCHER_(tn, $j, __VA_ARGS__) gmock_a$j]]]] +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD$i[[]]_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + $arg_as) constness { \ + GTEST_COMPILE_ASSERT_((::testing::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value == $i), \ + this_method_does_not_take_$i[[]]_argument[[$if i != 1 [[s]]]]); \ + GMOCK_MOCKER_($i, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_($i, constness, Method).Invoke($as); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method($matcher_as) constness { \ + GMOCK_MOCKER_($i, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_($i, constness, Method).With($as); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_($i, constness, Method) + + +]] +$for i [[ +#define MOCK_METHOD$i(m, ...) GMOCK_METHOD$i[[]]_(, , , m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i(m, ...) GMOCK_METHOD$i[[]]_(, const, , m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_METHOD$i[[]]_T(m, ...) GMOCK_METHOD$i[[]]_(typename, , , m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i[[]]_T(m, ...) \ + GMOCK_METHOD$i[[]]_(typename, const, , m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_METHOD$i[[]]_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD$i[[]]_(, , ct, m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i[[]]_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD$i[[]]_(, const, ct, m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_METHOD$i[[]]_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD$i[[]]_(typename, , ct, m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i[[]]_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD$i[[]]_(typename, const, ct, m, __VA_ARGS__) + +]] + +// A MockFunction class has one mock method whose type is F. It is +// useful when you just want your test code to emit some messages and +// have Google Mock verify the right messages are sent (and perhaps at +// the right times). For example, if you are exercising code: +// +// Foo(1); +// Foo(2); +// Foo(3); +// +// and want to verify that Foo(1) and Foo(3) both invoke +// mock.Bar("a"), but Foo(2) doesn't invoke anything, you can write: +// +// TEST(FooTest, InvokesBarCorrectly) { +// MyMock mock; +// MockFunction check; +// { +// InSequence s; +// +// EXPECT_CALL(mock, Bar("a")); +// EXPECT_CALL(check, Call("1")); +// EXPECT_CALL(check, Call("2")); +// EXPECT_CALL(mock, Bar("a")); +// } +// Foo(1); +// check.Call("1"); +// Foo(2); +// check.Call("2"); +// Foo(3); +// } +// +// The expectation spec says that the first Bar("a") must happen +// before check point "1", the second Bar("a") must happen after check +// point "2", and nothing should happen between the two check +// points. The explicit check points make it easy to tell which +// Bar("a") is called by which call to Foo(). +// +// MockFunction can also be used to exercise code that accepts +// std::function callbacks. To do so, use AsStdFunction() method +// to create std::function proxy forwarding to original object's Call. +// Example: +// +// TEST(FooTest, RunsCallbackWithBarArgument) { +// MockFunction callback; +// EXPECT_CALL(callback, Call("bar")).WillOnce(Return(1)); +// Foo(callback.AsStdFunction()); +// } +template +class MockFunction; + + +$for i [[ +$range j 0..i-1 +$var ArgTypes = [[$for j, [[A$j]]]] +$var ArgNames = [[$for j, [[a$j]]]] +$var ArgDecls = [[$for j, [[A$j a$j]]]] +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD$i[[]]_T(Call, R($ArgTypes)); + +#if GTEST_HAS_STD_FUNCTION_ + std::function AsStdFunction() { + return [this]($ArgDecls) -> R { + return this->Call($ArgNames); + }; + } +#endif // GTEST_HAS_STD_FUNCTION_ + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + + +]] +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-matchers.h b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-matchers.h new file mode 100644 index 0000000000..57056fd91d --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-matchers.h @@ -0,0 +1,2179 @@ +// This file was GENERATED by command: +// pump.py gmock-generated-matchers.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic matchers. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ + +#include +#include +#include +#include +#include "gmock/gmock-matchers.h" + +namespace testing { +namespace internal { + +// The type of the i-th (0-based) field of Tuple. +#define GMOCK_FIELD_TYPE_(Tuple, i) \ + typename ::testing::tuple_element::type + +// TupleFields is for selecting fields from a +// tuple of type Tuple. It has two members: +// +// type: a tuple type whose i-th field is the ki-th field of Tuple. +// GetSelectedFields(t): returns fields k0, ..., and kn of t as a tuple. +// +// For example, in class TupleFields, 2, 0>, we have: +// +// type is tuple, and +// GetSelectedFields(make_tuple(true, 'a', 42)) is (42, true). + +template +class TupleFields; + +// This generic version is used when there are 10 selectors. +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t), get(t), get(t), get(t)); + } +}; + +// The following specialization is used for 0 ~ 9 selectors. + +template +class TupleFields { + public: + typedef ::testing::tuple<> type; + static type GetSelectedFields(const Tuple& /* t */) { + return type(); + } +}; + +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t), get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t), get(t), get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t), get(t), get(t), get(t), get(t), + get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::testing::tuple type; + static type GetSelectedFields(const Tuple& t) { + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t), get(t), get(t)); + } +}; + +#undef GMOCK_FIELD_TYPE_ + +// Implements the Args() matcher. +template +class ArgsMatcherImpl : public MatcherInterface { + public: + // ArgsTuple may have top-level const or reference modifiers. + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(ArgsTuple) RawArgsTuple; + typedef typename internal::TupleFields::type SelectedArgs; + typedef Matcher MonomorphicInnerMatcher; + + template + explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher) + : inner_matcher_(SafeMatcherCast(inner_matcher)) {} + + virtual bool MatchAndExplain(ArgsTuple args, + MatchResultListener* listener) const { + const SelectedArgs& selected_args = GetSelectedArgs(args); + if (!listener->IsInterested()) + return inner_matcher_.Matches(selected_args); + + PrintIndices(listener->stream()); + *listener << "are " << PrintToString(selected_args); + + StringMatchResultListener inner_listener; + const bool match = inner_matcher_.MatchAndExplain(selected_args, + &inner_listener); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return match; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeNegationTo(os); + } + + private: + static SelectedArgs GetSelectedArgs(ArgsTuple args) { + return TupleFields::GetSelectedFields(args); + } + + // Prints the indices of the selected fields. + static void PrintIndices(::std::ostream* os) { + *os << "whose fields ("; + const int indices[10] = { k0, k1, k2, k3, k4, k5, k6, k7, k8, k9 }; + for (int i = 0; i < 10; i++) { + if (indices[i] < 0) + break; + + if (i >= 1) + *os << ", "; + + *os << "#" << indices[i]; + } + *os << ") "; + } + + const MonomorphicInnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcherImpl); +}; + +template +class ArgsMatcher { + public: + explicit ArgsMatcher(const InnerMatcher& inner_matcher) + : inner_matcher_(inner_matcher) {} + + template + operator Matcher() const { + return MakeMatcher(new ArgsMatcherImpl(inner_matcher_)); + } + + private: + const InnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcher); +}; + +// A set of metafunctions for computing the result type of AllOf. +// AllOf(m1, ..., mN) returns +// AllOfResultN::type. + +// Although AllOf isn't defined for one argument, AllOfResult1 is defined +// to simplify the implementation. +template +struct AllOfResult1 { + typedef M1 type; +}; + +template +struct AllOfResult2 { + typedef BothOfMatcher< + typename AllOfResult1::type, + typename AllOfResult1::type + > type; +}; + +template +struct AllOfResult3 { + typedef BothOfMatcher< + typename AllOfResult1::type, + typename AllOfResult2::type + > type; +}; + +template +struct AllOfResult4 { + typedef BothOfMatcher< + typename AllOfResult2::type, + typename AllOfResult2::type + > type; +}; + +template +struct AllOfResult5 { + typedef BothOfMatcher< + typename AllOfResult2::type, + typename AllOfResult3::type + > type; +}; + +template +struct AllOfResult6 { + typedef BothOfMatcher< + typename AllOfResult3::type, + typename AllOfResult3::type + > type; +}; + +template +struct AllOfResult7 { + typedef BothOfMatcher< + typename AllOfResult3::type, + typename AllOfResult4::type + > type; +}; + +template +struct AllOfResult8 { + typedef BothOfMatcher< + typename AllOfResult4::type, + typename AllOfResult4::type + > type; +}; + +template +struct AllOfResult9 { + typedef BothOfMatcher< + typename AllOfResult4::type, + typename AllOfResult5::type + > type; +}; + +template +struct AllOfResult10 { + typedef BothOfMatcher< + typename AllOfResult5::type, + typename AllOfResult5::type + > type; +}; + +// A set of metafunctions for computing the result type of AnyOf. +// AnyOf(m1, ..., mN) returns +// AnyOfResultN::type. + +// Although AnyOf isn't defined for one argument, AnyOfResult1 is defined +// to simplify the implementation. +template +struct AnyOfResult1 { + typedef M1 type; +}; + +template +struct AnyOfResult2 { + typedef EitherOfMatcher< + typename AnyOfResult1::type, + typename AnyOfResult1::type + > type; +}; + +template +struct AnyOfResult3 { + typedef EitherOfMatcher< + typename AnyOfResult1::type, + typename AnyOfResult2::type + > type; +}; + +template +struct AnyOfResult4 { + typedef EitherOfMatcher< + typename AnyOfResult2::type, + typename AnyOfResult2::type + > type; +}; + +template +struct AnyOfResult5 { + typedef EitherOfMatcher< + typename AnyOfResult2::type, + typename AnyOfResult3::type + > type; +}; + +template +struct AnyOfResult6 { + typedef EitherOfMatcher< + typename AnyOfResult3::type, + typename AnyOfResult3::type + > type; +}; + +template +struct AnyOfResult7 { + typedef EitherOfMatcher< + typename AnyOfResult3::type, + typename AnyOfResult4::type + > type; +}; + +template +struct AnyOfResult8 { + typedef EitherOfMatcher< + typename AnyOfResult4::type, + typename AnyOfResult4::type + > type; +}; + +template +struct AnyOfResult9 { + typedef EitherOfMatcher< + typename AnyOfResult4::type, + typename AnyOfResult5::type + > type; +}; + +template +struct AnyOfResult10 { + typedef EitherOfMatcher< + typename AnyOfResult5::type, + typename AnyOfResult5::type + > type; +}; + +} // namespace internal + +// Args(a_matcher) matches a tuple if the selected +// fields of it matches a_matcher. C++ doesn't support default +// arguments for function templates, so we have to overload it. +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +// ElementsAre(e_1, e_2, ... e_n) matches an STL-style container with +// n elements, where the i-th element in the container must +// match the i-th argument in the list. Each argument of +// ElementsAre() can be either a value or a matcher. We support up to +// 10 arguments. +// +// The use of DecayArray in the implementation allows ElementsAre() +// to accept string literals, whose type is const char[N], but we +// want to treat them as const char*. +// +// NOTE: Since ElementsAre() cares about the order of the elements, it +// must not be used with containers whose elements's order is +// undefined (e.g. hash_map). + +inline internal::ElementsAreMatcher< + ::testing::tuple<> > +ElementsAre() { + typedef ::testing::tuple<> Args; + return internal::ElementsAreMatcher(Args()); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type> > +ElementsAre(const T1& e1) { + typedef ::testing::tuple< + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1)); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2)); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3)); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4)); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5)); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6)); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7)); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, + e8)); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, + e8, e9)); +} + +template +inline internal::ElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9, + const T10& e10) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, + e8, e9, e10)); +} + +// UnorderedElementsAre(e_1, e_2, ..., e_n) is an ElementsAre extension +// that matches n elements in any order. We support up to n=10 arguments. + +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple<> > +UnorderedElementsAre() { + typedef ::testing::tuple<> Args; + return internal::UnorderedElementsAreMatcher(Args()); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1) { + typedef ::testing::tuple< + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1)); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2)); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3)); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4)); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5)); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6)); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6, e7)); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6, e7, e8)); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6, e7, e8, e9)); +} + +template +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9, + const T10& e10) { + typedef ::testing::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6, e7, e8, e9, e10)); +} + +// AllOf(m1, m2, ..., mk) matches any value that matches all of the given +// sub-matchers. AllOf is called fully qualified to prevent ADL from firing. + +template +inline typename internal::AllOfResult2::type +AllOf(M1 m1, M2 m2) { + return typename internal::AllOfResult2::type( + m1, + m2); +} + +template +inline typename internal::AllOfResult3::type +AllOf(M1 m1, M2 m2, M3 m3) { + return typename internal::AllOfResult3::type( + m1, + ::testing::AllOf(m2, m3)); +} + +template +inline typename internal::AllOfResult4::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4) { + return typename internal::AllOfResult4::type( + ::testing::AllOf(m1, m2), + ::testing::AllOf(m3, m4)); +} + +template +inline typename internal::AllOfResult5::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5) { + return typename internal::AllOfResult5::type( + ::testing::AllOf(m1, m2), + ::testing::AllOf(m3, m4, m5)); +} + +template +inline typename internal::AllOfResult6::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6) { + return typename internal::AllOfResult6::type( + ::testing::AllOf(m1, m2, m3), + ::testing::AllOf(m4, m5, m6)); +} + +template +inline typename internal::AllOfResult7::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7) { + return typename internal::AllOfResult7::type( + ::testing::AllOf(m1, m2, m3), + ::testing::AllOf(m4, m5, m6, m7)); +} + +template +inline typename internal::AllOfResult8::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8) { + return typename internal::AllOfResult8::type( + ::testing::AllOf(m1, m2, m3, m4), + ::testing::AllOf(m5, m6, m7, m8)); +} + +template +inline typename internal::AllOfResult9::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9) { + return typename internal::AllOfResult9::type( + ::testing::AllOf(m1, m2, m3, m4), + ::testing::AllOf(m5, m6, m7, m8, m9)); +} + +template +inline typename internal::AllOfResult10::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9, M10 m10) { + return typename internal::AllOfResult10::type( + ::testing::AllOf(m1, m2, m3, m4, m5), + ::testing::AllOf(m6, m7, m8, m9, m10)); +} + +// AnyOf(m1, m2, ..., mk) matches any value that matches any of the given +// sub-matchers. AnyOf is called fully qualified to prevent ADL from firing. + +template +inline typename internal::AnyOfResult2::type +AnyOf(M1 m1, M2 m2) { + return typename internal::AnyOfResult2::type( + m1, + m2); +} + +template +inline typename internal::AnyOfResult3::type +AnyOf(M1 m1, M2 m2, M3 m3) { + return typename internal::AnyOfResult3::type( + m1, + ::testing::AnyOf(m2, m3)); +} + +template +inline typename internal::AnyOfResult4::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4) { + return typename internal::AnyOfResult4::type( + ::testing::AnyOf(m1, m2), + ::testing::AnyOf(m3, m4)); +} + +template +inline typename internal::AnyOfResult5::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5) { + return typename internal::AnyOfResult5::type( + ::testing::AnyOf(m1, m2), + ::testing::AnyOf(m3, m4, m5)); +} + +template +inline typename internal::AnyOfResult6::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6) { + return typename internal::AnyOfResult6::type( + ::testing::AnyOf(m1, m2, m3), + ::testing::AnyOf(m4, m5, m6)); +} + +template +inline typename internal::AnyOfResult7::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7) { + return typename internal::AnyOfResult7::type( + ::testing::AnyOf(m1, m2, m3), + ::testing::AnyOf(m4, m5, m6, m7)); +} + +template +inline typename internal::AnyOfResult8::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8) { + return typename internal::AnyOfResult8::type( + ::testing::AnyOf(m1, m2, m3, m4), + ::testing::AnyOf(m5, m6, m7, m8)); +} + +template +inline typename internal::AnyOfResult9::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9) { + return typename internal::AnyOfResult9::type( + ::testing::AnyOf(m1, m2, m3, m4), + ::testing::AnyOf(m5, m6, m7, m8, m9)); +} + +template +inline typename internal::AnyOfResult10::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9, M10 m10) { + return typename internal::AnyOfResult10::type( + ::testing::AnyOf(m1, m2, m3, m4, m5), + ::testing::AnyOf(m6, m7, m8, m9, m10)); +} + +} // namespace testing + + +// The MATCHER* family of macros can be used in a namespace scope to +// define custom matchers easily. +// +// Basic Usage +// =========== +// +// The syntax +// +// MATCHER(name, description_string) { statements; } +// +// defines a matcher with the given name that executes the statements, +// which must return a bool to indicate if the match succeeds. Inside +// the statements, you can refer to the value being matched by 'arg', +// and refer to its type by 'arg_type'. +// +// The description string documents what the matcher does, and is used +// to generate the failure message when the match fails. Since a +// MATCHER() is usually defined in a header file shared by multiple +// C++ source files, we require the description to be a C-string +// literal to avoid possible side effects. It can be empty, in which +// case we'll use the sequence of words in the matcher name as the +// description. +// +// For example: +// +// MATCHER(IsEven, "") { return (arg % 2) == 0; } +// +// allows you to write +// +// // Expects mock_foo.Bar(n) to be called where n is even. +// EXPECT_CALL(mock_foo, Bar(IsEven())); +// +// or, +// +// // Verifies that the value of some_expression is even. +// EXPECT_THAT(some_expression, IsEven()); +// +// If the above assertion fails, it will print something like: +// +// Value of: some_expression +// Expected: is even +// Actual: 7 +// +// where the description "is even" is automatically calculated from the +// matcher name IsEven. +// +// Argument Type +// ============= +// +// Note that the type of the value being matched (arg_type) is +// determined by the context in which you use the matcher and is +// supplied to you by the compiler, so you don't need to worry about +// declaring it (nor can you). This allows the matcher to be +// polymorphic. For example, IsEven() can be used to match any type +// where the value of "(arg % 2) == 0" can be implicitly converted to +// a bool. In the "Bar(IsEven())" example above, if method Bar() +// takes an int, 'arg_type' will be int; if it takes an unsigned long, +// 'arg_type' will be unsigned long; and so on. +// +// Parameterizing Matchers +// ======================= +// +// Sometimes you'll want to parameterize the matcher. For that you +// can use another macro: +// +// MATCHER_P(name, param_name, description_string) { statements; } +// +// For example: +// +// MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +// +// will allow you to write: +// +// EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +// +// which may lead to this message (assuming n is 10): +// +// Value of: Blah("a") +// Expected: has absolute value 10 +// Actual: -9 +// +// Note that both the matcher description and its parameter are +// printed, making the message human-friendly. +// +// In the matcher definition body, you can write 'foo_type' to +// reference the type of a parameter named 'foo'. For example, in the +// body of MATCHER_P(HasAbsoluteValue, value) above, you can write +// 'value_type' to refer to the type of 'value'. +// +// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P10 to +// support multi-parameter matchers. +// +// Describing Parameterized Matchers +// ================================= +// +// The last argument to MATCHER*() is a string-typed expression. The +// expression can reference all of the matcher's parameters and a +// special bool-typed variable named 'negation'. When 'negation' is +// false, the expression should evaluate to the matcher's description; +// otherwise it should evaluate to the description of the negation of +// the matcher. For example, +// +// using testing::PrintToString; +// +// MATCHER_P2(InClosedRange, low, hi, +// string(negation ? "is not" : "is") + " in range [" + +// PrintToString(low) + ", " + PrintToString(hi) + "]") { +// return low <= arg && arg <= hi; +// } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: is in range [4, 6] +// ... +// Expected: is not in range [2, 4] +// +// If you specify "" as the description, the failure message will +// contain the sequence of words in the matcher name followed by the +// parameter values printed as a tuple. For example, +// +// MATCHER_P2(InClosedRange, low, hi, "") { ... } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: in closed range (4, 6) +// ... +// Expected: not (in closed range (2, 4)) +// +// Types of Matcher Parameters +// =========================== +// +// For the purpose of typing, you can view +// +// MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +// +// as shorthand for +// +// template +// FooMatcherPk +// Foo(p1_type p1, ..., pk_type pk) { ... } +// +// When you write Foo(v1, ..., vk), the compiler infers the types of +// the parameters v1, ..., and vk for you. If you are not happy with +// the result of the type inference, you can specify the types by +// explicitly instantiating the template, as in Foo(5, +// false). As said earlier, you don't get to (or need to) specify +// 'arg_type' as that's determined by the context in which the matcher +// is used. You can assign the result of expression Foo(p1, ..., pk) +// to a variable of type FooMatcherPk. This +// can be useful when composing matchers. +// +// While you can instantiate a matcher template with reference types, +// passing the parameters by pointer usually makes your code more +// readable. If, however, you still want to pass a parameter by +// reference, be aware that in the failure message generated by the +// matcher you will see the value of the referenced object but not its +// address. +// +// Explaining Match Results +// ======================== +// +// Sometimes the matcher description alone isn't enough to explain why +// the match has failed or succeeded. For example, when expecting a +// long string, it can be very helpful to also print the diff between +// the expected string and the actual one. To achieve that, you can +// optionally stream additional information to a special variable +// named result_listener, whose type is a pointer to class +// MatchResultListener: +// +// MATCHER_P(EqualsLongString, str, "") { +// if (arg == str) return true; +// +// *result_listener << "the difference: " +/// << DiffStrings(str, arg); +// return false; +// } +// +// Overloading Matchers +// ==================== +// +// You can overload matchers with different numbers of parameters: +// +// MATCHER_P(Blah, a, description_string1) { ... } +// MATCHER_P2(Blah, a, b, description_string2) { ... } +// +// Caveats +// ======= +// +// When defining a new matcher, you should also consider implementing +// MatcherInterface or using MakePolymorphicMatcher(). These +// approaches require more work than the MATCHER* macros, but also +// give you more control on the types of the value being matched and +// the matcher parameters, which may leads to better compiler error +// messages when the matcher is used wrong. They also allow +// overloading matchers based on parameter types (as opposed to just +// based on the number of parameters). +// +// MATCHER*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using MATCHER*() inside +// a function. +// +// More Information +// ================ +// +// To learn more about using these macros, please search for 'MATCHER' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +#define MATCHER(name, description)\ + class name##Matcher {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl()\ + {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple<>()));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl());\ + }\ + name##Matcher() {\ + }\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##Matcher);\ + };\ + inline name##Matcher name() {\ + return name##Matcher();\ + }\ + template \ + bool name##Matcher::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P(name, p0, description)\ + template \ + class name##MatcherP {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + explicit gmock_Impl(p0##_type gmock_p0)\ + : p0(gmock_p0) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0));\ + }\ + explicit name##MatcherP(p0##_type gmock_p0) : p0(gmock_p0) {\ + }\ + p0##_type p0;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP);\ + };\ + template \ + inline name##MatcherP name(p0##_type p0) {\ + return name##MatcherP(p0);\ + }\ + template \ + template \ + bool name##MatcherP::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P2(name, p0, p1, description)\ + template \ + class name##MatcherP2 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1)\ + : p0(gmock_p0), p1(gmock_p1) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0, p1)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1));\ + }\ + name##MatcherP2(p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), \ + p1(gmock_p1) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP2);\ + };\ + template \ + inline name##MatcherP2 name(p0##_type p0, \ + p1##_type p1) {\ + return name##MatcherP2(p0, p1);\ + }\ + template \ + template \ + bool name##MatcherP2::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P3(name, p0, p1, p2, description)\ + template \ + class name##MatcherP3 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0, p1, \ + p2)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2));\ + }\ + name##MatcherP3(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP3);\ + };\ + template \ + inline name##MatcherP3 name(p0##_type p0, \ + p1##_type p1, p2##_type p2) {\ + return name##MatcherP3(p0, p1, p2);\ + }\ + template \ + template \ + bool name##MatcherP3::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P4(name, p0, p1, p2, p3, description)\ + template \ + class name##MatcherP4 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0, p1, p2, p3)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3));\ + }\ + name##MatcherP4(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP4);\ + };\ + template \ + inline name##MatcherP4 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3) {\ + return name##MatcherP4(p0, \ + p1, p2, p3);\ + }\ + template \ + template \ + bool name##MatcherP4::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P5(name, p0, p1, p2, p3, p4, description)\ + template \ + class name##MatcherP5 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0, p1, p2, p3, p4)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4));\ + }\ + name##MatcherP5(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, \ + p4##_type gmock_p4) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP5);\ + };\ + template \ + inline name##MatcherP5 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4) {\ + return name##MatcherP5(p0, p1, p2, p3, p4);\ + }\ + template \ + template \ + bool name##MatcherP5::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P6(name, p0, p1, p2, p3, p4, p5, description)\ + template \ + class name##MatcherP6 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0, p1, p2, p3, p4, p5)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5));\ + }\ + name##MatcherP6(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP6);\ + };\ + template \ + inline name##MatcherP6 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3, p4##_type p4, p5##_type p5) {\ + return name##MatcherP6(p0, p1, p2, p3, p4, p5);\ + }\ + template \ + template \ + bool name##MatcherP6::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P7(name, p0, p1, p2, p3, p4, p5, p6, description)\ + template \ + class name##MatcherP7 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0, p1, p2, p3, p4, p5, \ + p6)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6));\ + }\ + name##MatcherP7(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), \ + p6(gmock_p6) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP7);\ + };\ + template \ + inline name##MatcherP7 name(p0##_type p0, p1##_type p1, \ + p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6) {\ + return name##MatcherP7(p0, p1, p2, p3, p4, p5, p6);\ + }\ + template \ + template \ + bool name##MatcherP7::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P8(name, p0, p1, p2, p3, p4, p5, p6, p7, description)\ + template \ + class name##MatcherP8 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0, p1, p2, \ + p3, p4, p5, p6, p7)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7));\ + }\ + name##MatcherP8(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, \ + p7##_type gmock_p7) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP8);\ + };\ + template \ + inline name##MatcherP8 name(p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6, p7##_type p7) {\ + return name##MatcherP8(p0, p1, p2, p3, p4, p5, \ + p6, p7);\ + }\ + template \ + template \ + bool name##MatcherP8::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P9(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, description)\ + template \ + class name##MatcherP9 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0, p1, p2, p3, p4, p5, p6, p7, p8)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7, p8));\ + }\ + name##MatcherP9(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP9);\ + };\ + template \ + inline name##MatcherP9 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, \ + p8##_type p8) {\ + return name##MatcherP9(p0, p1, p2, \ + p3, p4, p5, p6, p7, p8);\ + }\ + template \ + template \ + bool name##MatcherP9::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P10(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, description)\ + template \ + class name##MatcherP10 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8), p9(gmock_p9) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9));\ + }\ + name##MatcherP10(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8, p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8), p9(gmock_p9) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP10);\ + };\ + template \ + inline name##MatcherP10 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9) {\ + return name##MatcherP10(p0, \ + p1, p2, p3, p4, p5, p6, p7, p8, p9);\ + }\ + template \ + template \ + bool name##MatcherP10::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-matchers.h.pump b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-matchers.h.pump new file mode 100644 index 0000000000..de30c2c92b --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-matchers.h.pump @@ -0,0 +1,672 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-actions.h. +$$ +$var n = 10 $$ The maximum arity we support. +$$ }} This line fixes auto-indentation of the following code in Emacs. +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic matchers. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ + +#include +#include +#include +#include +#include "gmock/gmock-matchers.h" + +namespace testing { +namespace internal { + +$range i 0..n-1 + +// The type of the i-th (0-based) field of Tuple. +#define GMOCK_FIELD_TYPE_(Tuple, i) \ + typename ::testing::tuple_element::type + +// TupleFields is for selecting fields from a +// tuple of type Tuple. It has two members: +// +// type: a tuple type whose i-th field is the ki-th field of Tuple. +// GetSelectedFields(t): returns fields k0, ..., and kn of t as a tuple. +// +// For example, in class TupleFields, 2, 0>, we have: +// +// type is tuple, and +// GetSelectedFields(make_tuple(true, 'a', 42)) is (42, true). + +template +class TupleFields; + +// This generic version is used when there are $n selectors. +template +class TupleFields { + public: + typedef ::testing::tuple<$for i, [[GMOCK_FIELD_TYPE_(Tuple, k$i)]]> type; + static type GetSelectedFields(const Tuple& t) { + return type($for i, [[get(t)]]); + } +}; + +// The following specialization is used for 0 ~ $(n-1) selectors. + +$for i [[ +$$ }}} +$range j 0..i-1 +$range k 0..n-1 + +template +class TupleFields { + public: + typedef ::testing::tuple<$for j, [[GMOCK_FIELD_TYPE_(Tuple, k$j)]]> type; + static type GetSelectedFields(const Tuple& $if i==0 [[/* t */]] $else [[t]]) { + return type($for j, [[get(t)]]); + } +}; + +]] + +#undef GMOCK_FIELD_TYPE_ + +// Implements the Args() matcher. + +$var ks = [[$for i, [[k$i]]]] +template +class ArgsMatcherImpl : public MatcherInterface { + public: + // ArgsTuple may have top-level const or reference modifiers. + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(ArgsTuple) RawArgsTuple; + typedef typename internal::TupleFields::type SelectedArgs; + typedef Matcher MonomorphicInnerMatcher; + + template + explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher) + : inner_matcher_(SafeMatcherCast(inner_matcher)) {} + + virtual bool MatchAndExplain(ArgsTuple args, + MatchResultListener* listener) const { + const SelectedArgs& selected_args = GetSelectedArgs(args); + if (!listener->IsInterested()) + return inner_matcher_.Matches(selected_args); + + PrintIndices(listener->stream()); + *listener << "are " << PrintToString(selected_args); + + StringMatchResultListener inner_listener; + const bool match = inner_matcher_.MatchAndExplain(selected_args, + &inner_listener); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return match; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeNegationTo(os); + } + + private: + static SelectedArgs GetSelectedArgs(ArgsTuple args) { + return TupleFields::GetSelectedFields(args); + } + + // Prints the indices of the selected fields. + static void PrintIndices(::std::ostream* os) { + *os << "whose fields ("; + const int indices[$n] = { $ks }; + for (int i = 0; i < $n; i++) { + if (indices[i] < 0) + break; + + if (i >= 1) + *os << ", "; + + *os << "#" << indices[i]; + } + *os << ") "; + } + + const MonomorphicInnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcherImpl); +}; + +template +class ArgsMatcher { + public: + explicit ArgsMatcher(const InnerMatcher& inner_matcher) + : inner_matcher_(inner_matcher) {} + + template + operator Matcher() const { + return MakeMatcher(new ArgsMatcherImpl(inner_matcher_)); + } + + private: + const InnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcher); +}; + +// A set of metafunctions for computing the result type of AllOf. +// AllOf(m1, ..., mN) returns +// AllOfResultN::type. + +// Although AllOf isn't defined for one argument, AllOfResult1 is defined +// to simplify the implementation. +template +struct AllOfResult1 { + typedef M1 type; +}; + +$range i 1..n + +$range i 2..n +$for i [[ +$range j 2..i +$var m = i/2 +$range k 1..m +$range t m+1..i + +template +struct AllOfResult$i { + typedef BothOfMatcher< + typename AllOfResult$m<$for k, [[M$k]]>::type, + typename AllOfResult$(i-m)<$for t, [[M$t]]>::type + > type; +}; + +]] + +// A set of metafunctions for computing the result type of AnyOf. +// AnyOf(m1, ..., mN) returns +// AnyOfResultN::type. + +// Although AnyOf isn't defined for one argument, AnyOfResult1 is defined +// to simplify the implementation. +template +struct AnyOfResult1 { + typedef M1 type; +}; + +$range i 1..n + +$range i 2..n +$for i [[ +$range j 2..i +$var m = i/2 +$range k 1..m +$range t m+1..i + +template +struct AnyOfResult$i { + typedef EitherOfMatcher< + typename AnyOfResult$m<$for k, [[M$k]]>::type, + typename AnyOfResult$(i-m)<$for t, [[M$t]]>::type + > type; +}; + +]] + +} // namespace internal + +// Args(a_matcher) matches a tuple if the selected +// fields of it matches a_matcher. C++ doesn't support default +// arguments for function templates, so we have to overload it. + +$range i 0..n +$for i [[ +$range j 1..i +template <$for j [[int k$j, ]]typename InnerMatcher> +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + + +]] +// ElementsAre(e_1, e_2, ... e_n) matches an STL-style container with +// n elements, where the i-th element in the container must +// match the i-th argument in the list. Each argument of +// ElementsAre() can be either a value or a matcher. We support up to +// $n arguments. +// +// The use of DecayArray in the implementation allows ElementsAre() +// to accept string literals, whose type is const char[N], but we +// want to treat them as const char*. +// +// NOTE: Since ElementsAre() cares about the order of the elements, it +// must not be used with containers whose elements's order is +// undefined (e.g. hash_map). + +$range i 0..n +$for i [[ + +$range j 1..i + +$if i>0 [[ + +template <$for j, [[typename T$j]]> +]] + +inline internal::ElementsAreMatcher< + ::testing::tuple< +$for j, [[ + + typename internal::DecayArray::type]]> > +ElementsAre($for j, [[const T$j& e$j]]) { + typedef ::testing::tuple< +$for j, [[ + + typename internal::DecayArray::type]]> Args; + return internal::ElementsAreMatcher(Args($for j, [[e$j]])); +} + +]] + +// UnorderedElementsAre(e_1, e_2, ..., e_n) is an ElementsAre extension +// that matches n elements in any order. We support up to n=$n arguments. + +$range i 0..n +$for i [[ + +$range j 1..i + +$if i>0 [[ + +template <$for j, [[typename T$j]]> +]] + +inline internal::UnorderedElementsAreMatcher< + ::testing::tuple< +$for j, [[ + + typename internal::DecayArray::type]]> > +UnorderedElementsAre($for j, [[const T$j& e$j]]) { + typedef ::testing::tuple< +$for j, [[ + + typename internal::DecayArray::type]]> Args; + return internal::UnorderedElementsAreMatcher(Args($for j, [[e$j]])); +} + +]] + +// AllOf(m1, m2, ..., mk) matches any value that matches all of the given +// sub-matchers. AllOf is called fully qualified to prevent ADL from firing. + +$range i 2..n +$for i [[ +$range j 1..i +$var m = i/2 +$range k 1..m +$range t m+1..i + +template <$for j, [[typename M$j]]> +inline typename internal::AllOfResult$i<$for j, [[M$j]]>::type +AllOf($for j, [[M$j m$j]]) { + return typename internal::AllOfResult$i<$for j, [[M$j]]>::type( + $if m == 1 [[m1]] $else [[::testing::AllOf($for k, [[m$k]])]], + $if m+1 == i [[m$i]] $else [[::testing::AllOf($for t, [[m$t]])]]); +} + +]] + +// AnyOf(m1, m2, ..., mk) matches any value that matches any of the given +// sub-matchers. AnyOf is called fully qualified to prevent ADL from firing. + +$range i 2..n +$for i [[ +$range j 1..i +$var m = i/2 +$range k 1..m +$range t m+1..i + +template <$for j, [[typename M$j]]> +inline typename internal::AnyOfResult$i<$for j, [[M$j]]>::type +AnyOf($for j, [[M$j m$j]]) { + return typename internal::AnyOfResult$i<$for j, [[M$j]]>::type( + $if m == 1 [[m1]] $else [[::testing::AnyOf($for k, [[m$k]])]], + $if m+1 == i [[m$i]] $else [[::testing::AnyOf($for t, [[m$t]])]]); +} + +]] + +} // namespace testing +$$ } // This Pump meta comment fixes auto-indentation in Emacs. It will not +$$ // show up in the generated code. + + +// The MATCHER* family of macros can be used in a namespace scope to +// define custom matchers easily. +// +// Basic Usage +// =========== +// +// The syntax +// +// MATCHER(name, description_string) { statements; } +// +// defines a matcher with the given name that executes the statements, +// which must return a bool to indicate if the match succeeds. Inside +// the statements, you can refer to the value being matched by 'arg', +// and refer to its type by 'arg_type'. +// +// The description string documents what the matcher does, and is used +// to generate the failure message when the match fails. Since a +// MATCHER() is usually defined in a header file shared by multiple +// C++ source files, we require the description to be a C-string +// literal to avoid possible side effects. It can be empty, in which +// case we'll use the sequence of words in the matcher name as the +// description. +// +// For example: +// +// MATCHER(IsEven, "") { return (arg % 2) == 0; } +// +// allows you to write +// +// // Expects mock_foo.Bar(n) to be called where n is even. +// EXPECT_CALL(mock_foo, Bar(IsEven())); +// +// or, +// +// // Verifies that the value of some_expression is even. +// EXPECT_THAT(some_expression, IsEven()); +// +// If the above assertion fails, it will print something like: +// +// Value of: some_expression +// Expected: is even +// Actual: 7 +// +// where the description "is even" is automatically calculated from the +// matcher name IsEven. +// +// Argument Type +// ============= +// +// Note that the type of the value being matched (arg_type) is +// determined by the context in which you use the matcher and is +// supplied to you by the compiler, so you don't need to worry about +// declaring it (nor can you). This allows the matcher to be +// polymorphic. For example, IsEven() can be used to match any type +// where the value of "(arg % 2) == 0" can be implicitly converted to +// a bool. In the "Bar(IsEven())" example above, if method Bar() +// takes an int, 'arg_type' will be int; if it takes an unsigned long, +// 'arg_type' will be unsigned long; and so on. +// +// Parameterizing Matchers +// ======================= +// +// Sometimes you'll want to parameterize the matcher. For that you +// can use another macro: +// +// MATCHER_P(name, param_name, description_string) { statements; } +// +// For example: +// +// MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +// +// will allow you to write: +// +// EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +// +// which may lead to this message (assuming n is 10): +// +// Value of: Blah("a") +// Expected: has absolute value 10 +// Actual: -9 +// +// Note that both the matcher description and its parameter are +// printed, making the message human-friendly. +// +// In the matcher definition body, you can write 'foo_type' to +// reference the type of a parameter named 'foo'. For example, in the +// body of MATCHER_P(HasAbsoluteValue, value) above, you can write +// 'value_type' to refer to the type of 'value'. +// +// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P$n to +// support multi-parameter matchers. +// +// Describing Parameterized Matchers +// ================================= +// +// The last argument to MATCHER*() is a string-typed expression. The +// expression can reference all of the matcher's parameters and a +// special bool-typed variable named 'negation'. When 'negation' is +// false, the expression should evaluate to the matcher's description; +// otherwise it should evaluate to the description of the negation of +// the matcher. For example, +// +// using testing::PrintToString; +// +// MATCHER_P2(InClosedRange, low, hi, +// string(negation ? "is not" : "is") + " in range [" + +// PrintToString(low) + ", " + PrintToString(hi) + "]") { +// return low <= arg && arg <= hi; +// } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: is in range [4, 6] +// ... +// Expected: is not in range [2, 4] +// +// If you specify "" as the description, the failure message will +// contain the sequence of words in the matcher name followed by the +// parameter values printed as a tuple. For example, +// +// MATCHER_P2(InClosedRange, low, hi, "") { ... } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: in closed range (4, 6) +// ... +// Expected: not (in closed range (2, 4)) +// +// Types of Matcher Parameters +// =========================== +// +// For the purpose of typing, you can view +// +// MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +// +// as shorthand for +// +// template +// FooMatcherPk +// Foo(p1_type p1, ..., pk_type pk) { ... } +// +// When you write Foo(v1, ..., vk), the compiler infers the types of +// the parameters v1, ..., and vk for you. If you are not happy with +// the result of the type inference, you can specify the types by +// explicitly instantiating the template, as in Foo(5, +// false). As said earlier, you don't get to (or need to) specify +// 'arg_type' as that's determined by the context in which the matcher +// is used. You can assign the result of expression Foo(p1, ..., pk) +// to a variable of type FooMatcherPk. This +// can be useful when composing matchers. +// +// While you can instantiate a matcher template with reference types, +// passing the parameters by pointer usually makes your code more +// readable. If, however, you still want to pass a parameter by +// reference, be aware that in the failure message generated by the +// matcher you will see the value of the referenced object but not its +// address. +// +// Explaining Match Results +// ======================== +// +// Sometimes the matcher description alone isn't enough to explain why +// the match has failed or succeeded. For example, when expecting a +// long string, it can be very helpful to also print the diff between +// the expected string and the actual one. To achieve that, you can +// optionally stream additional information to a special variable +// named result_listener, whose type is a pointer to class +// MatchResultListener: +// +// MATCHER_P(EqualsLongString, str, "") { +// if (arg == str) return true; +// +// *result_listener << "the difference: " +/// << DiffStrings(str, arg); +// return false; +// } +// +// Overloading Matchers +// ==================== +// +// You can overload matchers with different numbers of parameters: +// +// MATCHER_P(Blah, a, description_string1) { ... } +// MATCHER_P2(Blah, a, b, description_string2) { ... } +// +// Caveats +// ======= +// +// When defining a new matcher, you should also consider implementing +// MatcherInterface or using MakePolymorphicMatcher(). These +// approaches require more work than the MATCHER* macros, but also +// give you more control on the types of the value being matched and +// the matcher parameters, which may leads to better compiler error +// messages when the matcher is used wrong. They also allow +// overloading matchers based on parameter types (as opposed to just +// based on the number of parameters). +// +// MATCHER*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using MATCHER*() inside +// a function. +// +// More Information +// ================ +// +// To learn more about using these macros, please search for 'MATCHER' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +$range i 0..n +$for i + +[[ +$var macro_name = [[$if i==0 [[MATCHER]] $elif i==1 [[MATCHER_P]] + $else [[MATCHER_P$i]]]] +$var class_name = [[name##Matcher[[$if i==0 [[]] $elif i==1 [[P]] + $else [[P$i]]]]]] +$range j 0..i-1 +$var template = [[$if i==0 [[]] $else [[ + + template <$for j, [[typename p$j##_type]]>\ +]]]] +$var ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] +$var impl_ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] +$var impl_inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(gmock_p$j)]]]]]] +$var inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(gmock_p$j)]]]]]] +$var params = [[$for j, [[p$j]]]] +$var param_types = [[$if i==0 [[]] $else [[<$for j, [[p$j##_type]]>]]]] +$var param_types_and_names = [[$for j, [[p$j##_type p$j]]]] +$var param_field_decls = [[$for j +[[ + + p$j##_type p$j;\ +]]]] +$var param_field_decls2 = [[$for j +[[ + + p$j##_type p$j;\ +]]]] + +#define $macro_name(name$for j [[, p$j]], description)\$template + class $class_name {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + [[$if i==1 [[explicit ]]]]gmock_Impl($impl_ctor_param_list)\ + $impl_inits {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\$param_field_decls + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::testing::tuple<$for j, [[p$j##_type]]>($for j, [[p$j]])));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl($params));\ + }\ + [[$if i==1 [[explicit ]]]]$class_name($ctor_param_list)$inits {\ + }\$param_field_decls2 + private:\ + GTEST_DISALLOW_ASSIGN_($class_name);\ + };\$template + inline $class_name$param_types name($param_types_and_names) {\ + return $class_name$param_types($params);\ + }\$template + template \ + bool $class_name$param_types::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const +]] + + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-nice-strict.h b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-nice-strict.h new file mode 100644 index 0000000000..4095f4d5bc --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-nice-strict.h @@ -0,0 +1,397 @@ +// This file was GENERATED by command: +// pump.py gmock-generated-nice-strict.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements class templates NiceMock, NaggyMock, and StrictMock. +// +// Given a mock class MockFoo that is created using Google Mock, +// NiceMock is a subclass of MockFoo that allows +// uninteresting calls (i.e. calls to mock methods that have no +// EXPECT_CALL specs), NaggyMock is a subclass of MockFoo +// that prints a warning when an uninteresting call occurs, and +// StrictMock is a subclass of MockFoo that treats all +// uninteresting calls as errors. +// +// Currently a mock is naggy by default, so MockFoo and +// NaggyMock behave like the same. However, we will soon +// switch the default behavior of mocks to be nice, as that in general +// leads to more maintainable tests. When that happens, MockFoo will +// stop behaving like NaggyMock and start behaving like +// NiceMock. +// +// NiceMock, NaggyMock, and StrictMock "inherit" the constructors of +// their respective base class, with up-to 10 arguments. Therefore +// you can write NiceMock(5, "a") to construct a nice mock +// where MockFoo has a constructor that accepts (int, const char*), +// for example. +// +// A known limitation is that NiceMock, NaggyMock, +// and StrictMock only works for mock methods defined using +// the MOCK_METHOD* family of macros DIRECTLY in the MockFoo class. +// If a mock method is defined in a base class of MockFoo, the "nice" +// or "strict" modifier may not affect it, depending on the compiler. +// In particular, nesting NiceMock, NaggyMock, and StrictMock is NOT +// supported. +// +// Another known limitation is that the constructors of the base mock +// cannot have arguments passed by non-const reference, which are +// banned by the Google C++ style guide anyway. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-port.h" + +namespace testing { + +template +class NiceMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + NiceMock() { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit NiceMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + template + NiceMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, + const A4& a4) : MockClass(a1, a2, a3, a4) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5) : MockClass(a1, a2, a3, a4, a5) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, + a6, a7) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, + a2, a3, a4, a5, a6, a7, a8) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, + const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, + const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + virtual ~NiceMock() { + ::testing::Mock::UnregisterCallReaction( + internal::ImplicitCast_(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(NiceMock); +}; + +template +class NaggyMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + NaggyMock() { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit NaggyMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + template + NaggyMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, + const A4& a4) : MockClass(a1, a2, a3, a4) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5) : MockClass(a1, a2, a3, a4, a5) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, + a6, a7) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, + a2, a3, a4, a5, a6, a7, a8) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, + const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, + const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + virtual ~NaggyMock() { + ::testing::Mock::UnregisterCallReaction( + internal::ImplicitCast_(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(NaggyMock); +}; + +template +class StrictMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + StrictMock() { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit StrictMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + template + StrictMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, + const A4& a4) : MockClass(a1, a2, a3, a4) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5) : MockClass(a1, a2, a3, a4, a5) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, + a6, a7) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, + a2, a3, a4, a5, a6, a7, a8) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, + const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, + const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + virtual ~StrictMock() { + ::testing::Mock::UnregisterCallReaction( + internal::ImplicitCast_(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(StrictMock); +}; + +// The following specializations catch some (relatively more common) +// user errors of nesting nice and strict mocks. They do NOT catch +// all possible errors. + +// These specializations are declared but not defined, as NiceMock, +// NaggyMock, and StrictMock cannot be nested. + +template +class NiceMock >; +template +class NiceMock >; +template +class NiceMock >; + +template +class NaggyMock >; +template +class NaggyMock >; +template +class NaggyMock >; + +template +class StrictMock >; +template +class StrictMock >; +template +class StrictMock >; + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-nice-strict.h.pump b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-nice-strict.h.pump new file mode 100644 index 0000000000..3ee1ce7f30 --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-generated-nice-strict.h.pump @@ -0,0 +1,161 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-nice-strict.h. +$$ +$var n = 10 $$ The maximum arity we support. +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements class templates NiceMock, NaggyMock, and StrictMock. +// +// Given a mock class MockFoo that is created using Google Mock, +// NiceMock is a subclass of MockFoo that allows +// uninteresting calls (i.e. calls to mock methods that have no +// EXPECT_CALL specs), NaggyMock is a subclass of MockFoo +// that prints a warning when an uninteresting call occurs, and +// StrictMock is a subclass of MockFoo that treats all +// uninteresting calls as errors. +// +// Currently a mock is naggy by default, so MockFoo and +// NaggyMock behave like the same. However, we will soon +// switch the default behavior of mocks to be nice, as that in general +// leads to more maintainable tests. When that happens, MockFoo will +// stop behaving like NaggyMock and start behaving like +// NiceMock. +// +// NiceMock, NaggyMock, and StrictMock "inherit" the constructors of +// their respective base class, with up-to $n arguments. Therefore +// you can write NiceMock(5, "a") to construct a nice mock +// where MockFoo has a constructor that accepts (int, const char*), +// for example. +// +// A known limitation is that NiceMock, NaggyMock, +// and StrictMock only works for mock methods defined using +// the MOCK_METHOD* family of macros DIRECTLY in the MockFoo class. +// If a mock method is defined in a base class of MockFoo, the "nice" +// or "strict" modifier may not affect it, depending on the compiler. +// In particular, nesting NiceMock, NaggyMock, and StrictMock is NOT +// supported. +// +// Another known limitation is that the constructors of the base mock +// cannot have arguments passed by non-const reference, which are +// banned by the Google C++ style guide anyway. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-port.h" + +namespace testing { + +$range kind 0..2 +$for kind [[ + +$var clazz=[[$if kind==0 [[NiceMock]] + $elif kind==1 [[NaggyMock]] + $else [[StrictMock]]]] + +$var method=[[$if kind==0 [[AllowUninterestingCalls]] + $elif kind==1 [[WarnUninterestingCalls]] + $else [[FailUninterestingCalls]]]] + +template +class $clazz : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + $clazz() { + ::testing::Mock::$method( + internal::ImplicitCast_(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit $clazz(const A1& a1) : MockClass(a1) { + ::testing::Mock::$method( + internal::ImplicitCast_(this)); + } + +$range i 2..n +$for i [[ +$range j 1..i + template <$for j, [[typename A$j]]> + $clazz($for j, [[const A$j& a$j]]) : MockClass($for j, [[a$j]]) { + ::testing::Mock::$method( + internal::ImplicitCast_(this)); + } + + +]] + virtual ~$clazz() { + ::testing::Mock::UnregisterCallReaction( + internal::ImplicitCast_(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_($clazz); +}; + +]] + +// The following specializations catch some (relatively more common) +// user errors of nesting nice and strict mocks. They do NOT catch +// all possible errors. + +// These specializations are declared but not defined, as NiceMock, +// NaggyMock, and StrictMock cannot be nested. + +template +class NiceMock >; +template +class NiceMock >; +template +class NiceMock >; + +template +class NaggyMock >; +template +class NaggyMock >; +template +class NaggyMock >; + +template +class StrictMock >; +template +class StrictMock >; +template +class StrictMock >; + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ diff --git a/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-matchers.h b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-matchers.h new file mode 100644 index 0000000000..33b37a7a5d --- /dev/null +++ b/SampleApplications/IPCServerSampleApplication/ThirdParty/googletest-release-1.8.0/googlemock/include/gmock/gmock-matchers.h @@ -0,0 +1,4399 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used argument matchers. More +// matchers can be defined by the user implementing the +// MatcherInterface interface if necessary. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ + +#include +#include +#include +#include +#include // NOLINT +#include +#include +#include +#include + +#include "gmock/internal/gmock-internal-utils.h" +#include "gmock/internal/gmock-port.h" +#include "gtest/gtest.h" + +#if GTEST_HAS_STD_INITIALIZER_LIST_ +# include // NOLINT -- must be after gtest.h +#endif + +namespace testing { + +// To implement a matcher Foo for type T, define: +// 1. a class FooMatcherImpl that implements the +// MatcherInterface interface, and +// 2. a factory function that creates a Matcher object from a +// FooMatcherImpl*. +// +// The two-level delegation design makes it possible to allow a user +// to write "v" instead of "Eq(v)" where a Matcher is expected, which +// is impossible if we pass matchers by pointers. It also eases +// ownership management as Matcher objects can now be copied like +// plain values. + +// MatchResultListener is an abstract class. Its << operator can be +// used by a matcher to explain why a value matches or doesn't match. +// +// TODO(wan@google.com): add method +// bool InterestedInWhy(bool result) const; +// to indicate whether the listener is interested in why the match +// result is 'result'. +class MatchResultListener { + public: + // Creates a listener object with the given underlying ostream. The + // listener does not own the ostream, and does not dereference it + // in the constructor or destructor. + explicit MatchResultListener(::std::ostream* os) : stream_(os) {} + virtual ~MatchResultListener() = 0; // Makes this class abstract. + + // Streams x to the underlying ostream; does nothing if the ostream + // is NULL. + template + MatchResultListener& operator<<(const T& x) { + if (stream_ != NULL) + *stream_ << x; + return *this; + } + + // Returns the underlying ostream. + ::std::ostream* stream() { return stream_; } + + // Returns true iff the listener is interested in an explanation of + // the match result. A matcher's MatchAndExplain() method can use + // this information to avoid generating the explanation when no one + // intends to hear it. + bool IsInterested() const { return stream_ != NULL; } + + private: + ::std::ostream* const stream_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(MatchResultListener); +}; + +inline MatchResultListener::~MatchResultListener() { +} + +// An instance of a subclass of this knows how to describe itself as a +// matcher. +class MatcherDescriberInterface { + public: + virtual ~MatcherDescriberInterface() {} + + // Describes this matcher to an ostream. The function should print + // a verb phrase that describes the property a value matching this + // matcher should have. The subject of the verb phrase is the value + // being matched. For example, the DescribeTo() method of the Gt(7) + // matcher prints "is greater than 7". + virtual void DescribeTo(::std::ostream* os) const = 0; + + // Describes the negation of this matcher to an ostream. For + // example, if the description of this matcher is "is greater than + // 7", the negated description could be "is not greater than 7". + // You are not required to override this when implementing + // MatcherInterface, but it is highly advised so that your matcher + // can produce good error messages. + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "not ("; + DescribeTo(os); + *os << ")"; + } +}; + +// The implementation of a matcher. +template +class MatcherInterface : public MatcherDescriberInterface { + public: + // Returns true iff the matcher matches x; also explains the match + // result to 'listener' if necessary (see the next paragraph), in + // the form of a non-restrictive relative clause ("which ...", + // "whose ...", etc) that describes x. For example, the + // MatchAndExplain() method of the Pointee(...) matcher should + // generate an explanation like "which points to ...". + // + // Implementations of MatchAndExplain() should add an explanation of + // the match result *if and only if* they can provide additional + // information that's not already present (or not obvious) in the + // print-out of x and the matcher's description. Whether the match + // succeeds is not a factor in deciding whether an explanation is + // needed, as sometimes the caller needs to print a failure message + // when the match succeeds (e.g. when the matcher is used inside + // Not()). + // + // For example, a "has at least 10 elements" matcher should explain + // what the actual element count is, regardless of the match result, + // as it is useful information to the reader; on the other hand, an + // "is empty" matcher probably only needs to explain what the actual + // size is when the match fails, as it's redundant to say that the + // size is 0 when the value is already known to be empty. + // + // You should override this method when defining a new matcher. + // + // It's the responsibility of the caller (Google Mock) to guarantee + // that 'listener' is not NULL. This helps to simplify a matcher's + // implementation when it doesn't care about the performance, as it + // can talk to 'listener' without checking its validity first. + // However, in order to implement dummy listeners efficiently, + // listener->stream() may be NULL. + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0; + + // Inherits these methods from MatcherDescriberInterface: + // virtual void DescribeTo(::std::ostream* os) const = 0; + // virtual void DescribeNegationTo(::std::ostream* os) const; +}; + +// A match result listener that stores the explanation in a string. +class StringMatchResultListener : public MatchResultListener { + public: + StringMatchResultListener() : MatchResultListener(&ss_) {} + + // Returns the explanation accumulated so far. + internal::string str() const { return ss_.str(); } + + // Clears the explanation accumulated so far. + void Clear() { ss_.str(""); } + + private: + ::std::stringstream ss_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(StringMatchResultListener); +}; + +namespace internal { + +struct AnyEq { + template + bool operator()(const A& a, const B& b) const { return a == b; } +}; +struct AnyNe { + template + bool operator()(const A& a, const B& b) const { return a != b; } +}; +struct AnyLt { + template + bool operator()(const A& a, const B& b) const { return a < b; } +}; +struct AnyGt { + template + bool operator()(const A& a, const B& b) const { return a > b; } +}; +struct AnyLe { + template + bool operator()(const A& a, const B& b) const { return a <= b; } +}; +struct AnyGe { + template + bool operator()(const A& a, const B& b) const { return a >= b; } +}; + +// A match result listener that ignores the explanation. +class DummyMatchResultListener : public MatchResultListener { + public: + DummyMatchResultListener() : MatchResultListener(NULL) {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(DummyMatchResultListener); +}; + +// A match result listener that forwards the explanation to a given +// ostream. The difference between this and MatchResultListener is +// that the former is concrete. +class StreamMatchResultListener : public MatchResultListener { + public: + explicit StreamMatchResultListener(::std::ostream* os) + : MatchResultListener(os) {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamMatchResultListener); +}; + +// An internal class for implementing Matcher, which will derive +// from it. We put functionalities common to all Matcher +// specializations here to avoid code duplication. +template +class MatcherBase { + public: + // Returns true iff the matcher matches x; also explains the match + // result to 'listener'. + bool MatchAndExplain(T x, MatchResultListener* listener) const { + return impl_->MatchAndExplain(x, listener); + } + + // Returns true iff this matcher matches x. + bool Matches(T x) const { + DummyMatchResultListener dummy; + return MatchAndExplain(x, &dummy); + } + + // Describes this matcher to an ostream. + void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } + + // Describes the negation of this matcher to an ostream. + void DescribeNegationTo(::std::ostream* os) const { + impl_->DescribeNegationTo(os); + } + + // Explains why x matches, or doesn't match, the matcher. + void ExplainMatchResultTo(T x, ::std::ostream* os) const { + StreamMatchResultListener listener(os); + MatchAndExplain(x, &listener); + } + + // Returns the describer for this matcher object; retains ownership + // of the describer, which is only guaranteed to be alive when + // this matcher object is alive. + const MatcherDescriberInterface* GetDescriber() const { + return impl_.get(); + } + + protected: + MatcherBase() {} + + // Constructs a matcher from its implementation. + explicit MatcherBase(const MatcherInterface* impl) + : impl_(impl) {} + + virtual ~MatcherBase() {} + + private: + // shared_ptr (util/gtl/shared_ptr.h) and linked_ptr have similar + // interfaces. The former dynamically allocates a chunk of memory + // to hold the reference count, while the latter tracks all + // references using a circular linked list without allocating + // memory. It has been observed that linked_ptr performs better in + // typical scenarios. However, shared_ptr can out-perform + // linked_ptr when there are many more uses of the copy constructor + // than the default constructor. + // + // If performance becomes a problem, we should see if using + // shared_ptr helps. + ::testing::internal::linked_ptr > impl_; +}; + +} // namespace internal + +// A Matcher is a copyable and IMMUTABLE (except by assignment) +// object that can check whether a value of type T matches. The +// implementation of Matcher is just a linked_ptr to const +// MatcherInterface, so copying is fairly cheap. Don't inherit +// from Matcher! +template +class Matcher : public internal::MatcherBase { + public: + // Constructs a null matcher. Needed for storing Matcher objects in STL + // containers. A default-constructed matcher is not yet initialized. You + // cannot use it until a valid value has been assigned to it. + explicit Matcher() {} // NOLINT + + // Constructs a matcher from its implementation. + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Implicit constructor here allows people to write + // EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes + Matcher(T value); // NOLINT +}; + +// The following two specializations allow the user to write str +// instead of Eq(str) and "foo" instead of Eq("foo") when a string +// matcher is expected. +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +#if GTEST_HAS_STRING_PIECE_ +// The following two specializations allow the user to write str +// instead of Eq(str) and "foo" instead of Eq("foo") when a StringPiece +// matcher is expected. +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT + + // Allows the user to pass StringPieces directly. + Matcher(StringPiece s); // NOLINT +}; + +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT + + // Allows the user to pass StringPieces directly. + Matcher(StringPiece s); // NOLINT +}; +#endif // GTEST_HAS_STRING_PIECE_ + +// The PolymorphicMatcher class template makes it easy to implement a +// polymorphic matcher (i.e. a matcher that can match values of more +// than one type, e.g. Eq(n) and NotNull()). +// +// To define a polymorphic matcher, a user should provide an Impl +// class that has a DescribeTo() method and a DescribeNegationTo() +// method, and define a member function (or member function template) +// +// bool MatchAndExplain(const Value& value, +// MatchResultListener* listener) const; +// +// See the definition of NotNull() for a complete example. +template +class PolymorphicMatcher { + public: + explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {} + + // Returns a mutable reference to the underlying matcher + // implementation object. + Impl& mutable_impl() { return impl_; } + + // Returns an immutable reference to the underlying matcher + // implementation object. + const Impl& impl() const { return impl_; } + + template + operator Matcher() const { + return Matcher(new MonomorphicImpl(impl_)); + } + + private: + template + class MonomorphicImpl : public MatcherInterface { + public: + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + virtual void DescribeTo(::std::ostream* os) const { + impl_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + impl_.DescribeNegationTo(os); + } + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + return impl_.MatchAndExplain(x, listener); + } + + private: + const Impl impl_; + + GTEST_DISALLOW_ASSIGN_(MonomorphicImpl); + }; + + Impl impl_; + + GTEST_DISALLOW_ASSIGN_(PolymorphicMatcher); +}; + +// Creates a matcher from its implementation. This is easier to use +// than the Matcher constructor as it doesn't require you to +// explicitly write the template argument, e.g. +// +// MakeMatcher(foo); +// vs +// Matcher(foo); +template +inline Matcher MakeMatcher(const MatcherInterface* impl) { + return Matcher(impl); +} + +// Creates a polymorphic matcher from its implementation. This is +// easier to use than the PolymorphicMatcher constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicMatcher(foo); +// vs +// PolymorphicMatcher(foo); +template +inline PolymorphicMatcher MakePolymorphicMatcher(const Impl& impl) { + return PolymorphicMatcher(impl); +} + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// The MatcherCastImpl class template is a helper for implementing +// MatcherCast(). We need this helper in order to partially +// specialize the implementation of MatcherCast() (C++ allows +// class/struct templates to be partially specialized, but not +// function templates.). + +// This general version is used when MatcherCast()'s argument is a +// polymorphic matcher (i.e. something that can be converted to a +// Matcher but is not one yet; for example, Eq(value)) or a value (for +// example, "hello"). +template +class MatcherCastImpl { + public: + static Matcher Cast(const M& polymorphic_matcher_or_value) { + // M can be a polymorhic matcher, in which case we want to use + // its conversion operator to create Matcher. Or it can be a value + // that should be passed to the Matcher's constructor. + // + // We can't call Matcher(polymorphic_matcher_or_value) when M is a + // polymorphic matcher because it'll be ambiguous if T has an implicit + // constructor from M (this usually happens when T has an implicit + // constructor from any type). + // + // It won't work to unconditionally implict_cast + // polymorphic_matcher_or_value to Matcher because it won't trigger + // a user-defined conversion from M to T if one exists (assuming M is + // a value). + return CastImpl( + polymorphic_matcher_or_value, + BooleanConstant< + internal::ImplicitlyConvertible >::value>()); + } + + private: + static Matcher CastImpl(const M& value, BooleanConstant) { + // M can't be implicitly converted to Matcher, so M isn't a polymorphic + // matcher. It must be a value then. Use direct initialization to create + // a matcher. + return Matcher(ImplicitCast_(value)); + } + + static Matcher CastImpl(const M& polymorphic_matcher_or_value, + BooleanConstant) { + // M is implicitly convertible to Matcher, which means that either + // M is a polymorhpic matcher or Matcher has an implicit constructor + // from M. In both cases using the implicit conversion will produce a + // matcher. + // + // Even if T has an implicit constructor from M, it won't be called because + // creating Matcher would require a chain of two user-defined conversions + // (first to create T from M and then to create Matcher from T). + return polymorphic_matcher_or_value; + } +}; + +// This more specialized version is used when MatcherCast()'s argument +// is already a Matcher. This only compiles when type T can be +// statically converted to type U. +template +class MatcherCastImpl > { + public: + static Matcher Cast(const Matcher& source_matcher) { + return Matcher(new Impl(source_matcher)); + } + + private: + class Impl : public MatcherInterface { + public: + explicit Impl(const Matcher& source_matcher) + : source_matcher_(source_matcher) {} + + // We delegate the matching logic to the source matcher. + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + return source_matcher_.MatchAndExplain(static_cast(x), listener); + } + + virtual void DescribeTo(::std::ostream* os) const { + source_matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + source_matcher_.DescribeNegationTo(os); + } + + private: + const Matcher source_matcher_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; +}; + +// This even more specialized version is used for efficiently casting +// a matcher to its own type. +template +class MatcherCastImpl > { + public: + static Matcher Cast(const Matcher& matcher) { return matcher; } +}; + +} // namespace internal + +// In order to be safe and clear, casting between different matcher +// types is done explicitly via MatcherCast(m), which takes a +// matcher m and returns a Matcher. It compiles only when T can be +// statically converted to the argument type of m. +template +inline Matcher MatcherCast(const M& matcher) { + return internal::MatcherCastImpl::Cast(matcher); +} + +// Implements SafeMatcherCast(). +// +// We use an intermediate class to do the actual safe casting as Nokia's +// Symbian compiler cannot decide between +// template ... (M) and +// template ... (const Matcher&) +// for function templates but can for member function templates. +template +class SafeMatcherCastImpl { + public: + // This overload handles polymorphic matchers and values only since + // monomorphic matchers are handled by the next one. + template + static inline Matcher Cast(const M& polymorphic_matcher_or_value) { + return internal::MatcherCastImpl::Cast(polymorphic_matcher_or_value); + } + + // This overload handles monomorphic matchers. + // + // In general, if type T can be implicitly converted to type U, we can + // safely convert a Matcher to a Matcher (i.e. Matcher is + // contravariant): just keep a copy of the original Matcher, convert the + // argument from type T to U, and then pass it to the underlying Matcher. + // The only exception is when U is a reference and T is not, as the + // underlying Matcher may be interested in the argument's address, which + // is not preserved in the conversion from T to U. + template + static inline Matcher Cast(const Matcher& matcher) { + // Enforce that T can be implicitly converted to U. + GTEST_COMPILE_ASSERT_((internal::ImplicitlyConvertible::value), + T_must_be_implicitly_convertible_to_U); + // Enforce that we are not converting a non-reference type T to a reference + // type U. + GTEST_COMPILE_ASSERT_( + internal::is_reference::value || !internal::is_reference::value, + cannot_convert_non_referentce_arg_to_reference); + // In case both T and U are arithmetic types, enforce that the + // conversion is not lossy. + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(T) RawT; + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(U) RawU; + const bool kTIsOther = GMOCK_KIND_OF_(RawT) == internal::kOther; + const bool kUIsOther = GMOCK_KIND_OF_(RawU) == internal::kOther; + GTEST_COMPILE_ASSERT_( + kTIsOther || kUIsOther || + (internal::LosslessArithmeticConvertible::value), + conversion_of_arithmetic_types_must_be_lossless); + return MatcherCast(matcher); + } +}; + +template +inline Matcher SafeMatcherCast(const M& polymorphic_matcher) { + return SafeMatcherCastImpl::Cast(polymorphic_matcher); +} + +// A() returns a matcher that matches any value of type T. +template +Matcher A(); + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// If the explanation is not empty, prints it to the ostream. +inline void PrintIfNotEmpty(const internal::string& explanation, + ::std::ostream* os) { + if (explanation != "" && os != NULL) { + *os << ", " << explanation; + } +} + +// Returns true if the given type name is easy to read by a human. +// This is used to decide whether printing the type of a value might +// be helpful. +inline bool IsReadableTypeName(const string& type_name) { + // We consider a type name readable if it's short or doesn't contain + // a template or function type. + return (type_name.length() <= 20 || + type_name.find_first_of("<(") == string::npos); +} + +// Matches the value against the given matcher, prints the value and explains +// the match result to the listener. Returns the match result. +// 'listener' must not be NULL. +// Value cannot be passed by const reference, because some matchers take a +// non-const argument. +template +bool MatchPrintAndExplain(Value& value, const Matcher& matcher, + MatchResultListener* listener) { + if (!listener->IsInterested()) { + // If the listener is not interested, we do not need to construct the + // inner explanation. + return matcher.Matches(value); + } + + StringMatchResultListener inner_listener; + const bool match = matcher.MatchAndExplain(value, &inner_listener); + + UniversalPrint(value, listener->stream()); +#if GTEST_HAS_RTTI + const string& type_name = GetTypeName(); + if (IsReadableTypeName(type_name)) + *listener->stream() << " (of type " << type_name << ")"; +#endif + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + + return match; +} + +// An internal helper class for doing compile-time loop on a tuple's +// fields. +template +class TuplePrefix { + public: + // TuplePrefix::Matches(matcher_tuple, value_tuple) returns true + // iff the first N fields of matcher_tuple matches the first N + // fields of value_tuple, respectively. + template + static bool Matches(const MatcherTuple& matcher_tuple, + const ValueTuple& value_tuple) { + return TuplePrefix::Matches(matcher_tuple, value_tuple) + && get(matcher_tuple).Matches(get(value_tuple)); + } + + // TuplePrefix::ExplainMatchFailuresTo(matchers, values, os) + // describes failures in matching the first N fields of matchers + // against the first N fields of values. If there is no failure, + // nothing will be streamed to os. + template + static void ExplainMatchFailuresTo(const MatcherTuple& matchers, + const ValueTuple& values, + ::std::ostream* os) { + // First, describes failures in the first N - 1 fields. + TuplePrefix::ExplainMatchFailuresTo(matchers, values, os); + + // Then describes the failure (if any) in the (N - 1)-th (0-based) + // field. + typename tuple_element::type matcher = + get(matchers); + typedef typename tuple_element::type Value; + Value value = get(values); + StringMatchResultListener listener; + if (!matcher.MatchAndExplain(value, &listener)) { + // TODO(wan): include in the message the name of the parameter + // as used in MOCK_METHOD*() when possible. + *os << " Expected arg #" << N - 1 << ": "; + get(matchers).DescribeTo(os); + *os << "\n Actual: "; + // We remove the reference in type Value to prevent the + // universal printer from printing the address of value, which + // isn't interesting to the user most of the time. The + // matcher's MatchAndExplain() method handles the case when + // the address is interesting. + internal::UniversalPrint(value, os); + PrintIfNotEmpty(listener.str(), os); + *os << "\n"; + } + } +}; + +// The base case. +template <> +class TuplePrefix<0> { + public: + template + static bool Matches(const MatcherTuple& /* matcher_tuple */, + const ValueTuple& /* value_tuple */) { + return true; + } + + template + static void ExplainMatchFailuresTo(const MatcherTuple& /* matchers */, + const ValueTuple& /* values */, + ::std::ostream* /* os */) {} +}; + +// TupleMatches(matcher_tuple, value_tuple) returns true iff all +// matchers in matcher_tuple match the corresponding fields in +// value_tuple. It is a compiler error if matcher_tuple and +// value_tuple have different number of fields or incompatible field +// types. +template +bool TupleMatches(const MatcherTuple& matcher_tuple, + const ValueTuple& value_tuple) { + // Makes sure that matcher_tuple and value_tuple have the same + // number of fields. + GTEST_COMPILE_ASSERT_(tuple_size::value == + tuple_size::value, + matcher_and_value_have_different_numbers_of_fields); + return TuplePrefix::value>:: + Matches(matcher_tuple, value_tuple); +} + +// Describes failures in matching matchers against values. If there +// is no failure, nothing will be streamed to os. +template +void ExplainMatchFailureTupleTo(const MatcherTuple& matchers, + const ValueTuple& values, + ::std::ostream* os) { + TuplePrefix::value>::ExplainMatchFailuresTo( + matchers, values, os); +} + +// TransformTupleValues and its helper. +// +// TransformTupleValuesHelper hides the internal machinery that +// TransformTupleValues uses to implement a tuple traversal. +template +class TransformTupleValuesHelper { + private: + typedef ::testing::tuple_size TupleSize; + + public: + // For each member of tuple 't', taken in order, evaluates '*out++ = f(t)'. + // Returns the final value of 'out' in case the caller needs it. + static OutIter Run(Func f, const Tuple& t, OutIter out) { + return IterateOverTuple()(f, t, out); + } + + private: + template + struct IterateOverTuple { + OutIter operator() (Func f, const Tup& t, OutIter out) const { + *out++ = f(::testing::get(t)); + return IterateOverTuple()(f, t, out); + } + }; + template + struct IterateOverTuple { + OutIter operator() (Func /* f */, const Tup& /* t */, OutIter out) const { + return out; + } + }; +}; + +// Successively invokes 'f(element)' on each element of the tuple 't', +// appending each result to the 'out' iterator. Returns the final value +// of 'out'. +template +OutIter TransformTupleValues(Func f, const Tuple& t, OutIter out) { + return TransformTupleValuesHelper::Run(f, t, out); +} + +// Implements A(). +template +class AnyMatcherImpl : public MatcherInterface { + public: + virtual bool MatchAndExplain( + T /* x */, MatchResultListener* /* listener */) const { return true; } + virtual void DescribeTo(::std::ostream* os) const { *os << "is anything"; } + virtual void DescribeNegationTo(::std::ostream* os) const { + // This is mostly for completeness' safe, as it's not very useful + // to write Not(A()). However we cannot completely rule out + // such a possibility, and it doesn't hurt to be prepared. + *os << "never matches"; + } +}; + +// Implements _, a matcher that matches any value of any +// type. This is a polymorphic matcher, so we need a template type +// conversion operator to make it appearing as a Matcher for any +// type T. +class AnythingMatcher { + public: + template + operator Matcher() const { return A(); } +}; + +// Implements a matcher that compares a given value with a +// pre-supplied value using one of the ==, <=, <, etc, operators. The +// two values being compared don't have to have the same type. +// +// The matcher defined here is polymorphic (for example, Eq(5) can be +// used to match an int, a short, a double, etc). Therefore we use +// a template type conversion operator in the implementation. +// +// The following template definition assumes that the Rhs parameter is +// a "bare" type (i.e. neither 'const T' nor 'T&'). +template +class ComparisonBase { + public: + explicit ComparisonBase(const Rhs& rhs) : rhs_(rhs) {} + template + operator Matcher() const { + return MakeMatcher(new Impl(rhs_)); + } + + private: + template + class Impl : public MatcherInterface { + public: + explicit Impl(const Rhs& rhs) : rhs_(rhs) {} + virtual bool MatchAndExplain( + Lhs lhs, MatchResultListener* /* listener */) const { + return Op()(lhs, rhs_); + } + virtual void DescribeTo(::std::ostream* os) const { + *os << D::Desc() << " "; + UniversalPrint(rhs_, os); + } + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << D::NegatedDesc() << " "; + UniversalPrint(rhs_, os); + } + private: + Rhs rhs_; + GTEST_DISALLOW_ASSIGN_(Impl); + }; + Rhs rhs_; + GTEST_DISALLOW_ASSIGN_(ComparisonBase); +}; + +template +class EqMatcher : public ComparisonBase, Rhs, AnyEq> { + public: + explicit EqMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, AnyEq>(rhs) { } + static const char* Desc() { return "is equal to"; } + static const char* NegatedDesc() { return "isn't equal to"; } +}; +template +class NeMatcher : public ComparisonBase, Rhs, AnyNe> { + public: + explicit NeMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, AnyNe>(rhs) { } + static const char* Desc() { return "isn't equal to"; } + static const char* NegatedDesc() { return "is equal to"; } +}; +template +class LtMatcher : public ComparisonBase, Rhs, AnyLt> { + public: + explicit LtMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, AnyLt>(rhs) { } + static const char* Desc() { return "is <"; } + static const char* NegatedDesc() { return "isn't <"; } +}; +template +class GtMatcher : public ComparisonBase, Rhs, AnyGt> { + public: + explicit GtMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, AnyGt>(rhs) { } + static const char* Desc() { return "is >"; } + static const char* NegatedDesc() { return "isn't >"; } +}; +template +class LeMatcher : public ComparisonBase, Rhs, AnyLe> { + public: + explicit LeMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, AnyLe>(rhs) { } + static const char* Desc() { return "is <="; } + static const char* NegatedDesc() { return "isn't <="; } +}; +template +class GeMatcher : public ComparisonBase, Rhs, AnyGe> { + public: + explicit GeMatcher(const Rhs& rhs) + : ComparisonBase, Rhs, AnyGe>(rhs) { } + static const char* Desc() { return "is >="; } + static const char* NegatedDesc() { return "isn't >="; } +}; + +// Implements the polymorphic IsNull() matcher, which matches any raw or smart +// pointer that is NULL. +class IsNullMatcher { + public: + template + bool MatchAndExplain(const Pointer& p, + MatchResultListener* /* listener */) const { +#if GTEST_LANG_CXX11 + return p == nullptr; +#else // GTEST_LANG_CXX11 + return GetRawPointer(p) == NULL; +#endif // GTEST_LANG_CXX11 + } + + void DescribeTo(::std::ostream* os) const { *os << "is NULL"; } + void DescribeNegationTo(::std::ostream* os) const { + *os << "isn't NULL"; + } +}; + +// Implements the polymorphic NotNull() matcher, which matches any raw or smart +// pointer that is not NULL. +class NotNullMatcher { + public: + template + bool MatchAndExplain(const Pointer& p, + MatchResultListener* /* listener */) const { +#if GTEST_LANG_CXX11 + return p != nullptr; +#else // GTEST_LANG_CXX11 + return GetRawPointer(p) != NULL; +#endif // GTEST_LANG_CXX11 + } + + void DescribeTo(::std::ostream* os) const { *os << "isn't NULL"; } + void DescribeNegationTo(::std::ostream* os) const { + *os << "is NULL"; + } +}; + +// Ref(variable) matches any argument that is a reference to +// 'variable'. This matcher is polymorphic as it can match any +// super type of the type of 'variable'. +// +// The RefMatcher template class implements Ref(variable). It can +// only be instantiated with a reference type. This prevents a user +// from mistakenly using Ref(x) to match a non-reference function +// argument. For example, the following will righteously cause a +// compiler error: +// +// int n; +// Matcher m1 = Ref(n); // This won't compile. +// Matcher m2 = Ref(n); // This will compile. +template +class RefMatcher; + +template +class RefMatcher { + // Google Mock is a generic framework and thus needs to support + // mocking any function types, including those that take non-const + // reference arguments. Therefore the template parameter T (and + // Super below) can be instantiated to either a const type or a + // non-const type. + public: + // RefMatcher() takes a T& instead of const T&, as we want the + // compiler to catch using Ref(const_value) as a matcher for a + // non-const reference. + explicit RefMatcher(T& x) : object_(x) {} // NOLINT + + template + operator Matcher() const { + // By passing object_ (type T&) to Impl(), which expects a Super&, + // we make sure that Super is a super type of T. In particular, + // this catches using Ref(const_value) as a matcher for a + // non-const reference, as you cannot implicitly convert a const + // reference to a non-const reference. + return MakeMatcher(new Impl(object_)); + } + + private: + template + class Impl : public MatcherInterface { + public: + explicit Impl(Super& x) : object_(x) {} // NOLINT + + // MatchAndExplain() takes a Super& (as opposed to const Super&) + // in order to match the interface MatcherInterface. + virtual bool MatchAndExplain( + Super& x, MatchResultListener* listener) const { + *listener << "which is located @" << static_cast(&x); + return &x == &object_; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "references the variable "; + UniversalPrinter::Print(object_, os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "does not reference the variable "; + UniversalPrinter::Print(object_, os); + } + + private: + const Super& object_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + T& object_; + + GTEST_DISALLOW_ASSIGN_(RefMatcher); +}; + +// Polymorphic helper functions for narrow and wide string matchers. +inline bool CaseInsensitiveCStringEquals(const char* lhs, const char* rhs) { + return String::CaseInsensitiveCStringEquals(lhs, rhs); +} + +inline bool CaseInsensitiveCStringEquals(const wchar_t* lhs, + const wchar_t* rhs) { + return String::CaseInsensitiveWideCStringEquals(lhs, rhs); +} + +// String comparison for narrow or wide strings that can have embedded NUL +// characters. +template +bool CaseInsensitiveStringEquals(const StringType& s1, + const StringType& s2) { + // Are the heads equal? + if (!CaseInsensitiveCStringEquals(s1.c_str(), s2.c_str())) { + return false; + } + + // Skip the equal heads. + const typename StringType::value_type nul = 0; + const size_t i1 = s1.find(nul), i2 = s2.find(nul); + + // Are we at the end of either s1 or s2? + if (i1 == StringType::npos || i2 == StringType::npos) { + return i1 == i2; + } + + // Are the tails equal? + return CaseInsensitiveStringEquals(s1.substr(i1 + 1), s2.substr(i2 + 1)); +} + +// String matchers. + +// Implements equality-based string matchers like StrEq, StrCaseNe, and etc. +template +class StrEqualityMatcher { + public: + StrEqualityMatcher(const StringType& str, bool expect_eq, + bool case_sensitive) + : string_(str), expect_eq_(expect_eq), case_sensitive_(case_sensitive) {} + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + if (s == NULL) { + return !expect_eq_; + } + return MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + const bool eq = case_sensitive_ ? s2 == string_ : + CaseInsensitiveStringEquals(s2, string_); + return expect_eq_ == eq; + } + + void DescribeTo(::std::ostream* os) const { + DescribeToHelper(expect_eq_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + DescribeToHelper(!expect_eq_, os); + } + + private: + void DescribeToHelper(bool expect_eq, ::std::ostream* os) const { + *os << (expect_eq ? "is " : "isn't "); + *os << "equal to "; + if (!case_sensitive_) { + *os << "(ignoring case) "; + } + UniversalPrint(string_, os); + } + + const StringType string_; + const bool expect_eq_; + const bool case_sensitive_; + + GTEST_DISALLOW_ASSIGN_(StrEqualityMatcher); +}; + +// Implements the polymorphic HasSubstr(substring) matcher, which +// can be used as a Matcher as long as T can be converted to a +// string. +template +class HasSubstrMatcher { + public: + explicit HasSubstrMatcher(const StringType& substring) + : substring_(substring) {} + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + return s2.find(substring_) != StringType::npos; + } + + // Describes what this matcher matches. + void DescribeTo(::std::ostream* os) const { + *os << "has substring "; + UniversalPrint(substring_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "has no substring "; + UniversalPrint(substring_, os); + } + + private: + const StringType substring_; + + GTEST_DISALLOW_ASSIGN_(HasSubstrMatcher); +}; + +// Implements the polymorphic StartsWith(substring) matcher, which +// can be used as a Matcher as long as T can be converted to a +// string. +template +class StartsWithMatcher { + public: + explicit StartsWithMatcher(const StringType& prefix) : prefix_(prefix) { + } + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + return s2.length() >= prefix_.length() && + s2.substr(0, prefix_.length()) == prefix_; + } + + void DescribeTo(::std::ostream* os) const { + *os << "starts with "; + UniversalPrint(prefix_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't start with "; + UniversalPrint(prefix_, os); + } + + private: + const StringType prefix_; + + GTEST_DISALLOW_ASSIGN_(StartsWithMatcher); +}; + +// Implements the polymorphic EndsWith(substring) matcher, which +// can be used as a Matcher as long as T can be converted to a +// string. +template +class EndsWithMatcher { + public: + explicit EndsWithMatcher(const StringType& suffix) : suffix_(suffix) {} + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + return s2.length() >= suffix_.length() && + s2.substr(s2.length() - suffix_.length()) == suffix_; + } + + void DescribeTo(::std::ostream* os) const { + *os << "ends with "; + UniversalPrint(suffix_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't end with "; + UniversalPrint(suffix_, os); + } + + private: + const StringType suffix_; + + GTEST_DISALLOW_ASSIGN_(EndsWithMatcher); +}; + +// Implements polymorphic matchers MatchesRegex(regex) and +// ContainsRegex(regex), which can be used as a Matcher as long as +// T can be converted to a string. +class MatchesRegexMatcher { + public: + MatchesRegexMatcher(const RE* regex, bool full_match) + : regex_(regex), full_match_(full_match) {} + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(internal::string(s), listener); + } + + // Matches anything that can convert to internal::string. + // + // This is a template, not just a plain function with const internal::string&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const internal::string& s2(s); + return full_match_ ? RE::FullMatch(s2, *regex_) : + RE::PartialMatch(s2, *regex_); + } + + void DescribeTo(::std::ostream* os) const { + *os << (full_match_ ? "matches" : "contains") + << " regular expression "; + UniversalPrinter::Print(regex_->pattern(), os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't " << (full_match_ ? "match" : "contain") + << " regular expression "; + UniversalPrinter::Print(regex_->pattern(), os); + } + + private: + const internal::linked_ptr regex_; + const bool full_match_; + + GTEST_DISALLOW_ASSIGN_(MatchesRegexMatcher); +}; + +// Implements a matcher that compares the two fields of a 2-tuple +// using one of the ==, <=, <, etc, operators. The two fields being +// compared don't have to have the same type. +// +// The matcher defined here is polymorphic (for example, Eq() can be +// used to match a tuple, a tuple, +// etc). Therefore we use a template type conversion operator in the +// implementation. +template +class PairMatchBase { + public: + template + operator Matcher< ::testing::tuple >() const { + return MakeMatcher(new Impl< ::testing::tuple >); + } + template + operator Matcher&>() const { + return MakeMatcher(new Impl&>); + } + + private: + static ::std::ostream& GetDesc(::std::ostream& os) { // NOLINT + return os << D::Desc(); + } + + template + class Impl : public MatcherInterface { + public: + virtual bool MatchAndExplain( + Tuple args, + MatchResultListener* /* listener */) const { + return Op()(::testing::get<0>(args), ::testing::get<1>(args)); + } + virtual void DescribeTo(::std::ostream* os) const { + *os << "are " << GetDesc; + } + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "aren't " << GetDesc; + } + }; +}; + +class Eq2Matcher : public PairMatchBase { + public: + static const char* Desc() { return "an equal pair"; } +}; +class Ne2Matcher : public PairMatchBase { + public: + static const char* Desc() { return "an unequal pair"; } +}; +class Lt2Matcher : public PairMatchBase { + public: + static const char* Desc() { return "a pair where the first < the second"; } +}; +class Gt2Matcher : public PairMatchBase { + public: + static const char* Desc() { return "a pair where the first > the second"; } +}; +class Le2Matcher : public PairMatchBase { + public: + static const char* Desc() { return "a pair where the first <= the second"; } +}; +class Ge2Matcher : public PairMatchBase { + public: + static const char* Desc() { return "a pair where the first >= the second"; } +}; + +// Implements the Not(...) matcher for a particular argument type T. +// We do not nest it inside the NotMatcher class template, as that +// will prevent different instantiations of NotMatcher from sharing +// the same NotMatcherImpl class. +template +class NotMatcherImpl : public MatcherInterface { + public: + explicit NotMatcherImpl(const Matcher& matcher) + : matcher_(matcher) {} + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + return !matcher_.MatchAndExplain(x, listener); + } + + virtual void DescribeTo(::std::ostream* os) const { + matcher_.DescribeNegationTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + matcher_.DescribeTo(os); + } + + private: + const Matcher matcher_; + + GTEST_DISALLOW_ASSIGN_(NotMatcherImpl); +}; + +// Implements the Not(m) matcher, which matches a value that doesn't +// match matcher m. +template +class NotMatcher { + public: + explicit NotMatcher(InnerMatcher matcher) : matcher_(matcher) {} + + // This template type conversion operator allows Not(m) to be used + // to match any type m can match. + template + operator Matcher() const { + return Matcher(new NotMatcherImpl(SafeMatcherCast(matcher_))); + } + + private: + InnerMatcher matcher_; + + GTEST_DISALLOW_ASSIGN_(NotMatcher); +}; + +// Implements the AllOf(m1, m2) matcher for a particular argument type +// T. We do not nest it inside the BothOfMatcher class template, as +// that will prevent different instantiations of BothOfMatcher from +// sharing the same BothOfMatcherImpl class. +template +class BothOfMatcherImpl : public MatcherInterface { + public: + BothOfMatcherImpl(const Matcher& matcher1, const Matcher& matcher2) + : matcher1_(matcher1), matcher2_(matcher2) {} + + virtual void DescribeTo(::std::ostream* os) const { + *os << "("; + matcher1_.DescribeTo(os); + *os << ") and ("; + matcher2_.DescribeTo(os); + *os << ")"; + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "("; + matcher1_.DescribeNegationTo(os); + *os << ") or ("; + matcher2_.DescribeNegationTo(os); + *os << ")"; + } + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + // If either matcher1_ or matcher2_ doesn't match x, we only need + // to explain why one of them fails. + StringMatchResultListener listener1; + if (!matcher1_.MatchAndExplain(x, &listener1)) { + *listener << listener1.str(); + return false; + } + + StringMatchResultListener listener2; + if (!matcher2_.MatchAndExplain(x, &listener2)) { + *listener << listener2.str(); + return false; + } + + // Otherwise we need to explain why *both* of them match. + const internal::string s1 = listener1.str(); + const internal::string s2 = listener2.str(); + + if (s1 == "") { + *listener << s2; + } else { + *listener << s1; + if (s2 != "") { + *listener << ", and " << s2; + } + } + return true; + } + + private: + const Matcher matcher1_; + const Matcher matcher2_; + + GTEST_DISALLOW_ASSIGN_(BothOfMatcherImpl); +}; + +#if GTEST_LANG_CXX11 +// MatcherList provides mechanisms for storing a variable number of matchers in +// a list structure (ListType) and creating a combining matcher from such a +// list. +// The template is defined recursively using the following template paramters: +// * kSize is the length of the MatcherList. +// * Head is the type of the first matcher of the list. +// * Tail denotes the types of the remaining matchers of the list. +template +struct MatcherList { + typedef MatcherList MatcherListTail; + typedef ::std::pair ListType; + + // BuildList stores variadic type values in a nested pair structure. + // Example: + // MatcherList<3, int, string, float>::BuildList(5, "foo", 2.0) will return + // the corresponding result of type pair>. + static ListType BuildList(const Head& matcher, const Tail&... tail) { + return ListType(matcher, MatcherListTail::BuildList(tail...)); + } + + // CreateMatcher creates a Matcher from a given list of matchers (built + // by BuildList()). CombiningMatcher is used to combine the matchers of the + // list. CombiningMatcher must implement MatcherInterface and have a + // constructor taking two Matchers as input. + template class CombiningMatcher> + static Matcher CreateMatcher(const ListType& matchers) { + return Matcher(new CombiningMatcher( + SafeMatcherCast(matchers.first), + MatcherListTail::template CreateMatcher( + matchers.second))); + } +}; + +// The following defines the base case for the recursive definition of +// MatcherList. +template +struct MatcherList<2, Matcher1, Matcher2> { + typedef ::std::pair ListType; + + static ListType BuildList(const Matcher1& matcher1, + const Matcher2& matcher2) { + return ::std::pair(matcher1, matcher2); + } + + template class CombiningMatcher> + static Matcher CreateMatcher(const ListType& matchers) { + return Matcher(new CombiningMatcher( + SafeMatcherCast(matchers.first), + SafeMatcherCast(matchers.second))); + } +}; + +// VariadicMatcher is used for the variadic implementation of +// AllOf(m_1, m_2, ...) and AnyOf(m_1, m_2, ...). +// CombiningMatcher is used to recursively combine the provided matchers +// (of type Args...). +template