From c6201aad83b146d729e75cfad911e7932d257feb Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 17 Jun 2022 13:35:39 +0100 Subject: [PATCH 01/60] Merge branch 'master' of https://github.com/alexa/avs-device-sdk into feature/merge_1.26.0 # Conflicts: # KWD/CMakeLists.txt # KWD/KWDProvider/src/CMakeLists.txt # KWD/KWDProvider/src/KeywordDetectorProvider.cpp # SampleApp/src/CMakeLists.txt # SampleApp/src/SampleApplication.cpp # SampleApp/src/main.cpp # shared/KWD/acsdkKWDImplementations/XMOS/CMakeLists.txt # shared/KWD/acsdkKWDImplementations/XMOS/GPIO/CMakeLists.txt # shared/KWD/acsdkKWDImplementations/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h # shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/CMakeLists.txt # shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/GPIOKeywordDetector.cpp # shared/KWD/acsdkKWDImplementations/XMOS/HID/CMakeLists.txt # shared/KWD/acsdkKWDImplementations/XMOS/HID/include/HID/HIDKeywordDetector.h # shared/KWD/acsdkKWDImplementations/XMOS/HID/src/CMakeLists.txt # shared/KWD/acsdkKWDImplementations/XMOS/HID/src/HIDKeywordDetector.cpp # shared/KWD/acsdkKWDImplementations/XMOS/include/XMOS/XMOSKeywordDetector.h # shared/KWD/acsdkKWDImplementations/XMOS/src/CMakeLists.txt # shared/KWD/acsdkKWDImplementations/XMOS/src/XMOSKeywordDetector.cpp # tools/Install/pi.sh # tools/Install/setup.sh --- 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 + .../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 +- .../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 +- .../src/KeywordDetectorProvider.cpp | 60 - KWD/Sensory/src/CMakeLists.txt | 13 - KWD/Sensory/test/CMakeLists.txt | 5 - KWD/XMOS/GPIO/CMakeLists.txt | 6 - KWD/XMOS/HID/CMakeLists.txt | 6 - 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 | 70 +- 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 | 44 +- 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 + .../XMOS/CMakeLists.txt | 0 .../XMOS/GPIO/CMakeLists.txt | 0 .../GPIO/include/GPIO/GPIOKeywordDetector.h | 0 .../XMOS/GPIO/src/CMakeLists.txt | 0 .../XMOS/GPIO/src/GPIOKeywordDetector.cpp | 0 .../XMOS/HID/CMakeLists.txt | 0 .../XMOS/HID/include/HID/HIDKeywordDetector.h | 0 .../XMOS/HID/src/CMakeLists.txt | 0 .../XMOS/HID/src/HIDKeywordDetector.cpp | 0 .../XMOS/include/XMOS/XMOSKeywordDetector.h | 0 .../XMOS/src/CMakeLists.txt | 0 .../XMOS/src/XMOSKeywordDetector.cpp | 0 .../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 | 44 +- tools/Install/setup.sh | 112 +- tools/Testing.cmake | 6 + 788 files changed, 53923 insertions(+), 4109 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/Sensory/src/CMakeLists.txt delete mode 100644 KWD/Sensory/test/CMakeLists.txt delete mode 100644 KWD/XMOS/GPIO/CMakeLists.txt delete mode 100644 KWD/XMOS/HID/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 => shared/KWD/acsdkKWDImplementations}/XMOS/CMakeLists.txt (100%) create mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/GPIO/CMakeLists.txt rename {KWD => shared/KWD/acsdkKWDImplementations}/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/XMOS/GPIO/src/CMakeLists.txt (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/XMOS/GPIO/src/GPIOKeywordDetector.cpp (100%) create mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/HID/CMakeLists.txt rename {KWD => shared/KWD/acsdkKWDImplementations}/XMOS/HID/include/HID/HIDKeywordDetector.h (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/XMOS/HID/src/CMakeLists.txt (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/XMOS/HID/src/HIDKeywordDetector.cpp (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/XMOS/include/XMOS/XMOSKeywordDetector.h (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/XMOS/src/CMakeLists.txt (100%) rename {KWD => shared/KWD/acsdkKWDImplementations}/XMOS/src/XMOSKeywordDetector.cpp (100%) rename {KWD/include/KWD => shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations}/AbstractKeywordDetector.h (80%) 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 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/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/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/KWDProvider/src/KeywordDetectorProvider.cpp b/KWD/KWDProvider/src/KeywordDetectorProvider.cpp index 243d0d0818..e69de29bb2 100644 --- a/KWD/KWDProvider/src/KeywordDetectorProvider.cpp +++ b/KWD/KWDProvider/src/KeywordDetectorProvider.cpp @@ -1,60 +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 -#elif KWD_GPIO -#include -#elif KWD_HID -#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", - std::chrono::milliseconds(10)); - -#elif defined(KWD_GPIO) - return kwd::GPIOKeywordDetector::create( - stream, - audioFormat, - keyWordObservers, - keyWordDetectorStateObservers); -#elif defined(KWD_HID) - return kwd::HIDKeywordDetector::create( - stream, - audioFormat, - keyWordObservers, - keyWordDetectorStateObservers); -#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/XMOS/GPIO/CMakeLists.txt b/KWD/XMOS/GPIO/CMakeLists.txt deleted file mode 100644 index 90f204835c..0000000000 --- a/KWD/XMOS/GPIO/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -project(GPIO LANGUAGES CXX) - -include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) - -add_subdirectory("src") diff --git a/KWD/XMOS/HID/CMakeLists.txt b/KWD/XMOS/HID/CMakeLists.txt deleted file mode 100644 index 5df9f258f7..0000000000 --- a/KWD/XMOS/HID/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -project(HID LANGUAGES CXX) - -include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) - -add_subdirectory("src") 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 0b2690c97e..ce072edce4 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. * @@ -164,16 +182,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); @@ -223,6 +239,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; @@ -286,7 +305,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 4e43a1da2b..d290dce1fd 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 571f47695f..b7e793fecc 100644 --- a/SampleApp/src/CMakeLists.txt +++ b/SampleApp/src/CMakeLists.txt @@ -63,11 +63,15 @@ if(XMOS_AVS_TESTS) add_definitions(-DXMOS_AVS_TESTS) 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). @@ -75,19 +79,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) @@ -120,6 +127,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) @@ -150,6 +161,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 3ecd123b6f..6803c5810a 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 @@ -677,7 +681,6 @@ 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); @@ -689,7 +692,7 @@ std::unique_ptr SampleApplication::create( PortAudioMicrophoneWrapper::setIsFileStream(m_isFileStream); InteractionManager::setIsFileStream(m_isFileStream); #endif - if (!clientApplication->initialize(consoleReader, configFiles, pathToInputFolder, logLevel, diagnostics)) { + if (!clientApplication->initialize(consoleReader, configFiles, logLevel, diagnostics)) { ACSDK_CRITICAL(LX("Failed to initialize SampleApplication")); return nullptr; } @@ -727,6 +730,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(); @@ -739,13 +749,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, @@ -773,7 +788,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; @@ -848,6 +862,8 @@ bool SampleApplication::initialize( * auto sampleAppComponent = acsdkSampleApplication::getComponent( * std::move(initParams), * m_shutdownRequiredList, + * m_cryptoFactory, + * m_keyStore, * myCustomAuthDelegate, * myCustomMetricRecorder, * myCustomLogger); @@ -864,8 +880,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!")); @@ -1068,8 +1088,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); @@ -1310,122 +1330,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; @@ -1434,18 +1435,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 @@ -1476,14 +1477,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; @@ -1513,41 +1514,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 @@ -1584,7 +1576,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 @@ -1621,14 +1613,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. @@ -1640,18 +1632,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!")); @@ -1666,7 +1662,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 39aa1dfade..3ab4347d4e 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; #ifdef SENSORY_OP_POINT int sensoryOpPoint = 5; @@ -71,12 +70,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"); @@ -86,40 +79,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]) + -#ifndef XMOS_AVS_TESTS - " [log_level] [op_point]"); -#else - " [log_level] [op_point] [XMOS_AVS_TESTS]"); -#endif - return SampleAppReturnCode::ERROR; - } else { - pathToKWDInputFolder = std::string(argv[2]); - if (4 <= argc) { - logLevel = std::string(argv[3]); - } -#ifdef SENSORY_OP_POINT - if (5 <= argc) { - sensoryOpPoint = atoi(argv[4]); - } -#endif -#ifdef XMOS_AVS_TESTS - if (6 <= argc) { - if (argv[5] == std::string("XMOS_AVS_TESTS")) { - isFileStream = true; - } - } -#endif - } -#else if (argc < 2) { ConsolePrinter::simplePrint( "USAGE: " + std::string(argv[0]) + " [log_level]"); @@ -128,7 +92,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])); @@ -159,7 +122,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 af44289afd..cbba5aa0f8 100644 --- a/KWD/Sensory/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -15,6 +15,7 @@ #include +#include #include #include "Sensory/SensoryKeywordDetector.h" @@ -166,6 +167,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, @@ -174,26 +176,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; } @@ -207,11 +239,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 f20aa76652..b542261660 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= # -DGPIO_KEY_WORD_DETECTOR=ON @@ -67,13 +67,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) @@ -90,6 +83,10 @@ 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() if(GPIO_KEY_WORD_DETECTOR) 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/XMOS/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/CMakeLists.txt similarity index 100% rename from KWD/XMOS/CMakeLists.txt rename to shared/KWD/acsdkKWDImplementations/XMOS/CMakeLists.txt diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/CMakeLists.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/KWD/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h b/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h similarity index 100% rename from KWD/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h rename to shared/KWD/acsdkKWDImplementations/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h diff --git a/KWD/XMOS/GPIO/src/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/CMakeLists.txt similarity index 100% rename from KWD/XMOS/GPIO/src/CMakeLists.txt rename to shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/CMakeLists.txt diff --git a/KWD/XMOS/GPIO/src/GPIOKeywordDetector.cpp b/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/GPIOKeywordDetector.cpp similarity index 100% rename from KWD/XMOS/GPIO/src/GPIOKeywordDetector.cpp rename to shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/GPIOKeywordDetector.cpp diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/HID/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/HID/CMakeLists.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/KWD/XMOS/HID/include/HID/HIDKeywordDetector.h b/shared/KWD/acsdkKWDImplementations/XMOS/HID/include/HID/HIDKeywordDetector.h similarity index 100% rename from KWD/XMOS/HID/include/HID/HIDKeywordDetector.h rename to shared/KWD/acsdkKWDImplementations/XMOS/HID/include/HID/HIDKeywordDetector.h diff --git a/KWD/XMOS/HID/src/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/HID/src/CMakeLists.txt similarity index 100% rename from KWD/XMOS/HID/src/CMakeLists.txt rename to shared/KWD/acsdkKWDImplementations/XMOS/HID/src/CMakeLists.txt diff --git a/KWD/XMOS/HID/src/HIDKeywordDetector.cpp b/shared/KWD/acsdkKWDImplementations/XMOS/HID/src/HIDKeywordDetector.cpp similarity index 100% rename from KWD/XMOS/HID/src/HIDKeywordDetector.cpp rename to shared/KWD/acsdkKWDImplementations/XMOS/HID/src/HIDKeywordDetector.cpp diff --git a/KWD/XMOS/include/XMOS/XMOSKeywordDetector.h b/shared/KWD/acsdkKWDImplementations/XMOS/include/XMOS/XMOSKeywordDetector.h similarity index 100% rename from KWD/XMOS/include/XMOS/XMOSKeywordDetector.h rename to shared/KWD/acsdkKWDImplementations/XMOS/include/XMOS/XMOSKeywordDetector.h diff --git a/KWD/XMOS/src/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/src/CMakeLists.txt similarity index 100% rename from KWD/XMOS/src/CMakeLists.txt rename to shared/KWD/acsdkKWDImplementations/XMOS/src/CMakeLists.txt diff --git a/KWD/XMOS/src/XMOSKeywordDetector.cpp b/shared/KWD/acsdkKWDImplementations/XMOS/src/XMOSKeywordDetector.cpp similarity index 100% rename from KWD/XMOS/src/XMOSKeywordDetector.cpp rename to shared/KWD/acsdkKWDImplementations/XMOS/src/XMOSKeywordDetector.cpp diff --git a/KWD/include/KWD/AbstractKeywordDetector.h b/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/AbstractKeywordDetector.h similarity index 80% rename from KWD/include/KWD/AbstractKeywordDetector.h rename to shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/AbstractKeywordDetector.h index ff4d25ca30..c292e95dd7 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. */ @@ -74,7 +77,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. @@ -86,6 +89,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. * @@ -147,32 +160,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 27743ffa56..be50ed3d28 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. @@ -51,11 +51,12 @@ done SOUND_CONFIG="$HOME/.asoundrc" START_SCRIPT="$INSTALL_BASE/startsample.sh" START_PREVIEW_SCRIPT="$INSTALL_BASE/startpreview.sh" -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" \ - -DCURL_INCLUDE_DIR=${THIRD_PARTY_PATH}/curl-${CURL_VER}/include \ - -DCURL_LIBRARY=${THIRD_PARTY_PATH}/curl-${CURL_VER}/lib/.libs/libcurl.so) +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" \ + -DCURL_INCLUDE_DIR=${THIRD_PARTY_PATH}/curl-${CURL_VER}/include \ + -DCURL_LIBRARY=${THIRD_PARTY_PATH}/curl-${CURL_VER}/lib/.libs/libcurl.so) # Add the flags for the different keyword detectors if [ -n "$SENSORY_KEY_WORD_DETECTOR_FLAG" ] @@ -81,12 +82,10 @@ install_dependencies() { run_os_specifics() { build_port_audio build_curl - if [ -n "$SENSORY_KEY_WORD_DETECTOR_FLAG" ] - then - build_kwd_engine - fi - # the step below is not necessary for any XMOS setup - # configure_sound + echo + echo "==============> TAP-TO-TALK IS ENABLED ==============" + echo + configure_sound } configure_sound() { @@ -109,21 +108,6 @@ configure_sound() { EOF } -build_kwd_engine() { - #get sensory and build - echo - echo "==============> CLONING AND BUILDING SENSORY ==============" - echo - - cd $THIRD_PARTY_PATH - rm -rf alexa-rpi - git clone git://github.com/Sensory/alexa-rpi.git - pushd alexa-rpi > /dev/null - git checkout $SENSORY_MODEL_HASH -- models/spot-alexa-rpi-31000.snsr - popd > /dev/null - bash ./alexa-rpi/bin/license.sh -} - build_curl() { #get curl and build echo @@ -142,13 +126,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 } @@ -157,8 +141,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 EOF diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index fdd3d4c8fd..06b0c015c6 100755 --- 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. @@ -31,12 +31,10 @@ 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'} pushd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null -CURRENT_DIR="$( pwd )" +CURRENT_DIR="$(pwd)" THIS_SCRIPT="$CURRENT_DIR/setup.sh" popd > /dev/null @@ -53,8 +51,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/temp_AlexaClientSDKConfig.json" @@ -85,6 +81,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 @@ -126,13 +128,17 @@ show_help() { echo ' The is the tag in the GIT repository xmos/avs-device-sdk' echo '' echo 'Optional parameters' - echo ' -s If nothing is provided, the default device serial number is 123456' - 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 ' -s If nothing is provided, the default device serial number is 123456' + 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 ' -x XMOS device to setup: possible values are xvf3100, xvf3500, xvf3510, xvf3600-slave, xvf3600-master, or xvf3610, default is xvf3510' echo ' -g Flag to enable keyword detector on GPIO interrupt' - echo ' -h Display this help and exit' + echo ' -h Display this help and exit' } if [[ $# -lt 2 ]]; then @@ -151,7 +157,7 @@ XMOS_TAG=$2 shift 2 -OPTIONS=s:a:d:m:x:GHh +OPTIONS=s:a:d:hp:k:i:t:m:x:GHh while getopts "$OPTIONS" opt ; do case $opt in s ) @@ -184,6 +190,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 @@ -192,6 +210,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)} @@ -393,39 +454,34 @@ then $PI_HAT_FLAG \ $XMOS_AVS_TESTS_FLAG \ -DCMAKE_BUILD_TYPE=DEBUG \ + -DPKCS11=$ACSDK_PKCS11 \ "${CMAKE_PLATFORM_SPECIFIC[@]}" cd $BUILD_PATH -# remove -j2 option to allow building in Raspberry Pi3 + # remove -j2 option to allow building in Raspberry Pi3 make SampleApp - # The PreviewAlexaClient is intended only for integrating the manufactory into the SDK - # It is not used bv the XMOS EVK and it currently doesn't build on RPi3 - # make PreviewAlexaClient + make PreviewAlexaClient + make all else cd $BUILD_PATH -# remove -j2 option to allow building in Raspberry Pi3 + # remove -j2 option to allow building in Raspberry Pi3 make SampleApp - # The PreviewAlexaClient is intended only for integrating the manufactory into the SDK - # It is not used bv the XMOS EVK and it currently doesn't build on RPi3 - # make PreviewAlexaClient + make PreviewAlexaClient + make all 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 $CURRENT_DIR 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 bdf466f25c8f7e575d58a3bb2dcdabbc8b042ba4 Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 17 Jun 2022 14:01:35 +0100 Subject: [PATCH 02/60] Restore old files --- KWD/XMOS/CMakeLists.txt | 11 + KWD/XMOS/GPIO/CMakeLists.txt | 6 + .../GPIO/include/GPIO/GPIOKeywordDetector.h | 112 ++++++++ KWD/XMOS/GPIO/src/CMakeLists.txt | 13 + KWD/XMOS/GPIO/src/GPIOKeywordDetector.cpp | 242 ++++++++++++++++ KWD/XMOS/HID/CMakeLists.txt | 6 + KWD/XMOS/HID/include/HID/HIDKeywordDetector.h | 116 ++++++++ KWD/XMOS/HID/src/CMakeLists.txt | 19 ++ KWD/XMOS/HID/src/HIDKeywordDetector.cpp | 261 ++++++++++++++++++ KWD/XMOS/include/XMOS/XMOSKeywordDetector.h | 129 +++++++++ KWD/XMOS/src/CMakeLists.txt | 12 + KWD/XMOS/src/XMOSKeywordDetector.cpp | 97 +++++++ 12 files changed, 1024 insertions(+) create mode 100644 KWD/XMOS/CMakeLists.txt create mode 100644 KWD/XMOS/GPIO/CMakeLists.txt create mode 100644 KWD/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h create mode 100644 KWD/XMOS/GPIO/src/CMakeLists.txt create mode 100644 KWD/XMOS/GPIO/src/GPIOKeywordDetector.cpp create mode 100644 KWD/XMOS/HID/CMakeLists.txt create mode 100644 KWD/XMOS/HID/include/HID/HIDKeywordDetector.h create mode 100644 KWD/XMOS/HID/src/CMakeLists.txt create mode 100644 KWD/XMOS/HID/src/HIDKeywordDetector.cpp create mode 100644 KWD/XMOS/include/XMOS/XMOSKeywordDetector.h create mode 100644 KWD/XMOS/src/CMakeLists.txt create mode 100644 KWD/XMOS/src/XMOSKeywordDetector.cpp diff --git a/KWD/XMOS/CMakeLists.txt b/KWD/XMOS/CMakeLists.txt new file mode 100644 index 0000000000..0a63d121ce --- /dev/null +++ b/KWD/XMOS/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(XMOS LANGUAGES CXX) + +add_subdirectory("src") + +if(HID_KEY_WORD_DETECTOR) + add_subdirectory("HID") +endif() +if(GPIO_KEY_WORD_DETECTOR) + add_subdirectory("GPIO") +endif() diff --git a/KWD/XMOS/GPIO/CMakeLists.txt b/KWD/XMOS/GPIO/CMakeLists.txt new file mode 100644 index 0000000000..90f204835c --- /dev/null +++ b/KWD/XMOS/GPIO/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(GPIO LANGUAGES CXX) + +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +add_subdirectory("src") diff --git a/KWD/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h b/KWD/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h new file mode 100644 index 0000000000..369fa61e70 --- /dev/null +++ b/KWD/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h @@ -0,0 +1,112 @@ +// Copyright (c) 2021-2022 XMOS LIMITED. This Software is subject to the terms of the +// XMOS Public License: Version 1 +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES 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_KWD_XMOS_GPIO_INCLUDE_GPIO_GPIOKEYWORDDETECTOR_H_ +#define ALEXA_CLIENT_SDK_KWD_XMOS_GPIO_INCLUDE_GPIO_GPIOKEYWORDDETECTOR_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "XMOS/XMOSKeywordDetector.h" + +namespace alexaClientSDK { +namespace kwd { + +using namespace avsCommon; +using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; + +// A specialization of a KeyWordEngine, where a trigger comes from GPIO +class GPIOKeywordDetector : public XMOSKeywordDetector { +public: + /** + * Creates a @c GPIOKeywordDetector. + * + * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + * @return A new @c GPIOKeywordDetector, or @c nullptr if the operation failed. + */ + static std::unique_ptr create( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * Destructor. + */ + ~GPIOKeywordDetector(); + +private: + /** + * Constructor. + * + * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + */ + GPIOKeywordDetector( + std::shared_ptr stream, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * Initializes the stream reader, sets up the GPIO pin, and kicks off threads to begin processing data from + * the stream. This function should only be called once with each new @c GPIOKeywordDetector. + * + * @return @c true if the engine was initialized properly and @c false otherwise. + */ + bool init(); + + /** + * Open the I2C port connected to the device + * + * @return @c true if file descriptor for the connected device is correctly set + */ + bool openDevice(); + + /// Re-declaration of base class member function + void detectionLoop(); + + /// The file descriptor to access I2C port + int m_fileDescriptor; + +}; +} // namespace kwd +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_KWD_GPIO_INCLUDE_GPIO_GPIOKEYWORDDETECTOR_H_ diff --git a/KWD/XMOS/GPIO/src/CMakeLists.txt b/KWD/XMOS/GPIO/src/CMakeLists.txt new file mode 100644 index 0000000000..f5d9033c2d --- /dev/null +++ b/KWD/XMOS/GPIO/src/CMakeLists.txt @@ -0,0 +1,13 @@ +add_definitions("-DACSDK_LOG_MODULE=GPIOKeywordDetector") +add_library(GPIO SHARED + GPIOKeywordDetector.cpp) + +target_include_directories(GPIO PUBLIC + "${KWD_SOURCE_DIR}/include" + "${XMOS_SOURCE_DIR}/include" + "${GPIO_SOURCE_DIR}/include") + +target_link_libraries(GPIO XMOS KWD AVSCommon wiringPi) + +# install target +asdk_install() diff --git a/KWD/XMOS/GPIO/src/GPIOKeywordDetector.cpp b/KWD/XMOS/GPIO/src/GPIOKeywordDetector.cpp new file mode 100644 index 0000000000..79bb25e414 --- /dev/null +++ b/KWD/XMOS/GPIO/src/GPIOKeywordDetector.cpp @@ -0,0 +1,242 @@ +// Copyright (c) 2021-2022 XMOS LIMITED. This Software is subject to the terms of the +// XMOS Public License: Version 1 +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT 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 "GPIO/GPIOKeywordDetector.h" + +namespace alexaClientSDK { +namespace kwd { + +using namespace avsCommon::utils::logger; + +/// String to identify log entries originating from this file. +static const std::string TAG("GPIOKeywordDetector"); + +/** + * 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) + +/// GPIO pin to monitor: +// Wiring Pi pin 2 which corresponds to Physical/Board pin 13 and GPIO/BCM pin 27 +static const int GPIO_PIN = 2; + +/// The device name of the I2C port connected to the device. +static const char *DEVNAME = "/dev/i2c-1"; + +/// The address of the I2C port connected to the device. +static const unsigned char I2C_ADDRESS = 0x2C; + +/// The maximum size in bytes of the I2C transaction +static const int I2C_TRANSACTION_MAX_BYTES = 256; + +/// The resource ID of the XMOS control command. +static const int CONTROL_RESOURCE_ID = 0xE0; + +/// The command ID of the XMOS control command. +static const int CONTROL_CMD_ID = 0xAF; + +/// The length of the payload of the XMOS control command +/// one control byte plus 3 uint64_t values +static const int CONTROL_CMD_PAYLOAD_LEN = 25; + +bool GPIOKeywordDetector::openDevice() { + int rc = 0; + m_fileDescriptor = -1; + + setenv("WIRINGPI_GPIOMEM", "1", 1); + if (wiringPiSetup() < 0) { + ACSDK_ERROR(LX("openDeviceFailed").d("reason", "wiringPiSetup failed")); + return false; + } + pinMode(GPIO_PIN, INPUT); + + // Open port for reading and writing + if ((m_fileDescriptor = open(DEVNAME, O_RDWR)) < 0) { + ACSDK_ERROR(LX("openDeviceFailed") + .d("reason", "openFailed")); + perror( "" ); + return false; + } + // Set the port options and set the address of the device we wish to speak to + if ((rc = ioctl(m_fileDescriptor, I2C_SLAVE, I2C_ADDRESS)) < 0) { + ACSDK_ERROR(LX("openDeviceFailed") + .d("reason", "setI2CConfigurationFailed")); + perror( "" ); + return false; + } + + ACSDK_INFO(LX("openDeviceSuccess").d("port", I2C_ADDRESS)); + + return true; +} + +std::unique_ptr GPIOKeywordDetector::create( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + 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)) { + ACSDK_ERROR(LX("createFailed").d("reason", "endianMismatch")); + return nullptr; + } + + std::unique_ptr detector(new GPIOKeywordDetector( + stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat)); + + if (!detector->init()) { + ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); + return nullptr; + } + + return detector; +} + +GPIOKeywordDetector::GPIOKeywordDetector( + std::shared_ptr stream, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration) : + XMOSKeywordDetector(stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat, msToPushPerIteration) { +} + +GPIOKeywordDetector::~GPIOKeywordDetector() { +} + +bool GPIOKeywordDetector::init() { + if (XMOSKeywordDetector::init()) { + m_detectionThread = std::thread(&GPIOKeywordDetector::detectionLoop, this); + return true; + } + return false; +} + +void GPIOKeywordDetector::detectionLoop() { + notifyKeyWordDetectorStateObservers(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); + int oldGpioValue = HIGH; + + std::chrono::steady_clock::time_point prev_time; + std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); + + while (!m_isShuttingDown) { + auto currentIndex = m_streamReader->tell(); + + // Read gpio value + int gpioValue = digitalRead(GPIO_PIN); + + // Check if GPIO pin is changing from high to low + if (gpioValue == LOW && oldGpioValue == HIGH) + { + std::chrono::steady_clock::time_point current_time = std::chrono::steady_clock::now(); + ACSDK_DEBUG0(LX("detectionLoopGPIOevent").d("absoluteElapsedTime (ms)", std::chrono::duration_cast (current_time - start_time).count())); + + // Check if this is not the first GPIO event + if (prev_time != std::chrono::steady_clock::time_point()) { + ACSDK_DEBUG0(LX("detectionLoopGPIOevent").d("elapsedTimeFromPreviousEvent (ms)", std::chrono::duration_cast (current_time - prev_time).count())); + } + prev_time = current_time; + + // Retrieve device indexes using control message via USB + unsigned char payload[64]; + + uint8_t cmd_ret = 1; + + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + int rc = 0; + while (cmd_ret!=0) { + // Do a repeated start (write followed by read with no stop bit) + unsigned char read_hdr[I2C_TRANSACTION_MAX_BYTES]; + read_hdr[0] = CONTROL_RESOURCE_ID; + read_hdr[1] = CONTROL_CMD_ID; + read_hdr[2] = (uint8_t)CONTROL_CMD_PAYLOAD_LEN; + + struct i2c_msg rdwr_msgs[2] = { + { // Start address + .addr = I2C_ADDRESS, + .flags = 0, // write + .len = 3, // this is always 3 + .buf = read_hdr + }, + { // Read buffer + .addr = I2C_ADDRESS, + .flags = I2C_M_RD, // read + .len = CONTROL_CMD_PAYLOAD_LEN, + .buf = payload + } + }; + + struct i2c_rdwr_ioctl_data rdwr_data = { + .msgs = rdwr_msgs, + .nmsgs = 2 + }; + + rc = ioctl( m_fileDescriptor, I2C_RDWR, &rdwr_data ); + + if ( rc < 0 ) { + ACSDK_ERROR(LX("detectionLoopControlCommandFailed").d("reason", rc)); + perror( "" ); + } + + cmd_ret = payload[0]; + } + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + ACSDK_DEBUG0(LX("detectionLoopControlCommand").d("time (us)", std::chrono::duration_cast (end - begin).count() )); + + // Read indexes + uint64_t currentDeviceIndex = readIndex(payload, 1); + uint64_t beginKWDeviceIndex = readIndex(payload, 9); + uint64_t endKWDeviceIndex = readIndex(payload, 17); + auto beginKWServerIndex = currentIndex - (currentDeviceIndex - beginKWDeviceIndex); + + // Send information to the server + notifyKeyWordObservers( + m_stream, + KEYWORD_STRING, + beginKWServerIndex, + currentIndex); + ACSDK_DEBUG0(LX("detectionLoopIndexes").d("hostCurrentIndex", currentIndex) + .d("deviceCurrentIndex", currentDeviceIndex) + .d("deviceKWEndIndex", endKWDeviceIndex) + .d("deviceKWBeginIndex", beginKWDeviceIndex) + .d("serverKWEndIndex", currentIndex) + .d("serverKWBeginIndex", beginKWServerIndex)); + } + oldGpioValue = gpioValue; + } + m_streamReader->close(); +} + +} // namespace kwd +} // namespace alexaClientSDK diff --git a/KWD/XMOS/HID/CMakeLists.txt b/KWD/XMOS/HID/CMakeLists.txt new file mode 100644 index 0000000000..5df9f258f7 --- /dev/null +++ b/KWD/XMOS/HID/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(HID LANGUAGES CXX) + +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +add_subdirectory("src") diff --git a/KWD/XMOS/HID/include/HID/HIDKeywordDetector.h b/KWD/XMOS/HID/include/HID/HIDKeywordDetector.h new file mode 100644 index 0000000000..c190c3ef72 --- /dev/null +++ b/KWD/XMOS/HID/include/HID/HIDKeywordDetector.h @@ -0,0 +1,116 @@ +// Copyright (c) 2021-2022 XMOS LIMITED. This Software is subject to the terms of the +// XMOS Public License: Version 1 +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES 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_KWD_HID_INCLUDE_HID_HIDKEYWORDDETECTOR_H_ +#define ALEXA_CLIENT_SDK_KWD_HID_INCLUDE_HID_HIDKEYWORDDETECTOR_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "XMOS/XMOSKeywordDetector.h" + +namespace alexaClientSDK { +namespace kwd { + +using namespace avsCommon; +using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; + +// A specialization of a KeyWordEngine, where a trigger comes from HID +class HIDKeywordDetector : public XMOSKeywordDetector { +public: + /** + * Creates a @c HIDKeywordDetector. + * + * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + * @return A new @c HIDKeywordDetector, or @c nullptr if the operation failed. + */ + static std::unique_ptr create( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * Destructor. + */ + ~HIDKeywordDetector(); + +private: + /** + * Constructor. + * + * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + */ + HIDKeywordDetector( + std::shared_ptr stream, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * Initializes the stream reader, sets up the USB connection, and kicks off threads to begin processing data from + * the stream. This function should only be called once with each new @c HIDKeywordDetector. + * + * @return @c true if the engine was initialized properly and @c false otherwise. + */ + bool init(); + + /** + * Search for an USB device, open the connection and return the correct handlers + * + * @return @c true if device is found and handlers are correctly set + */ + bool openDevice(); + + /// Declaration of function from base class + void detectionLoop(); + + /// The device handler necessary for reading HID events + struct libevdev *m_evdev; + + //The device handler necessary for sending control commands + libusb_device_handle *m_devh; +}; + +} // namespace kwd +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_KWD_HID_INCLUDE_HID_HIDKEYWORDDETECTOR_H_ diff --git a/KWD/XMOS/HID/src/CMakeLists.txt b/KWD/XMOS/HID/src/CMakeLists.txt new file mode 100644 index 0000000000..243b37bbfa --- /dev/null +++ b/KWD/XMOS/HID/src/CMakeLists.txt @@ -0,0 +1,19 @@ +add_definitions("-DACSDK_LOG_MODULE=HIDKeywordDetector") + +find_package(PkgConfig) +pkg_check_modules(libevdev REQUIRED libevdev) +find_package(PkgConfig) +pkg_check_modules(libusb-1.0 REQUIRED libusb-1.0) + +add_library(HID SHARED + HIDKeywordDetector.cpp) + +target_include_directories(HID PUBLIC + "${KWD_SOURCE_DIR}/include" + "${XMOS_SOURCE_DIR}/include" + "${HID_SOURCE_DIR}/include") + +target_link_libraries(HID KWD AVSCommon evdev usb-1.0) + +# install target +asdk_install() diff --git a/KWD/XMOS/HID/src/HIDKeywordDetector.cpp b/KWD/XMOS/HID/src/HIDKeywordDetector.cpp new file mode 100644 index 0000000000..297f7097a5 --- /dev/null +++ b/KWD/XMOS/HID/src/HIDKeywordDetector.cpp @@ -0,0 +1,261 @@ +// Copyright (c) 2021 XMOS LIMITED. This Software is subject to the terms of the +// XMOS Public License: Version 1 +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT 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 "HID/HIDKeywordDetector.h" + +namespace alexaClientSDK { +namespace kwd { + +using namespace avsCommon::utils::logger; + +/// String to identify log entries originating from this file. +static const std::string TAG("HIDKeywordDetector"); + +/** + * 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) + +/// HID keycode to monitor: +static const char * HID_KEY_CODE = "KEY_T"; + +/// HID device directory path +static const std::string HID_DEVICE_DIR_PATH = "/dev/input/"; + +/// HID device name +static const std::string HID_DEVICE_NAME = "XMOS XVF3615 Voice Processor Keyboard"; + +/// USB Product ID of XMOS device +static const int USB_VENDOR_ID = 0x20B1; + +/// USB Product ID of XMOS device +static const int USB_PRODUCT_ID = 0x18; + +/// USB timeout for control transfer +static const int USB_TIMEOUT_MS = 500; + +/// The resource ID of the XMOS control command. +static const int CONTROL_RESOURCE_ID = 0xE0; + +/// The command ID of the XMOS control command. +static const int CONTROL_CMD_ID = 0xAF; + +/// The length of the payload of the XMOS control command +/// one control byte plus 3 uint64_t values +static const int CONTROL_CMD_PAYLOAD_LEN = 25; + +bool HIDKeywordDetector::openDevice() { + int rc = 1; + libusb_device **devs = NULL; + libusb_device *dev = NULL; + + ACSDK_INFO(LX("openDeviceOngoing") + .d("HIDDeviceName", HID_DEVICE_NAME) + .d("USBVendorID", USB_VENDOR_ID) + .d("USBProductID", USB_PRODUCT_ID)); + + // Find USB device for reading HID events + int fd = -1; + DIR *dir; + struct dirent *entry; + + //search all files in directory + dir = opendir(HID_DEVICE_DIR_PATH.c_str()); + if (dir) { + while ((entry = readdir(dir)) != NULL) { + std::string file_path(entry->d_name); + fd = open((HID_DEVICE_DIR_PATH+file_path).c_str(), O_RDONLY|O_NONBLOCK); + + // Do not check if command is successful, as the entries reported by readdir() are not all devices + rc = libevdev_new_from_fd(fd, &m_evdev); + if (!rc) { + if (libevdev_get_name(m_evdev)==HID_DEVICE_NAME) { + ACSDK_INFO(LX("openDeviceSuccess").d("reason", "Found HID device").d("path", HID_DEVICE_DIR_PATH+file_path)); + break; + } + } + fd = -1; + } + closedir(dir); //close directory + } + if (fd==-1) { + ACSDK_ERROR(LX("openDeviceFailed").d("reason", "HidDeviceNotFound")); + } + // Find USB device for sending control commands + int ret = libusb_init(NULL); + if (ret < 0) { + ACSDK_ERROR(LX("openDeviceFailed").d("reason", "initialiseLibUsbFailed")); + return false; + } + + int num_dev = libusb_get_device_list(NULL, &devs); + + for (int i = 0; i < num_dev; i++) { + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(devs[i], &desc); + if (desc.idVendor == USB_VENDOR_ID && desc.idProduct == USB_PRODUCT_ID) { + dev = devs[i]; + break; + } + } + + if (dev == NULL) { + ACSDK_ERROR(LX("openDeviceFailed").d("reason", "UsbDeviceNotFound")); + return false; + } + + if (libusb_open(dev, &m_devh) < 0) { + ACSDK_ERROR(LX("openDeviceFailed").d("reason", "UsbDeviceNotOpened")); + return false; + } + + libusb_free_device_list(devs, 1); + ACSDK_INFO(LX("openDeviceSuccess").d("reason", "UsbDeviceOpened")); + return true; +} + +std::unique_ptr HIDKeywordDetector::create( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + 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)) { + ACSDK_ERROR(LX("createFailed").d("reason", "endianMismatch")); + return nullptr; + } + + std::unique_ptr detector(new HIDKeywordDetector( + stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat)); + + if (!detector->init()) { + ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); + return nullptr; + } + + return detector; +} + +HIDKeywordDetector::HIDKeywordDetector( + std::shared_ptr stream, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration) : + XMOSKeywordDetector(stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat, msToPushPerIteration) { +} + +HIDKeywordDetector::~HIDKeywordDetector() { +} + + +bool HIDKeywordDetector::init() { + if (XMOSKeywordDetector::init()) { + m_detectionThread = std::thread(&HIDKeywordDetector::detectionLoop, this); + return true; + } + return false; +} + +void HIDKeywordDetector::detectionLoop() { + notifyKeyWordDetectorStateObservers(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); + int rc = 1; + + std::chrono::steady_clock::time_point prev_time; + std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); + + while (!m_isShuttingDown) { + auto currentIndex = m_streamReader->tell(); + struct input_event ev; + rc = libevdev_next_event(m_evdev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + + // wait for HID_KEY_CODE true event + if (rc == 0 && strcmp(libevdev_event_type_get_name(ev.type), "EV_KEY")==0 && \ + strcmp(libevdev_event_code_get_name(ev.type, ev.code), HID_KEY_CODE)==0 && \ + ev.value == 1) + { + std::chrono::steady_clock::time_point current_time = std::chrono::steady_clock::now(); + ACSDK_DEBUG0(LX("detectionLoopHIDevent").d("absoluteElapsedTime (ms)", std::chrono::duration_cast (current_time - start_time).count())); + + // Check if this is not the first HID event + if (prev_time != std::chrono::steady_clock::time_point()) { + ACSDK_DEBUG0(LX("detectionLoopHIDevent").d("elapsedTimeFromPreviousEvent (ms)", std::chrono::duration_cast (current_time - prev_time).count())); + } + prev_time = current_time; + + // Retrieve device indexes using control message via USB + unsigned char payload[64]; + + uint8_t cmd_ret = 1; + + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + while (cmd_ret!=0) { + rc = libusb_control_transfer(m_devh, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + 0, CONTROL_CMD_ID, CONTROL_RESOURCE_ID, payload, CONTROL_CMD_PAYLOAD_LEN, USB_TIMEOUT_MS); + + cmd_ret = payload[0]; + } + if (rc != CONTROL_CMD_PAYLOAD_LEN) { + ACSDK_ERROR(LX("detectionLoopControlCommand").d("reason", "USBControlTransferFailed")); + } + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + ACSDK_DEBUG0(LX("detectionLoopControlCommand").d("time (us)", std::chrono::duration_cast (end - begin).count() )); + + // Read indexes + uint64_t currentDeviceIndex = readIndex(payload, 1); + uint64_t beginKWDeviceIndex = readIndex(payload, 9); + uint64_t endKWDeviceIndex = readIndex(payload, 17); + + auto beginKWServerIndex = currentIndex - (currentDeviceIndex - beginKWDeviceIndex); + + // Send information to the server + notifyKeyWordObservers( + m_stream, + KEYWORD_STRING, + beginKWServerIndex, + currentIndex); + + ACSDK_DEBUG0(LX("detectionLoopIndexes").d("hostCurrentIndex", currentIndex) + .d("deviceCurrentIndex", currentDeviceIndex) + .d("deviceKWEndIndex", endKWDeviceIndex) + .d("deviceKWBeginIndex", beginKWDeviceIndex) + .d("serverKWEndIndex", currentIndex) + .d("serverKWBeginIndex", beginKWServerIndex)); + } + } +} + +} // namespace kwd +} // namespace alexaClientSDK diff --git a/KWD/XMOS/include/XMOS/XMOSKeywordDetector.h b/KWD/XMOS/include/XMOS/XMOSKeywordDetector.h new file mode 100644 index 0000000000..3ada07bb4c --- /dev/null +++ b/KWD/XMOS/include/XMOS/XMOSKeywordDetector.h @@ -0,0 +1,129 @@ +// Copyright (c) 2022 XMOS LIMITED. This Software is subject to the terms of the +// XMOS Public License: Version 1 +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES 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_KWD_XMOS_INCLUDE_XMOS_XMOSKEYWORDDETECTOR_H_ +#define ALEXA_CLIENT_SDK_KWD_XMOS_INCLUDE_XMOS_XMOSKEYWORDDETECTOR_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "KWD/AbstractKeywordDetector.h" + +namespace alexaClientSDK { +namespace kwd { + +/// Keyword string +static const std::string KEYWORD_STRING = "alexa"; + +/// The number of hertz per kilohertz. +static const size_t HERTZ_PER_KILOHERTZ = 1000; + +/// The timeout to use for read calls to the SharedDataStream. +const std::chrono::milliseconds TIMEOUT_FOR_READ_CALLS = std::chrono::milliseconds(1000); + +using namespace avsCommon; +using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; + +// A specialization of a KeyWordEngine, where a trigger comes from an external XMOS device +class XMOSKeywordDetector : public AbstractKeywordDetector { + +protected: + + /** + * Constructor. + * + * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + */ + XMOSKeywordDetector( + std::shared_ptr stream, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * Destructor. + */ + ~XMOSKeywordDetector(); + + /** + * Initializes the stream reader, sets up the connection to the device, and kicks off thread to begin reading + * the audio stream. This function should only be called once with each new @c XMOSKeywordDetector. + * + * @return @c true if the engine was initialized properly and @c false otherwise. + */ + bool init(); + + /// Function to establish a connection with an XMOS device + virtual bool openDevice() = 0; + + /// The main function that reads data and feeds it into the engine. + virtual void detectionLoop() = 0; + + /// The main function that reads data and feeds it into the engine. + void readAudioLoop(); + + /** + * Read a specific index from the payload of the USB control message + * + * @param payload The data returned via control message + * @param start_index The index in the payload to start reading from + * @return value stored in payload + */ + uint64_t readIndex(uint8_t* payload, int start_index); + + /// Indicates whether the internal main loop should keep running. + std::atomic m_isShuttingDown; + + /// The stream of audio data. + const std::shared_ptr m_stream; + + /// The reader that will be used to read audio data from the stream. + std::shared_ptr m_streamReader; + + /// Internal thread that read audio samples + std::thread m_readAudioThread; + + /// Internal thread that monitors the external XMOS device + std::thread m_detectionThread; + + /** + * The max number of samples to push into the underlying engine per iteration. This will be determined based on the + * sampling rate of the audio data passed in. + */ + const size_t m_maxSamplesPerPush; +}; + +} // namespace kwd +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_KWD_XMOS_INCLUDE_XMOS_XMOSKEYWORDDETECTOR_H_ diff --git a/KWD/XMOS/src/CMakeLists.txt b/KWD/XMOS/src/CMakeLists.txt new file mode 100644 index 0000000000..1f37dde4a0 --- /dev/null +++ b/KWD/XMOS/src/CMakeLists.txt @@ -0,0 +1,12 @@ +add_definitions("-DACSDK_LOG_MODULE=XMOSKeywordDetector") +add_library(XMOS SHARED + XMOSKeywordDetector.cpp) + +target_include_directories(XMOS PUBLIC + "${KWD_SOURCE_DIR}/include" + "${XMOS_SOURCE_DIR}/include") + +target_link_libraries(XMOS KWD AVSCommon) + +# install target +asdk_install() diff --git a/KWD/XMOS/src/XMOSKeywordDetector.cpp b/KWD/XMOS/src/XMOSKeywordDetector.cpp new file mode 100644 index 0000000000..e37713c1b9 --- /dev/null +++ b/KWD/XMOS/src/XMOSKeywordDetector.cpp @@ -0,0 +1,97 @@ +// Copyright (c) 2022 XMOS LIMITED. This Software is subject to the terms of the +// XMOS Public License: Version 1 +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT 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 "XMOS/XMOSKeywordDetector.h" + +namespace alexaClientSDK { +namespace kwd { + +/// String to identify log entries originating from this file. +static const std::string TAG("XMOSKeywordDetector"); + +/** + * 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) + +XMOSKeywordDetector::XMOSKeywordDetector( + std::shared_ptr stream, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration) : + AbstractKeywordDetector(keyWordObservers, keyWordDetectorStateObservers), + m_stream{stream}, + m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { +} + +XMOSKeywordDetector::~XMOSKeywordDetector() { + m_isShuttingDown = true; + if (m_detectionThread.joinable()) + m_detectionThread.join(); + if (m_readAudioThread.joinable()) + m_readAudioThread.join(); +} + +bool XMOSKeywordDetector::init() { + if (!openDevice()) { + ACSDK_ERROR(LX("initFailed").d("reason", "openDeviceFailed")); + return false; + } + + m_streamReader = m_stream->createReader(AudioInputStream::Reader::Policy::BLOCKING); + if (!m_streamReader) { + ACSDK_ERROR(LX("initFailed").d("reason", "createStreamReaderFailed")); + return false; + } + + m_isShuttingDown = false; + m_readAudioThread = std::thread(&XMOSKeywordDetector::readAudioLoop, this); + return true; +} + +void XMOSKeywordDetector::readAudioLoop() { + std::vector audioDataToPush(m_maxSamplesPerPush); + bool didErrorOccur = false; + + while (!m_isShuttingDown) { + readFromStream( + m_streamReader, + m_stream, + audioDataToPush.data(), + audioDataToPush.size(), + TIMEOUT_FOR_READ_CALLS, + &didErrorOccur); + if (didErrorOccur) { + m_isShuttingDown = true; + } + } +} + +uint64_t XMOSKeywordDetector::readIndex(uint8_t* payload, int start_index) { + uint64_t u64value = 0; + // convert array of bytes into uint64_t value + memcpy(&u64value, &payload[start_index], sizeof(uint64_t)); + // swap bytes of uint64_t value + u64value = __bswap_64(u64value); + return u64value; +} + +} // namespace kwd +} // namespace alexaClientSDK From caffa25adfdccb0ca62ce679baa8f1d09722be8b Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 09:04:37 +0100 Subject: [PATCH 03/60] Move old files to the new locations --- {KWD/XMOS => applications/acsdkXMOSAdapter}/GPIO/CMakeLists.txt | 0 .../acsdkXMOSAdapter}/GPIO/include/GPIO/GPIOKeywordDetector.h | 0 .../acsdkXMOSAdapter}/GPIO/src/CMakeLists.txt | 0 .../acsdkXMOSAdapter}/GPIO/src/GPIOKeywordDetector.cpp | 0 {KWD/XMOS => applications/acsdkXMOSAdapter}/HID/CMakeLists.txt | 0 .../acsdkXMOSAdapter}/HID/include/HID/HIDKeywordDetector.h | 0 .../XMOS => applications/acsdkXMOSAdapter}/HID/src/CMakeLists.txt | 0 .../acsdkXMOSAdapter}/HID/src/HIDKeywordDetector.cpp | 0 {KWD => applications/acsdkXMOSAdapter}/XMOS/CMakeLists.txt | 0 .../acsdkXMOSAdapter}/XMOS/include/XMOS/XMOSKeywordDetector.h | 0 {KWD => applications/acsdkXMOSAdapter}/XMOS/src/CMakeLists.txt | 0 .../acsdkXMOSAdapter}/XMOS/src/XMOSKeywordDetector.cpp | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename {KWD/XMOS => applications/acsdkXMOSAdapter}/GPIO/CMakeLists.txt (100%) rename {KWD/XMOS => applications/acsdkXMOSAdapter}/GPIO/include/GPIO/GPIOKeywordDetector.h (100%) rename {KWD/XMOS => applications/acsdkXMOSAdapter}/GPIO/src/CMakeLists.txt (100%) rename {KWD/XMOS => applications/acsdkXMOSAdapter}/GPIO/src/GPIOKeywordDetector.cpp (100%) rename {KWD/XMOS => applications/acsdkXMOSAdapter}/HID/CMakeLists.txt (100%) rename {KWD/XMOS => applications/acsdkXMOSAdapter}/HID/include/HID/HIDKeywordDetector.h (100%) rename {KWD/XMOS => applications/acsdkXMOSAdapter}/HID/src/CMakeLists.txt (100%) rename {KWD/XMOS => applications/acsdkXMOSAdapter}/HID/src/HIDKeywordDetector.cpp (100%) rename {KWD => applications/acsdkXMOSAdapter}/XMOS/CMakeLists.txt (100%) rename {KWD => applications/acsdkXMOSAdapter}/XMOS/include/XMOS/XMOSKeywordDetector.h (100%) rename {KWD => applications/acsdkXMOSAdapter}/XMOS/src/CMakeLists.txt (100%) rename {KWD => applications/acsdkXMOSAdapter}/XMOS/src/XMOSKeywordDetector.cpp (100%) diff --git a/KWD/XMOS/GPIO/CMakeLists.txt b/applications/acsdkXMOSAdapter/GPIO/CMakeLists.txt similarity index 100% rename from KWD/XMOS/GPIO/CMakeLists.txt rename to applications/acsdkXMOSAdapter/GPIO/CMakeLists.txt diff --git a/KWD/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h b/applications/acsdkXMOSAdapter/GPIO/include/GPIO/GPIOKeywordDetector.h similarity index 100% rename from KWD/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h rename to applications/acsdkXMOSAdapter/GPIO/include/GPIO/GPIOKeywordDetector.h diff --git a/KWD/XMOS/GPIO/src/CMakeLists.txt b/applications/acsdkXMOSAdapter/GPIO/src/CMakeLists.txt similarity index 100% rename from KWD/XMOS/GPIO/src/CMakeLists.txt rename to applications/acsdkXMOSAdapter/GPIO/src/CMakeLists.txt diff --git a/KWD/XMOS/GPIO/src/GPIOKeywordDetector.cpp b/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp similarity index 100% rename from KWD/XMOS/GPIO/src/GPIOKeywordDetector.cpp rename to applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp diff --git a/KWD/XMOS/HID/CMakeLists.txt b/applications/acsdkXMOSAdapter/HID/CMakeLists.txt similarity index 100% rename from KWD/XMOS/HID/CMakeLists.txt rename to applications/acsdkXMOSAdapter/HID/CMakeLists.txt diff --git a/KWD/XMOS/HID/include/HID/HIDKeywordDetector.h b/applications/acsdkXMOSAdapter/HID/include/HID/HIDKeywordDetector.h similarity index 100% rename from KWD/XMOS/HID/include/HID/HIDKeywordDetector.h rename to applications/acsdkXMOSAdapter/HID/include/HID/HIDKeywordDetector.h diff --git a/KWD/XMOS/HID/src/CMakeLists.txt b/applications/acsdkXMOSAdapter/HID/src/CMakeLists.txt similarity index 100% rename from KWD/XMOS/HID/src/CMakeLists.txt rename to applications/acsdkXMOSAdapter/HID/src/CMakeLists.txt diff --git a/KWD/XMOS/HID/src/HIDKeywordDetector.cpp b/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp similarity index 100% rename from KWD/XMOS/HID/src/HIDKeywordDetector.cpp rename to applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp diff --git a/KWD/XMOS/CMakeLists.txt b/applications/acsdkXMOSAdapter/XMOS/CMakeLists.txt similarity index 100% rename from KWD/XMOS/CMakeLists.txt rename to applications/acsdkXMOSAdapter/XMOS/CMakeLists.txt diff --git a/KWD/XMOS/include/XMOS/XMOSKeywordDetector.h b/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h similarity index 100% rename from KWD/XMOS/include/XMOS/XMOSKeywordDetector.h rename to applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h diff --git a/KWD/XMOS/src/CMakeLists.txt b/applications/acsdkXMOSAdapter/XMOS/src/CMakeLists.txt similarity index 100% rename from KWD/XMOS/src/CMakeLists.txt rename to applications/acsdkXMOSAdapter/XMOS/src/CMakeLists.txt diff --git a/KWD/XMOS/src/XMOSKeywordDetector.cpp b/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp similarity index 100% rename from KWD/XMOS/src/XMOSKeywordDetector.cpp rename to applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp From 667ba47b91339dd350a1134f68d583b828c11330 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 09:07:34 +0100 Subject: [PATCH 04/60] Remove unnecessary files --- KWD/CMakeLists.txt | 25 ------------------- KWD/KWDProvider/src/CMakeLists.txt | 22 ---------------- .../src/KeywordDetectorProvider.cpp | 0 3 files changed, 47 deletions(-) delete mode 100644 KWD/CMakeLists.txt delete mode 100644 KWD/KWDProvider/src/CMakeLists.txt delete mode 100644 KWD/KWDProvider/src/KeywordDetectorProvider.cpp diff --git a/KWD/CMakeLists.txt b/KWD/CMakeLists.txt deleted file mode 100644 index e6d3c180f7..0000000000 --- a/KWD/CMakeLists.txt +++ /dev/null @@ -1,25 +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(HID_KEY_WORD_DETECTOR) - add_subdirectory("XMOS") -endif() -if(GPIO_KEY_WORD_DETECTOR) - add_subdirectory("XMOS") -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 8070630b2e..0000000000 --- a/KWD/KWDProvider/src/CMakeLists.txt +++ /dev/null @@ -1,22 +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() - -if(HID_KEY_WORD_DETECTOR) - target_link_libraries(KeywordDetectorProvider XMOS HID) -endif() - -if(GPIO_KEY_WORD_DETECTOR) - target_link_libraries(KeywordDetectorProvider XMOS GPIO) -endif() - -# install target -asdk_install() diff --git a/KWD/KWDProvider/src/KeywordDetectorProvider.cpp b/KWD/KWDProvider/src/KeywordDetectorProvider.cpp deleted file mode 100644 index e69de29bb2..0000000000 From e5c63e48a14c297f40424585dde3c3633629fe78 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 09:14:06 +0100 Subject: [PATCH 05/60] Update XMOS adapter classes to comply with v1.26.0 --- .../acsdkXMOSAdapter/GPIO/CMakeLists.txt | 5 ++ .../GPIO/include/GPIO/GPIOKeywordDetector.h | 28 +++++++-- .../acsdkXMOSAdapter/GPIO/src/CMakeLists.txt | 13 ++-- .../GPIO/src/GPIOKeywordDetector.cpp | 38 ++++++++++-- .../acsdkXMOSAdapter/HID/CMakeLists.txt | 5 ++ .../HID/include/HID/HIDKeywordDetector.h | 30 +++++++-- .../acsdkXMOSAdapter/HID/src/CMakeLists.txt | 13 ++-- .../HID/src/HIDKeywordDetector.cpp | 40 +++++++++--- .../acsdkXMOSAdapter/XMOS/CMakeLists.txt | 11 ++-- .../XMOS/include/XMOS/XMOSKeywordDetector.h | 61 +++++++++++++++---- .../acsdkXMOSAdapter/XMOS/src/CMakeLists.txt | 12 ++-- .../XMOS/src/XMOSKeywordDetector.cpp | 22 +++---- cmakeBuild/cmake/KeywordDetector.cmake | 15 +++++ 13 files changed, 230 insertions(+), 63 deletions(-) diff --git a/applications/acsdkXMOSAdapter/GPIO/CMakeLists.txt b/applications/acsdkXMOSAdapter/GPIO/CMakeLists.txt index 90f204835c..7828ffab8e 100644 --- a/applications/acsdkXMOSAdapter/GPIO/CMakeLists.txt +++ b/applications/acsdkXMOSAdapter/GPIO/CMakeLists.txt @@ -3,4 +3,9 @@ project(GPIO LANGUAGES CXX) include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +set(KWD_ADAPTER_REGISTRATION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/acsdkKWDProvider/src/GPIORegistration.cpp" PARENT_SCOPE) +set(KWD_COMPONENT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/acsdkKWD/src/KWDComponent.cpp" PARENT_SCOPE) + +set(TARGET_KWD_LIB "GPIO" PARENT_SCOPE) + add_subdirectory("src") diff --git a/applications/acsdkXMOSAdapter/GPIO/include/GPIO/GPIOKeywordDetector.h b/applications/acsdkXMOSAdapter/GPIO/include/GPIO/GPIOKeywordDetector.h index 369fa61e70..c92b6f1924 100644 --- a/applications/acsdkXMOSAdapter/GPIO/include/GPIO/GPIOKeywordDetector.h +++ b/applications/acsdkXMOSAdapter/GPIO/include/GPIO/GPIOKeywordDetector.h @@ -42,6 +42,26 @@ using namespace avsCommon::sdkInterfaces; class GPIOKeywordDetector : public XMOSKeywordDetector { public: /** + * Creates a @c GPIOKeywordDetector. + * + * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + * @return A new @c GPIOKeywordDetector, 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, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * @deprecated * Creates a @c GPIOKeywordDetector. * * @param stream The stream of audio data. This should be formatted in LPCM encoded with 16 bits per sample and @@ -72,15 +92,15 @@ class GPIOKeywordDetector : public XMOSKeywordDetector { * @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 the cloud at a time. This was the amount used by * Sensory in example code. */ GPIOKeywordDetector( 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)); diff --git a/applications/acsdkXMOSAdapter/GPIO/src/CMakeLists.txt b/applications/acsdkXMOSAdapter/GPIO/src/CMakeLists.txt index f5d9033c2d..a746826c8e 100644 --- a/applications/acsdkXMOSAdapter/GPIO/src/CMakeLists.txt +++ b/applications/acsdkXMOSAdapter/GPIO/src/CMakeLists.txt @@ -1,13 +1,18 @@ add_definitions("-DACSDK_LOG_MODULE=GPIOKeywordDetector") -add_library(GPIO SHARED + +add_library(GPIO GPIOKeywordDetector.cpp) target_include_directories(GPIO PUBLIC - "${KWD_SOURCE_DIR}/include" - "${XMOS_SOURCE_DIR}/include" + "${GPIO_SOURCE_DIR}/../XMOS/include" "${GPIO_SOURCE_DIR}/include") -target_link_libraries(GPIO XMOS KWD AVSCommon wiringPi) +target_link_libraries(GPIO + XMOS + acsdkKWDImplementations + acsdkKWDInterfaces + AVSCommon + wiringPi) # install target asdk_install() diff --git a/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp b/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp index 79bb25e414..1509e12b8a 100644 --- a/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp +++ b/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp @@ -22,7 +22,9 @@ #include #include +#include #include + #include "GPIO/GPIOKeywordDetector.h" namespace alexaClientSDK { @@ -94,26 +96,52 @@ bool GPIOKeywordDetector::openDevice() { return true; } +// Deprecated create method. std::unique_ptr GPIOKeywordDetector::create( std::shared_ptr stream, avsCommon::utils::AudioFormat audioFormat, std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers, 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, + msToPushPerIteration); +} + +std::unique_ptr GPIOKeywordDetector::create( + std::shared_ptr stream, + const std::shared_ptr& audioFormat, + const std::shared_ptr keywordNotifier, + const std::shared_ptr keywordDetectorStateNotifier, + 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; } std::unique_ptr detector(new GPIOKeywordDetector( - stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat)); + stream, keywordNotifier, keywordDetectorStateNotifier, *audioFormat)); if (!detector->init()) { ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); @@ -125,11 +153,11 @@ std::unique_ptr GPIOKeywordDetector::create( GPIOKeywordDetector::GPIOKeywordDetector( 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) : - XMOSKeywordDetector(stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat, msToPushPerIteration) { + XMOSKeywordDetector(stream, keywordNotifier, keywordDetectorStateNotifier, audioFormat, msToPushPerIteration) { } GPIOKeywordDetector::~GPIOKeywordDetector() { diff --git a/applications/acsdkXMOSAdapter/HID/CMakeLists.txt b/applications/acsdkXMOSAdapter/HID/CMakeLists.txt index 5df9f258f7..1d19fe8ce1 100644 --- a/applications/acsdkXMOSAdapter/HID/CMakeLists.txt +++ b/applications/acsdkXMOSAdapter/HID/CMakeLists.txt @@ -3,4 +3,9 @@ project(HID LANGUAGES CXX) include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) +set(KWD_ADAPTER_REGISTRATION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/acsdkKWDProvider/src/HIDRegistration.cpp" PARENT_SCOPE) +set(KWD_COMPONENT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/acsdkKWD/src/KWDComponent.cpp" PARENT_SCOPE) + +set(TARGET_KWD_LIB "HID" PARENT_SCOPE) + add_subdirectory("src") diff --git a/applications/acsdkXMOSAdapter/HID/include/HID/HIDKeywordDetector.h b/applications/acsdkXMOSAdapter/HID/include/HID/HIDKeywordDetector.h index c190c3ef72..6920415d02 100644 --- a/applications/acsdkXMOSAdapter/HID/include/HID/HIDKeywordDetector.h +++ b/applications/acsdkXMOSAdapter/HID/include/HID/HIDKeywordDetector.h @@ -43,6 +43,26 @@ using namespace avsCommon::sdkInterfaces; class HIDKeywordDetector : public XMOSKeywordDetector { public: /** + * Creates a @c HIDKeywordDetector. + * + * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + * @return A new @c HIDKeywordDetector, 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, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * @deprecated * Creates a @c HIDKeywordDetector. * * @param stream The stream of audio data. This should be formatted in LPCM encoded with 16 bits per sample and @@ -55,7 +75,7 @@ class HIDKeywordDetector : public XMOSKeywordDetector { * @return A new @c HIDKeywordDetector, 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, @@ -73,15 +93,15 @@ class HIDKeywordDetector : public XMOSKeywordDetector { * @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 the cloud at a time. This was the amount used by * Sensory in example code. */ HIDKeywordDetector( 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)); diff --git a/applications/acsdkXMOSAdapter/HID/src/CMakeLists.txt b/applications/acsdkXMOSAdapter/HID/src/CMakeLists.txt index 243b37bbfa..6868f168ff 100644 --- a/applications/acsdkXMOSAdapter/HID/src/CMakeLists.txt +++ b/applications/acsdkXMOSAdapter/HID/src/CMakeLists.txt @@ -5,15 +5,20 @@ pkg_check_modules(libevdev REQUIRED libevdev) find_package(PkgConfig) pkg_check_modules(libusb-1.0 REQUIRED libusb-1.0) -add_library(HID SHARED +add_library(HID HIDKeywordDetector.cpp) target_include_directories(HID PUBLIC - "${KWD_SOURCE_DIR}/include" - "${XMOS_SOURCE_DIR}/include" + "${HID_SOURCE_DIR}/../XMOS/include" "${HID_SOURCE_DIR}/include") -target_link_libraries(HID KWD AVSCommon evdev usb-1.0) +target_link_libraries(HID + XMOS + acsdkKWDImplementations + acsdkKWDInterfaces + AVSCommon + evdev + usb-1.0) # install target asdk_install() diff --git a/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp b/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp index 297f7097a5..6c5981df6e 100644 --- a/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp +++ b/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021 XMOS LIMITED. This Software is subject to the terms of the +// Copyright (c) 2021-2022 XMOS LIMITED. This Software is subject to the terms of the // XMOS Public License: Version 1 /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. @@ -21,6 +21,7 @@ #include #include +#include #include #include "HID/HIDKeywordDetector.h" @@ -138,26 +139,52 @@ bool HIDKeywordDetector::openDevice() { return true; } +// Deprecated create method. std::unique_ptr HIDKeywordDetector::create( std::shared_ptr stream, avsCommon::utils::AudioFormat audioFormat, std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers, 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, + msToPushPerIteration); +} + +std::unique_ptr HIDKeywordDetector::create( + std::shared_ptr stream, + const std::shared_ptr& audioFormat, + const std::shared_ptr keywordNotifier, + const std::shared_ptr keywordDetectorStateNotifier, + 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; } std::unique_ptr detector(new HIDKeywordDetector( - stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat)); + stream, keywordNotifier, keywordDetectorStateNotifier, *audioFormat)); if (!detector->init()) { ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); @@ -169,17 +196,16 @@ std::unique_ptr HIDKeywordDetector::create( HIDKeywordDetector::HIDKeywordDetector( 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) : - XMOSKeywordDetector(stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat, msToPushPerIteration) { + XMOSKeywordDetector(stream, keywordNotifier, keywordDetectorStateNotifier, audioFormat, msToPushPerIteration) { } HIDKeywordDetector::~HIDKeywordDetector() { } - bool HIDKeywordDetector::init() { if (XMOSKeywordDetector::init()) { m_detectionThread = std::thread(&HIDKeywordDetector::detectionLoop, this); diff --git a/applications/acsdkXMOSAdapter/XMOS/CMakeLists.txt b/applications/acsdkXMOSAdapter/XMOS/CMakeLists.txt index 0a63d121ce..2fbf1962b2 100644 --- a/applications/acsdkXMOSAdapter/XMOS/CMakeLists.txt +++ b/applications/acsdkXMOSAdapter/XMOS/CMakeLists.txt @@ -1,11 +1,8 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(XMOS LANGUAGES CXX) -add_subdirectory("src") +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +# set(TARGET_KWD_LIB "acsdkXMOSAdapter" PARENT_SCOPE) -if(HID_KEY_WORD_DETECTOR) - add_subdirectory("HID") -endif() -if(GPIO_KEY_WORD_DETECTOR) - add_subdirectory("GPIO") -endif() +add_subdirectory("src") diff --git a/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h b/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h index 3ada07bb4c..bba47e09ef 100644 --- a/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h +++ b/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h @@ -22,14 +22,14 @@ #include #include #include -#include -#include +#include +#include +#include #include -#include #include - -#include "KWD/AbstractKeywordDetector.h" +#include +#include namespace alexaClientSDK { namespace kwd { @@ -48,12 +48,31 @@ using namespace avsCommon::avs; using namespace avsCommon::sdkInterfaces; // A specialization of a KeyWordEngine, where a trigger comes from an external XMOS device -class XMOSKeywordDetector : public AbstractKeywordDetector { +class XMOSKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDetector { -protected: +public: /** - * Constructor. + * Creates an @c XMOSKeywordDetector + * + * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + */ + static std::unique_ptr create( + const std::shared_ptr stream, + const std::shared_ptr& audioFormat, + std::shared_ptr keyWordNotifier, + std::shared_ptr KeyWordDetectorStateNotifier, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * @deprecated + * Creates an @c XMOSKeywordDetector * * @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. @@ -62,12 +81,13 @@ class XMOSKeywordDetector : public AbstractKeywordDetector { * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. * @param msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by * Sensory in example code. + * @return A new @c XMOSKeywordDetector, or @c nullptr if the operation failed. */ - XMOSKeywordDetector( - std::shared_ptr stream, + static std::unique_ptr create( + const std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers, - avsCommon::utils::AudioFormat audioFormat, std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); /** @@ -75,6 +95,25 @@ class XMOSKeywordDetector : public AbstractKeywordDetector { */ ~XMOSKeywordDetector(); +protected: + /** + * Constructor. + * + * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + */ + XMOSKeywordDetector( + std::shared_ptr stream, + const std::shared_ptr keywordNotifier, + const std::shared_ptr KeywordDetectorStateNotifier, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + /** * Initializes the stream reader, sets up the connection to the device, and kicks off thread to begin reading * the audio stream. This function should only be called once with each new @c XMOSKeywordDetector. diff --git a/applications/acsdkXMOSAdapter/XMOS/src/CMakeLists.txt b/applications/acsdkXMOSAdapter/XMOS/src/CMakeLists.txt index 1f37dde4a0..5aff0ce737 100644 --- a/applications/acsdkXMOSAdapter/XMOS/src/CMakeLists.txt +++ b/applications/acsdkXMOSAdapter/XMOS/src/CMakeLists.txt @@ -1,12 +1,14 @@ -add_definitions("-DACSDK_LOG_MODULE=XMOSKeywordDetector") -add_library(XMOS SHARED +add_definitions("-DACSDK_LOG_MODULE=XMOS") +add_library(XMOS XMOSKeywordDetector.cpp) target_include_directories(XMOS PUBLIC - "${KWD_SOURCE_DIR}/include" - "${XMOS_SOURCE_DIR}/include") + "${XMOS_SOURCE_DIR}/include") -target_link_libraries(XMOS KWD AVSCommon) +target_link_libraries(XMOS + acsdkKWDImplementations + acsdkKWDInterfaces + AVSCommon) # install target asdk_install() diff --git a/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp b/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp index e37713c1b9..576a2c2c73 100644 --- a/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp +++ b/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp @@ -30,17 +30,6 @@ static const std::string TAG("XMOSKeywordDetector"); */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) -XMOSKeywordDetector::XMOSKeywordDetector( - std::shared_ptr stream, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - avsCommon::utils::AudioFormat audioFormat, - std::chrono::milliseconds msToPushPerIteration) : - AbstractKeywordDetector(keyWordObservers, keyWordDetectorStateObservers), - m_stream{stream}, - m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { -} - XMOSKeywordDetector::~XMOSKeywordDetector() { m_isShuttingDown = true; if (m_detectionThread.joinable()) @@ -49,6 +38,17 @@ XMOSKeywordDetector::~XMOSKeywordDetector() { m_readAudioThread.join(); } +XMOSKeywordDetector::XMOSKeywordDetector( + std::shared_ptr stream, + const std::shared_ptr keywordNotifier, + const std::shared_ptr KeywordDetectorStateNotifier, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration): + AbstractKeywordDetector(keywordNotifier, KeywordDetectorStateNotifier), + m_stream{stream}, + m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { +} + bool XMOSKeywordDetector::init() { if (!openDevice()) { ACSDK_ERROR(LX("initFailed").d("reason", "openDeviceFailed")); diff --git a/cmakeBuild/cmake/KeywordDetector.cmake b/cmakeBuild/cmake/KeywordDetector.cmake index b542261660..5b1d10e8ce 100644 --- a/cmakeBuild/cmake/KeywordDetector.cmake +++ b/cmakeBuild/cmake/KeywordDetector.cmake @@ -94,6 +94,13 @@ if(GPIO_KEY_WORD_DETECTOR) add_definitions(-DKWD) add_definitions(-DKWD_GPIO) set(KWD ON) + + # If XMOS KWD enabled add XMOSAdapter to extension paths to include with project. + set(EXTENSION_PATHS "${PROJECT_SOURCE_DIR}/applications/acsdkXMOSAdapter/XMOS;${EXTENSION_PATHS}" CACHE STRING + "Adding XMOSAdapter to the ExtensionPaths" FORCE) + # If XMOS GPIO KWD enabled add GPIO XMOSAdapter to extension paths to include with project. + set(EXTENSION_PATHS "${PROJECT_SOURCE_DIR}/applications/acsdkXMOSAdapter/GPIO;${EXTENSION_PATHS}" CACHE STRING + "Adding XMOSAdapter GPIO to the ExtensionPaths" FORCE) endif() if(HID_KEY_WORD_DETECTOR) @@ -101,6 +108,14 @@ if(HID_KEY_WORD_DETECTOR) add_definitions(-DKWD) add_definitions(-DKWD_HID) set(KWD ON) + + # If XMOS KWD enabled add XMOSAdapter to extension paths to include with project. + set(EXTENSION_PATHS "${PROJECT_SOURCE_DIR}/applications/acsdkXMOSAdapter/XMOS;${EXTENSION_PATHS}" CACHE STRING + "Adding XMOSAdapter to the ExtensionPaths" FORCE) + # If XMOS HID KWD enabled add HID XMOSAdapter to extension paths to include with project. + set(EXTENSION_PATHS "${PROJECT_SOURCE_DIR}/applications/acsdkXMOSAdapter/HID;${EXTENSION_PATHS}" CACHE STRING + "Adding XMOSAdapter HID to the ExtensionPaths" FORCE) + endif() if(PI_HAT_CTRL) From d6dd2b0d8e914831585542553af9892e34a21f95 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 09:18:44 +0100 Subject: [PATCH 06/60] Remove old files --- .../XMOS/CMakeLists.txt | 11 - .../XMOS/GPIO/CMakeLists.txt | 0 .../GPIO/include/GPIO/GPIOKeywordDetector.h | 112 -------- .../XMOS/GPIO/src/CMakeLists.txt | 13 - .../XMOS/GPIO/src/GPIOKeywordDetector.cpp | 242 ---------------- .../XMOS/HID/CMakeLists.txt | 0 .../XMOS/HID/include/HID/HIDKeywordDetector.h | 116 -------- .../XMOS/HID/src/CMakeLists.txt | 19 -- .../XMOS/HID/src/HIDKeywordDetector.cpp | 261 ------------------ .../XMOS/include/XMOS/XMOSKeywordDetector.h | 129 --------- .../XMOS/src/CMakeLists.txt | 12 - .../XMOS/src/XMOSKeywordDetector.cpp | 97 ------- 12 files changed, 1012 deletions(-) delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/CMakeLists.txt delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/GPIO/CMakeLists.txt delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/CMakeLists.txt delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/GPIOKeywordDetector.cpp delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/HID/CMakeLists.txt delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/HID/include/HID/HIDKeywordDetector.h delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/HID/src/CMakeLists.txt delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/HID/src/HIDKeywordDetector.cpp delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/include/XMOS/XMOSKeywordDetector.h delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/src/CMakeLists.txt delete mode 100644 shared/KWD/acsdkKWDImplementations/XMOS/src/XMOSKeywordDetector.cpp diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/CMakeLists.txt deleted file mode 100644 index 0a63d121ce..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -project(XMOS LANGUAGES CXX) - -add_subdirectory("src") - -if(HID_KEY_WORD_DETECTOR) - add_subdirectory("HID") -endif() -if(GPIO_KEY_WORD_DETECTOR) - add_subdirectory("GPIO") -endif() diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/CMakeLists.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h b/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h deleted file mode 100644 index 369fa61e70..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/include/GPIO/GPIOKeywordDetector.h +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2021-2022 XMOS LIMITED. This Software is subject to the terms of the -// XMOS Public License: Version 1 -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES 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_KWD_XMOS_GPIO_INCLUDE_GPIO_GPIOKEYWORDDETECTOR_H_ -#define ALEXA_CLIENT_SDK_KWD_XMOS_GPIO_INCLUDE_GPIO_GPIOKEYWORDDETECTOR_H_ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "XMOS/XMOSKeywordDetector.h" - -namespace alexaClientSDK { -namespace kwd { - -using namespace avsCommon; -using namespace avsCommon::avs; -using namespace avsCommon::sdkInterfaces; - -// A specialization of a KeyWordEngine, where a trigger comes from GPIO -class GPIOKeywordDetector : public XMOSKeywordDetector { -public: - /** - * Creates a @c GPIOKeywordDetector. - * - * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by - * Sensory in example code. - * @return A new @c GPIOKeywordDetector, or @c nullptr if the operation failed. - */ - static std::unique_ptr create( - std::shared_ptr stream, - avsCommon::utils::AudioFormat audioFormat, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); - - /** - * Destructor. - */ - ~GPIOKeywordDetector(); - -private: - /** - * Constructor. - * - * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by - * Sensory in example code. - */ - GPIOKeywordDetector( - std::shared_ptr stream, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - avsCommon::utils::AudioFormat audioFormat, - std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); - - /** - * Initializes the stream reader, sets up the GPIO pin, and kicks off threads to begin processing data from - * the stream. This function should only be called once with each new @c GPIOKeywordDetector. - * - * @return @c true if the engine was initialized properly and @c false otherwise. - */ - bool init(); - - /** - * Open the I2C port connected to the device - * - * @return @c true if file descriptor for the connected device is correctly set - */ - bool openDevice(); - - /// Re-declaration of base class member function - void detectionLoop(); - - /// The file descriptor to access I2C port - int m_fileDescriptor; - -}; -} // namespace kwd -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_KWD_GPIO_INCLUDE_GPIO_GPIOKEYWORDDETECTOR_H_ diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/CMakeLists.txt deleted file mode 100644 index f5d9033c2d..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -add_definitions("-DACSDK_LOG_MODULE=GPIOKeywordDetector") -add_library(GPIO SHARED - GPIOKeywordDetector.cpp) - -target_include_directories(GPIO PUBLIC - "${KWD_SOURCE_DIR}/include" - "${XMOS_SOURCE_DIR}/include" - "${GPIO_SOURCE_DIR}/include") - -target_link_libraries(GPIO XMOS KWD AVSCommon wiringPi) - -# install target -asdk_install() diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/GPIOKeywordDetector.cpp b/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/GPIOKeywordDetector.cpp deleted file mode 100644 index 79bb25e414..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/GPIO/src/GPIOKeywordDetector.cpp +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) 2021-2022 XMOS LIMITED. This Software is subject to the terms of the -// XMOS Public License: Version 1 -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT 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 "GPIO/GPIOKeywordDetector.h" - -namespace alexaClientSDK { -namespace kwd { - -using namespace avsCommon::utils::logger; - -/// String to identify log entries originating from this file. -static const std::string TAG("GPIOKeywordDetector"); - -/** - * 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) - -/// GPIO pin to monitor: -// Wiring Pi pin 2 which corresponds to Physical/Board pin 13 and GPIO/BCM pin 27 -static const int GPIO_PIN = 2; - -/// The device name of the I2C port connected to the device. -static const char *DEVNAME = "/dev/i2c-1"; - -/// The address of the I2C port connected to the device. -static const unsigned char I2C_ADDRESS = 0x2C; - -/// The maximum size in bytes of the I2C transaction -static const int I2C_TRANSACTION_MAX_BYTES = 256; - -/// The resource ID of the XMOS control command. -static const int CONTROL_RESOURCE_ID = 0xE0; - -/// The command ID of the XMOS control command. -static const int CONTROL_CMD_ID = 0xAF; - -/// The length of the payload of the XMOS control command -/// one control byte plus 3 uint64_t values -static const int CONTROL_CMD_PAYLOAD_LEN = 25; - -bool GPIOKeywordDetector::openDevice() { - int rc = 0; - m_fileDescriptor = -1; - - setenv("WIRINGPI_GPIOMEM", "1", 1); - if (wiringPiSetup() < 0) { - ACSDK_ERROR(LX("openDeviceFailed").d("reason", "wiringPiSetup failed")); - return false; - } - pinMode(GPIO_PIN, INPUT); - - // Open port for reading and writing - if ((m_fileDescriptor = open(DEVNAME, O_RDWR)) < 0) { - ACSDK_ERROR(LX("openDeviceFailed") - .d("reason", "openFailed")); - perror( "" ); - return false; - } - // Set the port options and set the address of the device we wish to speak to - if ((rc = ioctl(m_fileDescriptor, I2C_SLAVE, I2C_ADDRESS)) < 0) { - ACSDK_ERROR(LX("openDeviceFailed") - .d("reason", "setI2CConfigurationFailed")); - perror( "" ); - return false; - } - - ACSDK_INFO(LX("openDeviceSuccess").d("port", I2C_ADDRESS)); - - return true; -} - -std::unique_ptr GPIOKeywordDetector::create( - std::shared_ptr stream, - avsCommon::utils::AudioFormat audioFormat, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - 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)) { - ACSDK_ERROR(LX("createFailed").d("reason", "endianMismatch")); - return nullptr; - } - - std::unique_ptr detector(new GPIOKeywordDetector( - stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat)); - - if (!detector->init()) { - ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); - return nullptr; - } - - return detector; -} - -GPIOKeywordDetector::GPIOKeywordDetector( - std::shared_ptr stream, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - avsCommon::utils::AudioFormat audioFormat, - std::chrono::milliseconds msToPushPerIteration) : - XMOSKeywordDetector(stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat, msToPushPerIteration) { -} - -GPIOKeywordDetector::~GPIOKeywordDetector() { -} - -bool GPIOKeywordDetector::init() { - if (XMOSKeywordDetector::init()) { - m_detectionThread = std::thread(&GPIOKeywordDetector::detectionLoop, this); - return true; - } - return false; -} - -void GPIOKeywordDetector::detectionLoop() { - notifyKeyWordDetectorStateObservers(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); - int oldGpioValue = HIGH; - - std::chrono::steady_clock::time_point prev_time; - std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); - - while (!m_isShuttingDown) { - auto currentIndex = m_streamReader->tell(); - - // Read gpio value - int gpioValue = digitalRead(GPIO_PIN); - - // Check if GPIO pin is changing from high to low - if (gpioValue == LOW && oldGpioValue == HIGH) - { - std::chrono::steady_clock::time_point current_time = std::chrono::steady_clock::now(); - ACSDK_DEBUG0(LX("detectionLoopGPIOevent").d("absoluteElapsedTime (ms)", std::chrono::duration_cast (current_time - start_time).count())); - - // Check if this is not the first GPIO event - if (prev_time != std::chrono::steady_clock::time_point()) { - ACSDK_DEBUG0(LX("detectionLoopGPIOevent").d("elapsedTimeFromPreviousEvent (ms)", std::chrono::duration_cast (current_time - prev_time).count())); - } - prev_time = current_time; - - // Retrieve device indexes using control message via USB - unsigned char payload[64]; - - uint8_t cmd_ret = 1; - - std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - int rc = 0; - while (cmd_ret!=0) { - // Do a repeated start (write followed by read with no stop bit) - unsigned char read_hdr[I2C_TRANSACTION_MAX_BYTES]; - read_hdr[0] = CONTROL_RESOURCE_ID; - read_hdr[1] = CONTROL_CMD_ID; - read_hdr[2] = (uint8_t)CONTROL_CMD_PAYLOAD_LEN; - - struct i2c_msg rdwr_msgs[2] = { - { // Start address - .addr = I2C_ADDRESS, - .flags = 0, // write - .len = 3, // this is always 3 - .buf = read_hdr - }, - { // Read buffer - .addr = I2C_ADDRESS, - .flags = I2C_M_RD, // read - .len = CONTROL_CMD_PAYLOAD_LEN, - .buf = payload - } - }; - - struct i2c_rdwr_ioctl_data rdwr_data = { - .msgs = rdwr_msgs, - .nmsgs = 2 - }; - - rc = ioctl( m_fileDescriptor, I2C_RDWR, &rdwr_data ); - - if ( rc < 0 ) { - ACSDK_ERROR(LX("detectionLoopControlCommandFailed").d("reason", rc)); - perror( "" ); - } - - cmd_ret = payload[0]; - } - std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); - ACSDK_DEBUG0(LX("detectionLoopControlCommand").d("time (us)", std::chrono::duration_cast (end - begin).count() )); - - // Read indexes - uint64_t currentDeviceIndex = readIndex(payload, 1); - uint64_t beginKWDeviceIndex = readIndex(payload, 9); - uint64_t endKWDeviceIndex = readIndex(payload, 17); - auto beginKWServerIndex = currentIndex - (currentDeviceIndex - beginKWDeviceIndex); - - // Send information to the server - notifyKeyWordObservers( - m_stream, - KEYWORD_STRING, - beginKWServerIndex, - currentIndex); - ACSDK_DEBUG0(LX("detectionLoopIndexes").d("hostCurrentIndex", currentIndex) - .d("deviceCurrentIndex", currentDeviceIndex) - .d("deviceKWEndIndex", endKWDeviceIndex) - .d("deviceKWBeginIndex", beginKWDeviceIndex) - .d("serverKWEndIndex", currentIndex) - .d("serverKWBeginIndex", beginKWServerIndex)); - } - oldGpioValue = gpioValue; - } - m_streamReader->close(); -} - -} // namespace kwd -} // namespace alexaClientSDK diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/HID/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/HID/CMakeLists.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/HID/include/HID/HIDKeywordDetector.h b/shared/KWD/acsdkKWDImplementations/XMOS/HID/include/HID/HIDKeywordDetector.h deleted file mode 100644 index c190c3ef72..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/HID/include/HID/HIDKeywordDetector.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2021-2022 XMOS LIMITED. This Software is subject to the terms of the -// XMOS Public License: Version 1 -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES 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_KWD_HID_INCLUDE_HID_HIDKEYWORDDETECTOR_H_ -#define ALEXA_CLIENT_SDK_KWD_HID_INCLUDE_HID_HIDKEYWORDDETECTOR_H_ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "XMOS/XMOSKeywordDetector.h" - -namespace alexaClientSDK { -namespace kwd { - -using namespace avsCommon; -using namespace avsCommon::avs; -using namespace avsCommon::sdkInterfaces; - -// A specialization of a KeyWordEngine, where a trigger comes from HID -class HIDKeywordDetector : public XMOSKeywordDetector { -public: - /** - * Creates a @c HIDKeywordDetector. - * - * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by - * Sensory in example code. - * @return A new @c HIDKeywordDetector, or @c nullptr if the operation failed. - */ - static std::unique_ptr create( - std::shared_ptr stream, - avsCommon::utils::AudioFormat audioFormat, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); - - /** - * Destructor. - */ - ~HIDKeywordDetector(); - -private: - /** - * Constructor. - * - * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by - * Sensory in example code. - */ - HIDKeywordDetector( - std::shared_ptr stream, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - avsCommon::utils::AudioFormat audioFormat, - std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); - - /** - * Initializes the stream reader, sets up the USB connection, and kicks off threads to begin processing data from - * the stream. This function should only be called once with each new @c HIDKeywordDetector. - * - * @return @c true if the engine was initialized properly and @c false otherwise. - */ - bool init(); - - /** - * Search for an USB device, open the connection and return the correct handlers - * - * @return @c true if device is found and handlers are correctly set - */ - bool openDevice(); - - /// Declaration of function from base class - void detectionLoop(); - - /// The device handler necessary for reading HID events - struct libevdev *m_evdev; - - //The device handler necessary for sending control commands - libusb_device_handle *m_devh; -}; - -} // namespace kwd -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_KWD_HID_INCLUDE_HID_HIDKEYWORDDETECTOR_H_ diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/HID/src/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/HID/src/CMakeLists.txt deleted file mode 100644 index 243b37bbfa..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/HID/src/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -add_definitions("-DACSDK_LOG_MODULE=HIDKeywordDetector") - -find_package(PkgConfig) -pkg_check_modules(libevdev REQUIRED libevdev) -find_package(PkgConfig) -pkg_check_modules(libusb-1.0 REQUIRED libusb-1.0) - -add_library(HID SHARED - HIDKeywordDetector.cpp) - -target_include_directories(HID PUBLIC - "${KWD_SOURCE_DIR}/include" - "${XMOS_SOURCE_DIR}/include" - "${HID_SOURCE_DIR}/include") - -target_link_libraries(HID KWD AVSCommon evdev usb-1.0) - -# install target -asdk_install() diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/HID/src/HIDKeywordDetector.cpp b/shared/KWD/acsdkKWDImplementations/XMOS/HID/src/HIDKeywordDetector.cpp deleted file mode 100644 index 297f7097a5..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/HID/src/HIDKeywordDetector.cpp +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) 2021 XMOS LIMITED. This Software is subject to the terms of the -// XMOS Public License: Version 1 -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT 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 "HID/HIDKeywordDetector.h" - -namespace alexaClientSDK { -namespace kwd { - -using namespace avsCommon::utils::logger; - -/// String to identify log entries originating from this file. -static const std::string TAG("HIDKeywordDetector"); - -/** - * 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) - -/// HID keycode to monitor: -static const char * HID_KEY_CODE = "KEY_T"; - -/// HID device directory path -static const std::string HID_DEVICE_DIR_PATH = "/dev/input/"; - -/// HID device name -static const std::string HID_DEVICE_NAME = "XMOS XVF3615 Voice Processor Keyboard"; - -/// USB Product ID of XMOS device -static const int USB_VENDOR_ID = 0x20B1; - -/// USB Product ID of XMOS device -static const int USB_PRODUCT_ID = 0x18; - -/// USB timeout for control transfer -static const int USB_TIMEOUT_MS = 500; - -/// The resource ID of the XMOS control command. -static const int CONTROL_RESOURCE_ID = 0xE0; - -/// The command ID of the XMOS control command. -static const int CONTROL_CMD_ID = 0xAF; - -/// The length of the payload of the XMOS control command -/// one control byte plus 3 uint64_t values -static const int CONTROL_CMD_PAYLOAD_LEN = 25; - -bool HIDKeywordDetector::openDevice() { - int rc = 1; - libusb_device **devs = NULL; - libusb_device *dev = NULL; - - ACSDK_INFO(LX("openDeviceOngoing") - .d("HIDDeviceName", HID_DEVICE_NAME) - .d("USBVendorID", USB_VENDOR_ID) - .d("USBProductID", USB_PRODUCT_ID)); - - // Find USB device for reading HID events - int fd = -1; - DIR *dir; - struct dirent *entry; - - //search all files in directory - dir = opendir(HID_DEVICE_DIR_PATH.c_str()); - if (dir) { - while ((entry = readdir(dir)) != NULL) { - std::string file_path(entry->d_name); - fd = open((HID_DEVICE_DIR_PATH+file_path).c_str(), O_RDONLY|O_NONBLOCK); - - // Do not check if command is successful, as the entries reported by readdir() are not all devices - rc = libevdev_new_from_fd(fd, &m_evdev); - if (!rc) { - if (libevdev_get_name(m_evdev)==HID_DEVICE_NAME) { - ACSDK_INFO(LX("openDeviceSuccess").d("reason", "Found HID device").d("path", HID_DEVICE_DIR_PATH+file_path)); - break; - } - } - fd = -1; - } - closedir(dir); //close directory - } - if (fd==-1) { - ACSDK_ERROR(LX("openDeviceFailed").d("reason", "HidDeviceNotFound")); - } - // Find USB device for sending control commands - int ret = libusb_init(NULL); - if (ret < 0) { - ACSDK_ERROR(LX("openDeviceFailed").d("reason", "initialiseLibUsbFailed")); - return false; - } - - int num_dev = libusb_get_device_list(NULL, &devs); - - for (int i = 0; i < num_dev; i++) { - struct libusb_device_descriptor desc; - libusb_get_device_descriptor(devs[i], &desc); - if (desc.idVendor == USB_VENDOR_ID && desc.idProduct == USB_PRODUCT_ID) { - dev = devs[i]; - break; - } - } - - if (dev == NULL) { - ACSDK_ERROR(LX("openDeviceFailed").d("reason", "UsbDeviceNotFound")); - return false; - } - - if (libusb_open(dev, &m_devh) < 0) { - ACSDK_ERROR(LX("openDeviceFailed").d("reason", "UsbDeviceNotOpened")); - return false; - } - - libusb_free_device_list(devs, 1); - ACSDK_INFO(LX("openDeviceSuccess").d("reason", "UsbDeviceOpened")); - return true; -} - -std::unique_ptr HIDKeywordDetector::create( - std::shared_ptr stream, - avsCommon::utils::AudioFormat audioFormat, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - 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)) { - ACSDK_ERROR(LX("createFailed").d("reason", "endianMismatch")); - return nullptr; - } - - std::unique_ptr detector(new HIDKeywordDetector( - stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat)); - - if (!detector->init()) { - ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); - return nullptr; - } - - return detector; -} - -HIDKeywordDetector::HIDKeywordDetector( - std::shared_ptr stream, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - avsCommon::utils::AudioFormat audioFormat, - std::chrono::milliseconds msToPushPerIteration) : - XMOSKeywordDetector(stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat, msToPushPerIteration) { -} - -HIDKeywordDetector::~HIDKeywordDetector() { -} - - -bool HIDKeywordDetector::init() { - if (XMOSKeywordDetector::init()) { - m_detectionThread = std::thread(&HIDKeywordDetector::detectionLoop, this); - return true; - } - return false; -} - -void HIDKeywordDetector::detectionLoop() { - notifyKeyWordDetectorStateObservers(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); - int rc = 1; - - std::chrono::steady_clock::time_point prev_time; - std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); - - while (!m_isShuttingDown) { - auto currentIndex = m_streamReader->tell(); - struct input_event ev; - rc = libevdev_next_event(m_evdev, LIBEVDEV_READ_FLAG_NORMAL, &ev); - - // wait for HID_KEY_CODE true event - if (rc == 0 && strcmp(libevdev_event_type_get_name(ev.type), "EV_KEY")==0 && \ - strcmp(libevdev_event_code_get_name(ev.type, ev.code), HID_KEY_CODE)==0 && \ - ev.value == 1) - { - std::chrono::steady_clock::time_point current_time = std::chrono::steady_clock::now(); - ACSDK_DEBUG0(LX("detectionLoopHIDevent").d("absoluteElapsedTime (ms)", std::chrono::duration_cast (current_time - start_time).count())); - - // Check if this is not the first HID event - if (prev_time != std::chrono::steady_clock::time_point()) { - ACSDK_DEBUG0(LX("detectionLoopHIDevent").d("elapsedTimeFromPreviousEvent (ms)", std::chrono::duration_cast (current_time - prev_time).count())); - } - prev_time = current_time; - - // Retrieve device indexes using control message via USB - unsigned char payload[64]; - - uint8_t cmd_ret = 1; - - std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - while (cmd_ret!=0) { - rc = libusb_control_transfer(m_devh, - LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, - 0, CONTROL_CMD_ID, CONTROL_RESOURCE_ID, payload, CONTROL_CMD_PAYLOAD_LEN, USB_TIMEOUT_MS); - - cmd_ret = payload[0]; - } - if (rc != CONTROL_CMD_PAYLOAD_LEN) { - ACSDK_ERROR(LX("detectionLoopControlCommand").d("reason", "USBControlTransferFailed")); - } - std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); - ACSDK_DEBUG0(LX("detectionLoopControlCommand").d("time (us)", std::chrono::duration_cast (end - begin).count() )); - - // Read indexes - uint64_t currentDeviceIndex = readIndex(payload, 1); - uint64_t beginKWDeviceIndex = readIndex(payload, 9); - uint64_t endKWDeviceIndex = readIndex(payload, 17); - - auto beginKWServerIndex = currentIndex - (currentDeviceIndex - beginKWDeviceIndex); - - // Send information to the server - notifyKeyWordObservers( - m_stream, - KEYWORD_STRING, - beginKWServerIndex, - currentIndex); - - ACSDK_DEBUG0(LX("detectionLoopIndexes").d("hostCurrentIndex", currentIndex) - .d("deviceCurrentIndex", currentDeviceIndex) - .d("deviceKWEndIndex", endKWDeviceIndex) - .d("deviceKWBeginIndex", beginKWDeviceIndex) - .d("serverKWEndIndex", currentIndex) - .d("serverKWBeginIndex", beginKWServerIndex)); - } - } -} - -} // namespace kwd -} // namespace alexaClientSDK diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/include/XMOS/XMOSKeywordDetector.h b/shared/KWD/acsdkKWDImplementations/XMOS/include/XMOS/XMOSKeywordDetector.h deleted file mode 100644 index 3ada07bb4c..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/include/XMOS/XMOSKeywordDetector.h +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2022 XMOS LIMITED. This Software is subject to the terms of the -// XMOS Public License: Version 1 -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES 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_KWD_XMOS_INCLUDE_XMOS_XMOSKEYWORDDETECTOR_H_ -#define ALEXA_CLIENT_SDK_KWD_XMOS_INCLUDE_XMOS_XMOSKEYWORDDETECTOR_H_ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "KWD/AbstractKeywordDetector.h" - -namespace alexaClientSDK { -namespace kwd { - -/// Keyword string -static const std::string KEYWORD_STRING = "alexa"; - -/// The number of hertz per kilohertz. -static const size_t HERTZ_PER_KILOHERTZ = 1000; - -/// The timeout to use for read calls to the SharedDataStream. -const std::chrono::milliseconds TIMEOUT_FOR_READ_CALLS = std::chrono::milliseconds(1000); - -using namespace avsCommon; -using namespace avsCommon::avs; -using namespace avsCommon::sdkInterfaces; - -// A specialization of a KeyWordEngine, where a trigger comes from an external XMOS device -class XMOSKeywordDetector : public AbstractKeywordDetector { - -protected: - - /** - * Constructor. - * - * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by - * Sensory in example code. - */ - XMOSKeywordDetector( - std::shared_ptr stream, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - avsCommon::utils::AudioFormat audioFormat, - std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); - - /** - * Destructor. - */ - ~XMOSKeywordDetector(); - - /** - * Initializes the stream reader, sets up the connection to the device, and kicks off thread to begin reading - * the audio stream. This function should only be called once with each new @c XMOSKeywordDetector. - * - * @return @c true if the engine was initialized properly and @c false otherwise. - */ - bool init(); - - /// Function to establish a connection with an XMOS device - virtual bool openDevice() = 0; - - /// The main function that reads data and feeds it into the engine. - virtual void detectionLoop() = 0; - - /// The main function that reads data and feeds it into the engine. - void readAudioLoop(); - - /** - * Read a specific index from the payload of the USB control message - * - * @param payload The data returned via control message - * @param start_index The index in the payload to start reading from - * @return value stored in payload - */ - uint64_t readIndex(uint8_t* payload, int start_index); - - /// Indicates whether the internal main loop should keep running. - std::atomic m_isShuttingDown; - - /// The stream of audio data. - const std::shared_ptr m_stream; - - /// The reader that will be used to read audio data from the stream. - std::shared_ptr m_streamReader; - - /// Internal thread that read audio samples - std::thread m_readAudioThread; - - /// Internal thread that monitors the external XMOS device - std::thread m_detectionThread; - - /** - * The max number of samples to push into the underlying engine per iteration. This will be determined based on the - * sampling rate of the audio data passed in. - */ - const size_t m_maxSamplesPerPush; -}; - -} // namespace kwd -} // namespace alexaClientSDK - -#endif // ALEXA_CLIENT_SDK_KWD_XMOS_INCLUDE_XMOS_XMOSKEYWORDDETECTOR_H_ diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/src/CMakeLists.txt b/shared/KWD/acsdkKWDImplementations/XMOS/src/CMakeLists.txt deleted file mode 100644 index 1f37dde4a0..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/src/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -add_definitions("-DACSDK_LOG_MODULE=XMOSKeywordDetector") -add_library(XMOS SHARED - XMOSKeywordDetector.cpp) - -target_include_directories(XMOS PUBLIC - "${KWD_SOURCE_DIR}/include" - "${XMOS_SOURCE_DIR}/include") - -target_link_libraries(XMOS KWD AVSCommon) - -# install target -asdk_install() diff --git a/shared/KWD/acsdkKWDImplementations/XMOS/src/XMOSKeywordDetector.cpp b/shared/KWD/acsdkKWDImplementations/XMOS/src/XMOSKeywordDetector.cpp deleted file mode 100644 index e37713c1b9..0000000000 --- a/shared/KWD/acsdkKWDImplementations/XMOS/src/XMOSKeywordDetector.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2022 XMOS LIMITED. This Software is subject to the terms of the -// XMOS Public License: Version 1 -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT 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 "XMOS/XMOSKeywordDetector.h" - -namespace alexaClientSDK { -namespace kwd { - -/// String to identify log entries originating from this file. -static const std::string TAG("XMOSKeywordDetector"); - -/** - * 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) - -XMOSKeywordDetector::XMOSKeywordDetector( - std::shared_ptr stream, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - avsCommon::utils::AudioFormat audioFormat, - std::chrono::milliseconds msToPushPerIteration) : - AbstractKeywordDetector(keyWordObservers, keyWordDetectorStateObservers), - m_stream{stream}, - m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { -} - -XMOSKeywordDetector::~XMOSKeywordDetector() { - m_isShuttingDown = true; - if (m_detectionThread.joinable()) - m_detectionThread.join(); - if (m_readAudioThread.joinable()) - m_readAudioThread.join(); -} - -bool XMOSKeywordDetector::init() { - if (!openDevice()) { - ACSDK_ERROR(LX("initFailed").d("reason", "openDeviceFailed")); - return false; - } - - m_streamReader = m_stream->createReader(AudioInputStream::Reader::Policy::BLOCKING); - if (!m_streamReader) { - ACSDK_ERROR(LX("initFailed").d("reason", "createStreamReaderFailed")); - return false; - } - - m_isShuttingDown = false; - m_readAudioThread = std::thread(&XMOSKeywordDetector::readAudioLoop, this); - return true; -} - -void XMOSKeywordDetector::readAudioLoop() { - std::vector audioDataToPush(m_maxSamplesPerPush); - bool didErrorOccur = false; - - while (!m_isShuttingDown) { - readFromStream( - m_streamReader, - m_stream, - audioDataToPush.data(), - audioDataToPush.size(), - TIMEOUT_FOR_READ_CALLS, - &didErrorOccur); - if (didErrorOccur) { - m_isShuttingDown = true; - } - } -} - -uint64_t XMOSKeywordDetector::readIndex(uint8_t* payload, int start_index) { - uint64_t u64value = 0; - // convert array of bytes into uint64_t value - memcpy(&u64value, &payload[start_index], sizeof(uint64_t)); - // swap bytes of uint64_t value - u64value = __bswap_64(u64value); - return u64value; -} - -} // namespace kwd -} // namespace alexaClientSDK From 7cbd59400d6c77eb272ed0ba927f04ed736af2db Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 09:20:27 +0100 Subject: [PATCH 07/60] Update setup scripts --- tools/Install/pi.sh | 38 ++++++++++++++++++++------------------ tools/Install/setup.sh | 15 +++++++++------ 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index be50ed3d28..ad7a4679cd 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -20,8 +20,8 @@ # if [ -z "$PLATFORM" ]; then - echo "You should run the setup.sh script." - exit 1 + echo "You should run the setup.sh script." + exit 1 fi show_help() { @@ -34,18 +34,18 @@ show_help() { } OPTIONS=GHh while getopts "$OPTIONS" opt ; do - case $opt in - G ) - GPIO_KEY_WORD_DETECTOR_FLAG="ON" - ;; - H ) - HID_KEY_WORD_DETECTOR_FLAG="ON" - ;; - h ) - show_help - exit 1 - ;; - esac + case $opt in + G ) + GPIO_KEY_WORD_DETECTOR_FLAG="ON" + ;; + H ) + HID_KEY_WORD_DETECTOR_FLAG="ON" + ;; + h ) + show_help + exit 1 + ;; + esac done SOUND_CONFIG="$HOME/.asoundrc" @@ -82,10 +82,12 @@ install_dependencies() { run_os_specifics() { build_port_audio build_curl - echo - echo "==============> TAP-TO-TALK IS ENABLED ==============" - echo - configure_sound + if [ [ -z $GPIO_KEY_WORD_DETECTOR_FLAG ] && [ -z $HID_KEY_WORD_DETECTOR_FLAG ] && [ -z $SENSORY_KEY_WORD_DETECTOR_FLAG ] ] + then + echo + echo "==============> TAP-TO-TALK IS ENABLED ==============" + echo + fi } configure_sound() { diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 06b0c015c6..d15dcf04d0 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -460,15 +460,19 @@ then cd $BUILD_PATH # remove -j2 option to allow building in Raspberry Pi3 make SampleApp - make PreviewAlexaClient - make all + + # The steps below are not needed for the XMOS setup + # make PreviewAlexaClient + # make all else cd $BUILD_PATH # remove -j2 option to allow building in Raspberry Pi3 make SampleApp - make PreviewAlexaClient - make all + + # The steps below are not needed for the XMOS setup + # make PreviewAlexaClient + # make all fi echo @@ -489,13 +493,12 @@ awk -v config="$GSTREAMER_CONFIG" 'NR==1,/{/{sub(/{/,config)}1' $TEMP_CONFIG_FIL # Enable the suggestedLatency parameter for portAudio # Generate the lines to append CUSTOMTAB=" " -append_lines_str="${CUSTOMTAB}${CUSTOMTAB}\"portAudio\":{\n${CUSTOMTAB}${CUSTOMTAB}${CUSTOMTAB}\"suggestedLatency\": 0.150\n${CUSTOMTAB}${CUSTOMTAB}}" +append_lines_str="${CUSTOMTAB}${CUSTOMTAB}\"portAudio\":{\n${CUSTOMTAB}${CUSTOMTAB}${CUSTOMTAB}\"suggestedLatency\": 0.150\n${CUSTOMTAB}${CUSTOMTAB}}," # Save string with multiple lines in a file, so that it can be appended in the json file echo -e "$append_lines_str" > $TEMP_TEXT_FILE # Replace and append text -sed -i -e "/displayCardsSupported/s/$/,/" $OUTPUT_CONFIG_FILE sed -i -e "/displayCardsSupported/r ${TEMP_TEXT_FILE}" $OUTPUT_CONFIG_FILE # Delete temp files From 97ff8759e85a634bcc0c61287401cbf52ea4e2d8 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 09:39:37 +0100 Subject: [PATCH 08/60] Add CHANGELOG version --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ae03ccf7b..8a360c07f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## ChangeLog +### Version 1.26.0 - November 14 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) + +**XMOS-only change** - version 0: + + * ported XMOS KWD adapters to latest version of the SDK + ### 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) From b66db605345db9863b5b4c7883a113e0f46a445d Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 09:46:09 +0100 Subject: [PATCH 09/60] Add missing files --- .../GPIO/acsdkKWD/src/KWDComponent.cpp | 54 +++++++++++++++++++ .../acsdkKWDProvider/src/GPIORegistration.cpp | 45 ++++++++++++++++ .../HID/acsdkKWD/src/KWDComponent.cpp | 54 +++++++++++++++++++ .../acsdkKWDProvider/src/HIDRegistration.cpp | 45 ++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 applications/acsdkXMOSAdapter/GPIO/acsdkKWD/src/KWDComponent.cpp create mode 100644 applications/acsdkXMOSAdapter/GPIO/acsdkKWDProvider/src/GPIORegistration.cpp create mode 100644 applications/acsdkXMOSAdapter/HID/acsdkKWD/src/KWDComponent.cpp create mode 100644 applications/acsdkXMOSAdapter/HID/acsdkKWDProvider/src/HIDRegistration.cpp diff --git a/applications/acsdkXMOSAdapter/GPIO/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkXMOSAdapter/GPIO/acsdkKWD/src/KWDComponent.cpp new file mode 100644 index 0000000000..68a1f67070 --- /dev/null +++ b/applications/acsdkXMOSAdapter/GPIO/acsdkKWD/src/KWDComponent.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 "acsdkKWD/KWDComponent.h" + +namespace alexaClientSDK { +namespace acsdkKWD { + +/// String to identify log entries originating from this file. +static const std::string TAG{"GPIOKWDComponent"}; + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +static std::shared_ptr createAbstractKeywordDetector( + const std::shared_ptr& stream, + const std::shared_ptr& audioFormat, + std::shared_ptr keywordNotifier, + std::shared_ptr keywordDetectorStateNotifier) { + + return kwd::GPIOKeywordDetector::create( + stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier); +}; + +KWDComponent getComponent() { + return acsdkManufactory::ComponentAccumulator<>() + .addRetainedFactory(createAbstractKeywordDetector) + .addRetainedFactory(acsdkKWDImplementations::KWDNotifierFactories::createKeywordDetectorStateNotifier) + .addRetainedFactory(acsdkKWDImplementations::KWDNotifierFactories::createKeywordNotifier); +} + +} // namespace acsdkKWD +} // namespace alexaClientSDK diff --git a/applications/acsdkXMOSAdapter/GPIO/acsdkKWDProvider/src/GPIORegistration.cpp b/applications/acsdkXMOSAdapter/GPIO/acsdkKWDProvider/src/GPIORegistration.cpp new file mode 100644 index 0000000000..7e16ec6985 --- /dev/null +++ b/applications/acsdkXMOSAdapter/GPIO/acsdkKWDProvider/src/GPIORegistration.cpp @@ -0,0 +1,45 @@ +/* + * 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 { + +std::unique_ptr createGPIOKWDAdapter( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers) { + + return kwd::GPIOKeywordDetector::create( + stream, audioFormat, keyWordObservers, keyWordDetectorStateObservers); +} + +/// The registration object to register the XMOS GPIO adapter's creation method. +static const KeywordDetectorProvider::KWDRegistration g_gpioAdapterRegistration(createGPIOKWDAdapter); + +} // namespace kwd +} // namespace alexaClientSDK diff --git a/applications/acsdkXMOSAdapter/HID/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkXMOSAdapter/HID/acsdkKWD/src/KWDComponent.cpp new file mode 100644 index 0000000000..fc050ca02b --- /dev/null +++ b/applications/acsdkXMOSAdapter/HID/acsdkKWD/src/KWDComponent.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 "acsdkKWD/KWDComponent.h" + +namespace alexaClientSDK { +namespace acsdkKWD { + +/// String to identify log entries originating from this file. +static const std::string TAG{"HIDKWDComponent"}; + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param event The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +static std::shared_ptr createAbstractKeywordDetector( + const std::shared_ptr& stream, + const std::shared_ptr& audioFormat, + std::shared_ptr keywordNotifier, + std::shared_ptr keywordDetectorStateNotifier) { + + return kwd::HIDKeywordDetector::create( + stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier); +}; + +KWDComponent getComponent() { + return acsdkManufactory::ComponentAccumulator<>() + .addRetainedFactory(createAbstractKeywordDetector) + .addRetainedFactory(acsdkKWDImplementations::KWDNotifierFactories::createKeywordDetectorStateNotifier) + .addRetainedFactory(acsdkKWDImplementations::KWDNotifierFactories::createKeywordNotifier); +} + +} // namespace acsdkKWD +} // namespace alexaClientSDK diff --git a/applications/acsdkXMOSAdapter/HID/acsdkKWDProvider/src/HIDRegistration.cpp b/applications/acsdkXMOSAdapter/HID/acsdkKWDProvider/src/HIDRegistration.cpp new file mode 100644 index 0000000000..6955a50446 --- /dev/null +++ b/applications/acsdkXMOSAdapter/HID/acsdkKWDProvider/src/HIDRegistration.cpp @@ -0,0 +1,45 @@ +/* + * 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 { + +std::unique_ptr createHIDKWDAdapter( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers) { + + return kwd::HIDKeywordDetector::create( + stream, audioFormat, keyWordObservers, keyWordDetectorStateObservers); +} + +/// The registration object to register the XMOS HID adapter's creation method. +static const KeywordDetectorProvider::KWDRegistration g_hidAdapterRegistration(createHIDKWDAdapter); + +} // namespace kwd +} // namespace alexaClientSDK From 035cab7f95e9a8e81cbf739622ec749123444640 Mon Sep 17 00:00:00 2001 From: lucianom Date: Tue, 28 Jun 2022 16:23:49 +0100 Subject: [PATCH 10/60] Set op point to config file --- Integration/AlexaClientSDKConfig.json | 3 ++- SampleApp/include/SampleApp/SampleApplication.h | 16 ---------------- SampleApp/src/SampleApplication.cpp | 7 ------- SampleApp/src/main.cpp | 7 ------- .../acsdkKWD/src/KWDComponent.cpp | 10 +++++++++- .../Sensory/SensoryKeywordDetector.h | 15 ++++++++++----- .../src/SensoryKeywordDetector.cpp | 7 ++----- .../AbstractKeywordDetector.h | 16 ---------------- 8 files changed, 23 insertions(+), 58 deletions(-) diff --git a/Integration/AlexaClientSDKConfig.json b/Integration/AlexaClientSDKConfig.json index da213f9dde..3d161cf2b1 100644 --- a/Integration/AlexaClientSDKConfig.json +++ b/Integration/AlexaClientSDKConfig.json @@ -77,7 +77,8 @@ "sampleApp": { "displayCardsSupported":true, "sensory": { - "modelFilePath": "${SDK_SENSORY_MODEL_FILE_PATH}" + "modelFilePath": "${SDK_SENSORY_MODEL_FILE_PATH}", + "snsrOperatingPoint": 12 } } } diff --git a/SampleApp/include/SampleApp/SampleApplication.h b/SampleApp/include/SampleApp/SampleApplication.h index ce072edce4..c8b0982a03 100644 --- a/SampleApp/include/SampleApp/SampleApplication.h +++ b/SampleApp/include/SampleApp/SampleApplication.h @@ -158,15 +158,6 @@ class SampleApplication { avsCommon::sdkInterfaces::ChannelVolumeInterface::Type speakerType); }; -#ifdef SENSORY_OP_POINT - /** - * Set Sensory operating point value - */ - static void setSensoryOpPoint(int value) { - m_sensoryOpPoint = value; - } -#endif - #ifdef XMOS_AVS_TESTS /** * Set flag to indicate if the audio is streamed from a file. @@ -346,13 +337,6 @@ class SampleApplication { std::shared_ptr m_lwaAdapter; #endif -#ifdef SENSORY_OP_POINT - /* - * Operating point of the Sensory KWD engine. - */ - static int m_sensoryOpPoint; -#endif - #ifdef XMOS_AVS_TESTS /** * Write to file instead of a real audio device. diff --git a/SampleApp/src/SampleApplication.cpp b/SampleApp/src/SampleApplication.cpp index 6803c5810a..ec287766ae 100644 --- a/SampleApp/src/SampleApplication.cpp +++ b/SampleApp/src/SampleApplication.cpp @@ -173,10 +173,6 @@ std::shared_ptr SampleApplication::create( const std::string& logLevel, std::shared_ptr diagnostics) { auto clientApplication = std::unique_ptr(new SampleApplication); -#ifdef SENSORY_OP_POINT - alexaClientSDK::kwd::AbstractKeywordDetector::setSensoryOpPoint(SampleApplication::m_sensoryOpPoint); -#endif #ifdef XMOS_AVS_TESTS alexaClientSDK::mediaPlayer::MediaPlayer::setIsFileStream(m_isFileStream); PortAudioMicrophoneWrapper::setIsFileStream(m_isFileStream); diff --git a/SampleApp/src/main.cpp b/SampleApp/src/main.cpp index 3ab4347d4e..812eaa4b85 100644 --- a/SampleApp/src/main.cpp +++ b/SampleApp/src/main.cpp @@ -54,9 +54,6 @@ bool usesOptStyleArgs(int argc, char* argv[]) { int main(int argc, char* argv[]) { std::vector configFiles; std::string logLevel; -#ifdef SENSORY_OP_POINT - int sensoryOpPoint = 5; -#endif #ifdef XMOS_AVS_TESTS bool isFileStream = false; #endif @@ -111,10 +108,6 @@ int main(int argc, char* argv[]) { do { -#ifdef SENSORY_OP_POINT - SampleApplication::setSensoryOpPoint(sensoryOpPoint); -#endif - #ifdef XMOS_AVS_TESTS SampleApplication::setIsFileStream(isFileStream); #endif diff --git a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp index 3e46d4705f..736833d0cd 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp @@ -37,6 +37,7 @@ static const std::string TAG{"SensoryKWDComponent"}; 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 const std::string SENSORY_SNSR_OPERATING_POINT("snsrOperatingPoint"); static std::shared_ptr createAbstractKeywordDetector( const std::shared_ptr& stream, @@ -44,17 +45,24 @@ static std::shared_ptr createA std::shared_ptr keywordNotifier, std::shared_ptr keywordDetectorStateNotifier) { std::string modelFilePath; + int snsrOperatingPoint; 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); + config.getUint32(SENSORY_SNSR_OPERATING_POINT, &snsrOperatingPoint); } if (modelFilePath.empty()) { ACSDK_ERROR(LX("createFailed").d("reason", "emptyModelFilePath")); return nullptr; } + if (snsrOperatingPoint==0) { + ACSDK_ERROR(LX("createFailed").d("reason", "zeroSnsrOperatingPoint")); + return nullptr; + } + return kwd::SensoryKeywordDetector::create( - stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier, modelFilePath); + stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier, modelFilePath, snsrOperatingPoint); }; KWDComponent getComponent() { diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 7a812dd4af..fbf10d2b8d 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -41,8 +41,8 @@ using namespace avsCommon::sdkInterfaces; class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDetector { public: /** - * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value under - * sampleApp + * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value + * and a snsrOperatingPoint 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. @@ -50,6 +50,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @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 snsrOperatingPoint The operating point of the SNSR. * @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 @@ -63,12 +64,13 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe std::shared_ptr keyWordNotifier, std::shared_ptr KeyWordDetectorStateNotifier, const std::string& modelFilePath, + const uint32_t& snsrOperatingPoint, std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); /** * @deprecated - * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value under - * sampleApp + * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value + * and a snsrOperatingPoint 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. @@ -76,6 +78,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @param keyWordObservers The observers to notify of keyword detections. * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. * @param modelFilePath The path to the model file. + * @param snsrOperatingPoint The operating point of the SNSR. * @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 @@ -89,6 +92,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers, const std::string& modelFilePath, + const uint32_t& snsrOperatingPoint, std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); /** @@ -123,9 +127,10 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * the stream. This function should only be called once with each new @c SensoryKeywordDetector. * * @param modelFilePath The path to the model file. + * @param snsrOperatingPoint The operating point of the SNSR. * @return @c true if the engine was initialized properly and @c false otherwise. */ - bool init(const std::string& modelFilePath); + bool init(const std::string& modelFilePath, const uint32_t& snsrOperatingPoint); /** * Sets up the runtime settings for a @c SnsrSession. This includes setting the callback handler and setting the diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index cbba5aa0f8..addbba81c1 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -25,10 +25,6 @@ namespace kwd { using namespace avsCommon::utils::logger; -#ifdef SENSORY_OP_POINT -int AbstractKeywordDetector::m_sensoryOpPoint = 0; -#endif - /// String to identify log entries originating from this file. static const std::string TAG("SensoryKeywordDetector"); @@ -339,7 +335,8 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { } #ifdef SENSORY_OP_POINT - result = snsrSetInt(*session, SNSR_OPERATING_POINT, AbstractKeywordDetector::m_sensoryOpPoint); + reses + ult = snsrSetInt(*session, SNSR_OPERATING_POINT, AbstractKeywordDetector::m_sensoryOpPoint); if (result != SNSR_RC_OK) { ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") diff --git a/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/AbstractKeywordDetector.h b/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/AbstractKeywordDetector.h index c292e95dd7..7e8231a3d8 100644 --- a/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/AbstractKeywordDetector.h +++ b/shared/KWD/acsdkKWDImplementations/include/acsdkKWDImplementations/AbstractKeywordDetector.h @@ -66,14 +66,6 @@ class AbstractKeywordDetector { * Destructor. */ virtual ~AbstractKeywordDetector() = default; -#ifdef SENSORY_OP_POINT - /** - * Set Sensory operating point value - */ - static void setSensoryOpPoint(int value) { - m_sensoryOpPoint = value; - } -#endif protected: /** @@ -150,14 +142,6 @@ class AbstractKeywordDetector { */ static bool isByteswappingRequired(avsCommon::utils::AudioFormat audioFormat); -#ifdef SENSORY_OP_POINT - - /* - * Operating point of the Sensory KWD engine. - */ - static int m_sensoryOpPoint; -#endif - private: /** * The notifier to notify observers of key word detections. From adcc0c68e0e235a09e4ff0081d554ea2c9c10fde Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 11:06:09 +0100 Subject: [PATCH 11/60] Protect changes with define --- .../acsdkKWD/src/KWDComponent.cpp | 15 ++++++++++-- .../Sensory/SensoryKeywordDetector.h | 10 +++++--- .../src/SensoryKeywordDetector.cpp | 23 ------------------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp index 736833d0cd..4e14efe0d3 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp @@ -37,32 +37,43 @@ static const std::string TAG{"SensoryKWDComponent"}; 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"); +#ifdef SENSORY_OP_POINT static const std::string SENSORY_SNSR_OPERATING_POINT("snsrOperatingPoint"); - +#endif // SENSORY_OP_POINT 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; +#ifdef SENSORY_OP_POINT int snsrOperatingPoint; +#endif // SENSORY_OP_POINT 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); +#ifdef SENSORY_OP_POINT config.getUint32(SENSORY_SNSR_OPERATING_POINT, &snsrOperatingPoint); +#endif // SENSORY_OP_POINT } if (modelFilePath.empty()) { ACSDK_ERROR(LX("createFailed").d("reason", "emptyModelFilePath")); return nullptr; } +#ifdef SENSORY_OP_POINT if (snsrOperatingPoint==0) { ACSDK_ERROR(LX("createFailed").d("reason", "zeroSnsrOperatingPoint")); return nullptr; } +#endif // SENSORY_OP_POINT return kwd::SensoryKeywordDetector::create( - stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier, modelFilePath, snsrOperatingPoint); + stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier, modelFilePath \ +#ifdef SENSORY_OP_POINT + , snsrOperatingPoint +#endif // SENSORY_OP_POINT + ); }; KWDComponent getComponent() { diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index fbf10d2b8d..6c3678d95b 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -42,7 +42,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe public: /** * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value - * and a snsrOperatingPoint under sampleApp + * and a snsrOperatingPoint (XMOS-only feature) 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. @@ -50,7 +50,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @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 snsrOperatingPoint The operating point of the SNSR. + * @param snsrOperatingPoint The operating point of the SNSR (XMOS-only feature). * @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 @@ -64,7 +64,9 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe std::shared_ptr keyWordNotifier, std::shared_ptr KeyWordDetectorStateNotifier, const std::string& modelFilePath, +#ifdef SENSORY_OP_POINT const uint32_t& snsrOperatingPoint, +#endif // SENSORY_OP_POINT std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); /** @@ -78,7 +80,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @param keyWordObservers The observers to notify of keyword detections. * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. * @param modelFilePath The path to the model file. - * @param snsrOperatingPoint The operating point of the SNSR. + * @param snsrOperatingPoint The operating point of the SNSR (XMOS-only feature). * @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 @@ -92,7 +94,9 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers, const std::string& modelFilePath, +#ifdef SENSORY_OP_POINT const uint32_t& snsrOperatingPoint, +#endif // SENSORY_OP_POINT std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); /** diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index addbba81c1..09e7f5dc4e 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -333,29 +333,6 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { .d("error", getSensoryDetails(*session, result))); return false; } - -#ifdef SENSORY_OP_POINT - reses - ult = snsrSetInt(*session, SNSR_OPERATING_POINT, AbstractKeywordDetector::m_sensoryOpPoint); - if (result != SNSR_RC_OK) - { - ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") - .d("reason", "setSnsrOperatingPointFailure") - .d("error", getSensoryDetails(*session, result))); - return false; - } - - int op=0; - result = snsrGetInt(*session, SNSR_OPERATING_POINT, &op); - if (result != SNSR_RC_OK) { - ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") - .d("reason", "gettingOperatingPointFaied") - .d("error", getSensoryDetails(*session, result))); - return false; - } - printf("Sensory operating point = %d\n",op); - return true; -#endif// SENSORY_OP_POINT } void SensoryKeywordDetector::detectionLoop() { From e39ba8d01317cd9a659c3070e1da5f2229eb14d3 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 17:15:10 +0100 Subject: [PATCH 12/60] Move common operations to base class --- .../GPIO/src/GPIOKeywordDetector.cpp | 15 ++---------- .../HID/src/HIDKeywordDetector.cpp | 16 ++----------- .../XMOS/include/XMOS/XMOSKeywordDetector.h | 16 +++++++++++++ .../XMOS/src/XMOSKeywordDetector.cpp | 23 +++++++++++++++++++ 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp b/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp index 1509e12b8a..c2720a11f3 100644 --- a/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp +++ b/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp @@ -103,23 +103,12 @@ std::unique_ptr GPIOKeywordDetector::create( std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers, 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, + createNotifier(keyWordObservers), + createStateNotifier(keyWordDetectorStateObservers), msToPushPerIteration); } diff --git a/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp b/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp index 6c5981df6e..96266d1fe2 100644 --- a/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp +++ b/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp @@ -146,23 +146,11 @@ std::unique_ptr HIDKeywordDetector::create( std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers, 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, + createNotifier(keyWordObservers), + createStateNotifier(keyWordDetectorStateObservers), msToPushPerIteration); } diff --git a/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h b/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h index bba47e09ef..ed57f2d397 100644 --- a/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h +++ b/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h @@ -114,6 +114,22 @@ class XMOSKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDetec avsCommon::utils::AudioFormat audioFormat, std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + /** + * Create a keyword notifier from the given keyword observers + * @param keyWordObservers The observers to notify of keyword detections. + * @return A new instance of @c KeywordNotifierInterface. + */ + static std::shared_ptr createNotifier( + std::unordered_set> keyWordObservers); + + /** + * Create a keyword state notifier from the given keyword detector state observers + * @param keyWordObservers The observers to notify of keyword detections. + * @return A new instance of @c KeywordDetectorStateNotifierInterface. + */ + static std::shared_ptr createStateNotifier( + std::unordered_set> keyWordDetectorStateObservers); + /** * Initializes the stream reader, sets up the connection to the device, and kicks off thread to begin reading * the audio stream. This function should only be called once with each new @c XMOSKeywordDetector. diff --git a/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp b/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp index 576a2c2c73..73f69d3f79 100644 --- a/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp +++ b/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp @@ -15,6 +15,7 @@ * permissions and limitations under the License. */ +#include #include "XMOS/XMOSKeywordDetector.h" namespace alexaClientSDK { @@ -49,6 +50,28 @@ XMOSKeywordDetector::XMOSKeywordDetector( m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { } +std::shared_ptr XMOSKeywordDetector::createNotifier( + std::unordered_set> keyWordObservers) { + // Create Notifier to be used instead of the observers. + auto keywordNotifier = acsdkKWDImplementations::KWDNotifierFactories::createKeywordNotifier(); + for (auto kwObserver : keyWordObservers) { + keywordNotifier->addObserver(kwObserver); + } + return keywordNotifier; +} + +std::shared_ptr XMOSKeywordDetector::createStateNotifier( + std::unordered_set> keyWordDetectorStateObservers) { + + // Create State Notifier to be used instead of the observers. + auto keywordDetectorStateNotifier = + acsdkKWDImplementations::KWDNotifierFactories::createKeywordDetectorStateNotifier(); + for (auto kwdStateObserver : keyWordDetectorStateObservers) { + keywordDetectorStateNotifier->addObserver(kwdStateObserver); + } + return keywordDetectorStateNotifier; +} + bool XMOSKeywordDetector::init() { if (!openDevice()) { ACSDK_ERROR(LX("initFailed").d("reason", "openDeviceFailed")); From 508e0b450dfafa677b30cf2e1072769b5758c4b1 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 29 Jun 2022 17:29:53 +0100 Subject: [PATCH 13/60] Move checks to base class function --- .../GPIO/src/GPIOKeywordDetector.cpp | 10 ---------- .../acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp | 10 ---------- .../XMOS/include/XMOS/XMOSKeywordDetector.h | 3 +++ .../XMOS/src/XMOSKeywordDetector.cpp | 13 +++++++++++++ 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp b/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp index c2720a11f3..3acf96b29e 100644 --- a/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp +++ b/applications/acsdkXMOSAdapter/GPIO/src/GPIOKeywordDetector.cpp @@ -118,16 +118,6 @@ std::unique_ptr GPIOKeywordDetector::create( const std::shared_ptr keywordNotifier, const std::shared_ptr keywordDetectorStateNotifier, 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)) { - ACSDK_ERROR(LX("createFailed").d("reason", "endianMismatch")); - return nullptr; - } std::unique_ptr detector(new GPIOKeywordDetector( stream, keywordNotifier, keywordDetectorStateNotifier, *audioFormat)); diff --git a/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp b/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp index 96266d1fe2..f2743964e7 100644 --- a/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp +++ b/applications/acsdkXMOSAdapter/HID/src/HIDKeywordDetector.cpp @@ -160,16 +160,6 @@ std::unique_ptr HIDKeywordDetector::create( const std::shared_ptr keywordNotifier, const std::shared_ptr keywordDetectorStateNotifier, 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)) { - ACSDK_ERROR(LX("createFailed").d("reason", "endianMismatch")); - return nullptr; - } std::unique_ptr detector(new HIDKeywordDetector( stream, keywordNotifier, keywordDetectorStateNotifier, *audioFormat)); diff --git a/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h b/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h index ed57f2d397..b6cecb35ba 100644 --- a/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h +++ b/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h @@ -162,6 +162,9 @@ class XMOSKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDetec /// The stream of audio data. const std::shared_ptr m_stream; + /// The format of that audio data. + const avsCommon::utils::AudioFormat m_audioFormat; + /// The reader that will be used to read audio data from the stream. std::shared_ptr m_streamReader; diff --git a/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp b/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp index 73f69d3f79..a742a55b43 100644 --- a/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp +++ b/applications/acsdkXMOSAdapter/XMOS/src/XMOSKeywordDetector.cpp @@ -47,6 +47,7 @@ XMOSKeywordDetector::XMOSKeywordDetector( std::chrono::milliseconds msToPushPerIteration): AbstractKeywordDetector(keywordNotifier, KeywordDetectorStateNotifier), m_stream{stream}, + m_audioFormat{audioFormat}, m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { } @@ -73,6 +74,18 @@ std::shared_ptr XMOS } bool XMOSKeywordDetector::init() { + + if (!m_stream) { + ACSDK_ERROR(LX("initFailed").d("reason", "nullStream")); + return false; + } + + // TODO: ACSDK-249 - Investigate cpu usage of converting bytes between endianness and if it's not too much, do it. + if (isByteswappingRequired(m_audioFormat)) { + ACSDK_ERROR(LX("initFailed").d("reason", "endianMismatch")); + return false; + } + if (!openDevice()) { ACSDK_ERROR(LX("initFailed").d("reason", "openDeviceFailed")); return false; From 76c8163a4e47a09b9c20437043e1063ca2d67563 Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 15:01:29 +0100 Subject: [PATCH 14/60] Remove old code --- tools/Install/setup.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index d15dcf04d0..7d724bb685 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -331,12 +331,8 @@ AUTOSTART_SESSION="avsrun" AUTOSTART_DIR=$HOME/.config/lxsession/LXDE-pi AUTOSTART=$AUTOSTART_DIR/autostart AVSRUN_CMD="lxterminal -t avsrun -e \"$BUILD_PATH/SampleApp/src/SampleApp $OUTPUT_CONFIG_FILE " -if [ -n "$SENSORY_KEY_WORD_DETECTOR_FLAG" ]; then - AVSRUN_CMD+="$THIRD_PARTY_PATH/alexa-rpi/models NONE 12 \$*" #$* is for passing any extra arguments to Sampleapp through .avsrun-startup.sh shell script -else - AVSRUN_CMD+="NONE" -fi -AVSRUN_CMD+="\" &" +AVSRUN_CMD+="NONE\" &" + STARTUP_SCRIPT=$CURRENT_DIR/.avsrun-startup.sh if [ ! -f $AUTOSTART ]; then mkdir -p $AUTOSTART_DIR From c4acfcce85c04c34bff74cbe50f5c09dc21d0fbe Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 15:16:06 +0100 Subject: [PATCH 15/60] Enable flag for Sensory --- tools/Install/pi.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index ad7a4679cd..47f7791f72 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -30,6 +30,7 @@ show_help() { echo 'Optional parameters' echo ' -G Flag to enable keyword detector on GPIO interrupt' echo ' -H Flag to enable keyword detector on HID event' + echo ' -S Flag to enable Sensory keyword detector' echo ' -h Display this help and exit' } OPTIONS=GHh @@ -41,6 +42,10 @@ while getopts "$OPTIONS" opt ; do H ) HID_KEY_WORD_DETECTOR_FLAG="ON" ;; + S ) + SENSORY_KEY_WORD_DETECTOR_FLAG="ON" + ;; + h ) show_help exit 1 From 9f9cab730d9f4b73d6fe293ace311a79f8172dc6 Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 15:24:43 +0100 Subject: [PATCH 16/60] Enable flag for Sensory --- tools/Install/pi.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index 47f7791f72..78c2d69e79 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -33,7 +33,7 @@ show_help() { echo ' -S Flag to enable Sensory keyword detector' echo ' -h Display this help and exit' } -OPTIONS=GHh +OPTIONS=GHSh while getopts "$OPTIONS" opt ; do case $opt in G ) From 8981465fc0caf9da8d487f531bdd2dc29042e9eb Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 18:59:22 +0100 Subject: [PATCH 17/60] Add test changes --- .../Sensory/SensoryKeywordDetector.h | 5 +- .../acsdkSensoryAdapter/src/CMakeLists.txt | 3 +- .../src/SensoryKeywordDetector.cpp | 171 ++---------------- 3 files changed, 23 insertions(+), 156 deletions(-) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 6c3678d95b..5ab78fab2f 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -15,7 +15,8 @@ #ifndef ACSDKSENSORYADAPTER_SENSORY_SENSORYKEYWORDDETECTOR_H_ #define ACSDKSENSORYADAPTER_SENSORY_SENSORYKEYWORDDETECTOR_H_ - +#define SnsrSession int* +#define SnsrRC int #include #include #include @@ -29,7 +30,7 @@ #include #include -#include "snsr.h" +//#include "snsr.h" namespace alexaClientSDK { namespace kwd { diff --git a/applications/acsdkSensoryAdapter/src/CMakeLists.txt b/applications/acsdkSensoryAdapter/src/CMakeLists.txt index 4acb6d91f7..688887e2e7 100644 --- a/applications/acsdkSensoryAdapter/src/CMakeLists.txt +++ b/applications/acsdkSensoryAdapter/src/CMakeLists.txt @@ -9,8 +9,7 @@ target_include_directories(SENSORY PUBLIC target_link_libraries(SENSORY acsdkKWDImplementations acsdkKWDInterfaces - AVSCommon - "${SENSORY_KEY_WORD_DETECTOR_LIB_PATH}") + AVSCommon) # install target asdk_install() diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 09e7f5dc4e..b4ae42bc7c 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -12,7 +12,7 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ - +#define SNSR_RC_OK 0 #include #include @@ -103,34 +103,14 @@ static bool isAudioFormatCompatibleWithSensory(avsCommon::utils::AudioFormat aud return true; } -/** - * Returns information about the ongoing sensory session. Primarily used to populate error messages. - * - * @param session The Sensory session handle. - * @param result The Sensory return code. - * - * @return The pertinent message about the sensory session in string format. - */ -static std::string getSensoryDetails(SnsrSession session, SnsrRC result) { - std::string message; - // It is recommended by Sensory to prefer snsrErrorDetail() over snsrRCMessage() as it provides more details. - if (session) { - message = snsrErrorDetail(session); - } else { - message = snsrRCMessage(result); - } - if (message.empty()) { - message = "Unrecognized error"; - } - return message; -} - +#define SNSR_RES_BEGIN_SAMPLE 1 SnsrRC SensoryKeywordDetector::keyWordDetectedCallback(SnsrSession s, const char* key, void* userData) { SensoryKeywordDetector* engine = static_cast(userData); - SnsrRC result; - const char* keyword; - double begin; - double end; + //SnsrRC result; + const char* keyword = ""; + double begin = 0; + double end = 0; +/* result = snsrGetDouble(s, SNSR_RES_BEGIN_SAMPLE, &begin); if (result != SNSR_RC_OK) { ACSDK_ERROR(LX("keyWordDetectedCallbackFailed") @@ -154,7 +134,7 @@ SnsrRC SensoryKeywordDetector::keyWordDetectedCallback(SnsrSession s, const char .d("error", getSensoryDetails(s, result))); return result; } - +*/ engine->notifyKeyWordObservers( engine->m_stream, keyword, @@ -171,6 +151,7 @@ std::unique_ptr SensoryKeywordDetector::create( std::unordered_set> keyWordDetectorStateObservers, const std::string& modelFilePath, + const uint32_t& snsrOperatingPoint, std::chrono::milliseconds msToPushPerIteration) { // Create Notifiers to be used instead of the observers. auto keywordNotifier = acsdkKWDImplementations::KWDNotifierFactories::createKeywordNotifier(); @@ -190,6 +171,7 @@ std::unique_ptr SensoryKeywordDetector::create( keywordNotifier, keywordDetectorStateNotifier, modelFilePath, + snsrOperatingPoint, msToPushPerIteration); } @@ -199,6 +181,7 @@ std::unique_ptr SensoryKeywordDetector::create( const std::shared_ptr keywordNotifier, const std::shared_ptr keywordDetectorStateNotifier, const std::string& modelFilePath, + const uint32_t& snsrOperatingPoint, std::chrono::milliseconds msToPushPerIteration) { if (!stream) { ACSDK_ERROR(LX("createFailed").d("reason", "nullStream")); @@ -217,7 +200,7 @@ std::unique_ptr SensoryKeywordDetector::create( std::unique_ptr detector(new SensoryKeywordDetector( stream, keywordNotifier, keywordDetectorStateNotifier, *audioFormat, msToPushPerIteration)); - if (!detector->init(modelFilePath)) { + if (!detector->init(modelFilePath, snsrOperatingPoint)) { ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); return nullptr; } @@ -230,7 +213,7 @@ SensoryKeywordDetector::~SensoryKeywordDetector() { if (m_detectionThread.joinable()) { m_detectionThread.join(); } - snsrRelease(m_session); + //snsrRelease(m_session); } SensoryKeywordDetector::SensoryKeywordDetector( @@ -245,61 +228,12 @@ SensoryKeywordDetector::SensoryKeywordDetector( m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { } -bool SensoryKeywordDetector::init(const std::string& modelFilePath) { +bool SensoryKeywordDetector::init(const std::string& modelFilePath, const uint32_t& snsrOperatingPoint) { m_streamReader = m_stream->createReader(AudioInputStream::Reader::Policy::BLOCKING); if (!m_streamReader) { ACSDK_ERROR(LX("initFailed").d("reason", "createStreamReaderFailed")); return false; } - - // Allocate the Sensory library handle - SnsrRC result = snsrNew(&m_session); - if (result != SNSR_RC_OK) { - ACSDK_ERROR(LX("initFailed") - .d("reason", "allocatingNewSessionFailed") - .d("error", getSensoryDetails(m_session, result))); - return false; - } - - // Get the expiration date of the library - const char* info = nullptr; - result = snsrGetString(m_session, SNSR_LICENSE_EXPIRES, &info); - if (result == SNSR_RC_OK && info) { - // Will print "License expires on " - ACSDK_INFO(LX(info)); - } else { - ACSDK_INFO(LX("Sensory library license does not expire.")); - } - - // Check if the expiration date is near, then we should display a warning - result = snsrGetString(m_session, SNSR_LICENSE_WARNING, &info); - if (result == SNSR_RC_OK && info) { - // Will print "License will expire in days." - ACSDK_WARN(LX(info)); - } else { - ACSDK_INFO(LX("Sensory library license does not expire for at least 60 more days.")); - } - - result = snsrLoad(m_session, snsrStreamFromFileName(modelFilePath.c_str(), "r")); - if (result != SNSR_RC_OK) { - ACSDK_ERROR( - LX("initFailed").d("reason", "loadingSensoryModelFailed").d("error", getSensoryDetails(m_session, result))); - return false; - } - - result = snsrRequire(m_session, SNSR_TASK_TYPE, SNSR_PHRASESPOT); - if (result != SNSR_RC_OK) { - ACSDK_ERROR(LX("initFailed") - .d("reason", "invalidTaskType") - .d("expected", "SNSR_PHRASESPOT") - .d("error", getSensoryDetails(m_session, result))); - return false; - } - - if (!setUpRuntimeSettings(&m_session)) { - return false; - } - m_isShuttingDown = false; m_detectionThread = std::thread(&SensoryKeywordDetector::detectionLoop, this); return true; @@ -310,36 +244,14 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { ACSDK_ERROR(LX("setUpRuntimeSettingsFailed").d("reason", "nullSession")); return false; } - - // Setting the callback handler - SnsrRC result = snsrSetHandler( - *session, SNSR_RESULT_EVENT, snsrCallback(keyWordDetectedCallback, nullptr, reinterpret_cast(this))); - - if (result != SNSR_RC_OK) { - ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") - .d("reason", "setKeywordDetectionHandlerFailure") - .d("error", getSensoryDetails(*session, result))); - return false; - } - - /* - * Turns off automatic pipeline flushing that happens when the end of the input stream is reached. This is an - * internal setting recommended by Sensory when audio is presented to Sensory in small chunks. - */ - result = snsrSetInt(*session, SNSR_AUTO_FLUSH, 0); - if (result != SNSR_RC_OK) { - ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") - .d("reason", "disableAutoPipelineFlushingFailed") - .d("error", getSensoryDetails(*session, result))); - return false; - } + return true; } void SensoryKeywordDetector::detectionLoop() { m_beginIndexOfStreamReader = m_streamReader->tell(); notifyKeyWordDetectorStateObservers(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); std::vector audioDataToPush(m_maxSamplesPerPush); - SnsrRC result; + //SnsrRC result; while (!m_isShuttingDown) { bool didErrorOccur = false; auto wordsRead = readFromStream( @@ -361,56 +273,11 @@ void SensoryKeywordDetector::detectionLoop() { * relative to it. */ m_beginIndexOfStreamReader = m_streamReader->tell(); - SnsrSession newSession{nullptr}; - /* - * This duplicated SnsrSession will have all the same configurations as m_session but none of the runtime - * settings. Thus, we will need to setup some of the runtime settings again. The reason for creating a new - * session is so that on overrun conditions, Sensory can start counting from 0 again. - */ - result = snsrDup(m_session, &newSession); - if (result != SNSR_RC_OK) { - ACSDK_ERROR(LX("detectionLoopFailed") - .d("reason", "sessionDuplicationFailed") - .d("error", getSensoryDetails(newSession, result))); - break; - } - - if (!setUpRuntimeSettings(&newSession)) { - break; - } - - m_session = newSession; - } else if (wordsRead > 0) { - // Words were successfully read. - snsrSetStream( - m_session, - SNSR_SOURCE_AUDIO_PCM, - snsrStreamFromMemory( - audioDataToPush.data(), wordsRead * sizeof(*audioDataToPush.data()), SNSR_ST_MODE_READ)); - result = snsrRun(m_session); - switch (result) { - case SNSR_RC_STREAM_END: - // Reached end of buffer without any keyword detections - break; - case SNSR_RC_OK: - break; - default: - // A different return from the callback function that indicates some sort of error - ACSDK_ERROR(LX("detectionLoopFailed") - .d("reason", "unexpectedReturn") - .d("error", getSensoryDetails(m_session, result))); - - notifyKeyWordDetectorStateObservers( - KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ERROR); - didErrorOccur = true; - break; - } - if (didErrorOccur) { - break; - } + } // Reset return code for next round - snsrClearRC(m_session); + //snsrClearRC(m_session); + } m_streamReader->close(); } From abdb7748a1a7e567d1421df511ee58e59442355c Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 19:01:02 +0100 Subject: [PATCH 18/60] Add flag --- tools/Install/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 7d724bb685..10dfbe0d92 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -62,7 +62,7 @@ PATH_FILES_DIR="$HOME/.config/" VOCALFUSION_3510_SALES_DEMO_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_sales_demo_path" VOCALFUSION_3510_AVS_SETUP_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_avs_setup_path" PI_HAT_CTRL_PATH="$THIRD_PARTY_PATH/pi_hat_ctrl" -SENSORY_KEY_WORD_DETECTOR_FLAG="" +SENSORY_KEY_WORD_DETECTOR_FLAG="-S" GPIO_KEY_WORD_DETECTOR_FLAG="" HID_KEY_WORD_DETECTOR_FLAG="" ALIASES="$HOME/.bash_aliases" From 1e5e589df6b67fc051fe5da94282cfcaa715d584 Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 20:12:51 +0100 Subject: [PATCH 19/60] Fix errors --- .../acsdkKWD/src/KWDComponent.cpp | 4 ++-- .../src/SensoryRegistration.cpp | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp index 4e14efe0d3..efd6136a5e 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp @@ -47,7 +47,7 @@ static std::shared_ptr createA std::shared_ptr keywordDetectorStateNotifier) { std::string modelFilePath; #ifdef SENSORY_OP_POINT - int snsrOperatingPoint; + uint32_t snsrOperatingPoint; #endif // SENSORY_OP_POINT auto config = avsCommon::utils::configuration::ConfigurationNode::getRoot()[SAMPLE_APP_CONFIG_ROOT_KEY] [SENSORY_CONFIG_ROOT_KEY]; @@ -69,7 +69,7 @@ static std::shared_ptr createA #endif // SENSORY_OP_POINT return kwd::SensoryKeywordDetector::create( - stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier, modelFilePath \ + stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier, modelFilePath #ifdef SENSORY_OP_POINT , snsrOperatingPoint #endif // SENSORY_OP_POINT diff --git a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp index d6850a647f..02f6f12646 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp @@ -32,20 +32,35 @@ namespace kwd { 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"); - +#ifdef SENSORY_OP_POINT +static const std::string SENSORY_SNSR_OPERATING_POINT("snsrOperatingPoint"); +#endif // SENSORY_OP_POINT std::unique_ptr createSensoryKWDAdapter( std::shared_ptr stream, avsCommon::utils::AudioFormat audioFormat, std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers) { std::string modelFilePath; +#ifdef SENSORY_OP_POINT + uint32_t snsrOperatingPoint; +#endif // SENSORY_OP_POINT + 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); +#ifdef SENSORY_OP_POINT + config.getUint32(SENSORY_SNSR_OPERATING_POINT, &snsrOperatingPoint); +#endif // SENSORY_OP_POINT + } return kwd::SensoryKeywordDetector::create( - stream, audioFormat, keyWordObservers, keyWordDetectorStateObservers, modelFilePath); + stream, audioFormat, keyWordObservers, keyWordDetectorStateObservers, modelFilePath +#ifdef SENSORY_OP_POINT + , snsrOperatingPoint +#endif // SENSORY_OP_POINT + ); + } /// The registration object to register the Sensory adapter's creation method. From bb533b155e2e9563c2156b083de67fb04f39bef5 Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 20:29:36 +0100 Subject: [PATCH 20/60] Re-introduced deleted lines --- .../Sensory/SensoryKeywordDetector.h | 12 +- .../acsdkSensoryAdapter/src/CMakeLists.txt | 3 +- .../src/SensoryKeywordDetector.cpp | 198 ++++++++++++++++-- 3 files changed, 193 insertions(+), 20 deletions(-) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 5ab78fab2f..edf1c0c76e 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -15,8 +15,6 @@ #ifndef ACSDKSENSORYADAPTER_SENSORY_SENSORYKEYWORDDETECTOR_H_ #define ACSDKSENSORYADAPTER_SENSORY_SENSORYKEYWORDDETECTOR_H_ -#define SnsrSession int* -#define SnsrRC int #include #include #include @@ -30,7 +28,7 @@ #include #include -//#include "snsr.h" +#include "snsr.h" namespace alexaClientSDK { namespace kwd { @@ -132,10 +130,14 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * the stream. This function should only be called once with each new @c SensoryKeywordDetector. * * @param modelFilePath The path to the model file. - * @param snsrOperatingPoint The operating point of the SNSR. + * @param snsrOperatingPoint The operating point of the SNSR. (XMOS-only feature). * @return @c true if the engine was initialized properly and @c false otherwise. */ - bool init(const std::string& modelFilePath, const uint32_t& snsrOperatingPoint); + bool init(const std::string& modelFilePath, +#ifdef SENSORY_OP_POINT + const uint32_t& snsrOperatingPoint +#endif // SENSORY_OP_POINT + ); /** * Sets up the runtime settings for a @c SnsrSession. This includes setting the callback handler and setting the diff --git a/applications/acsdkSensoryAdapter/src/CMakeLists.txt b/applications/acsdkSensoryAdapter/src/CMakeLists.txt index 688887e2e7..4acb6d91f7 100644 --- a/applications/acsdkSensoryAdapter/src/CMakeLists.txt +++ b/applications/acsdkSensoryAdapter/src/CMakeLists.txt @@ -9,7 +9,8 @@ target_include_directories(SENSORY PUBLIC target_link_libraries(SENSORY acsdkKWDImplementations acsdkKWDInterfaces - AVSCommon) + AVSCommon + "${SENSORY_KEY_WORD_DETECTOR_LIB_PATH}") # install target asdk_install() diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index b4ae42bc7c..3285e9143f 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -12,7 +12,6 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ -#define SNSR_RC_OK 0 #include #include @@ -103,14 +102,35 @@ static bool isAudioFormatCompatibleWithSensory(avsCommon::utils::AudioFormat aud return true; } -#define SNSR_RES_BEGIN_SAMPLE 1 +/** + * Returns information about the ongoing sensory session. Primarily used to populate error messages. + * + * @param session The Sensory session handle. + * @param result The Sensory return code. + * + * @return The pertinent message about the sensory session in string format. + */ +static std::string getSensoryDetails(SnsrSession session, SnsrRC result) { + std::string message; + // It is recommended by Sensory to prefer snsrErrorDetail() over snsrRCMessage() as it provides more details. + if (session) { + message = snsrErrorDetail(session); + } else { + message = snsrRCMessage(result); + } + if (message.empty()) { + message = "Unrecognized error"; + } + return message; +} + SnsrRC SensoryKeywordDetector::keyWordDetectedCallback(SnsrSession s, const char* key, void* userData) { SensoryKeywordDetector* engine = static_cast(userData); - //SnsrRC result; - const char* keyword = ""; - double begin = 0; - double end = 0; -/* + SnsrRC result; + const char* keyword; + double begin; + double end; + result = snsrGetDouble(s, SNSR_RES_BEGIN_SAMPLE, &begin); if (result != SNSR_RC_OK) { ACSDK_ERROR(LX("keyWordDetectedCallbackFailed") @@ -134,7 +154,7 @@ SnsrRC SensoryKeywordDetector::keyWordDetectedCallback(SnsrSession s, const char .d("error", getSensoryDetails(s, result))); return result; } -*/ + engine->notifyKeyWordObservers( engine->m_stream, keyword, @@ -151,7 +171,9 @@ std::unique_ptr SensoryKeywordDetector::create( std::unordered_set> keyWordDetectorStateObservers, const std::string& modelFilePath, +#ifdef SENSORY_OP_POINT const uint32_t& snsrOperatingPoint, +#endif // SENSORY_OP_POINT std::chrono::milliseconds msToPushPerIteration) { // Create Notifiers to be used instead of the observers. auto keywordNotifier = acsdkKWDImplementations::KWDNotifierFactories::createKeywordNotifier(); @@ -171,7 +193,9 @@ std::unique_ptr SensoryKeywordDetector::create( keywordNotifier, keywordDetectorStateNotifier, modelFilePath, - snsrOperatingPoint, +#ifdef SENSORY_OP_POINT + snsrOperatingPoint, +#endif // SENSORY_OP_POINT msToPushPerIteration); } @@ -181,7 +205,9 @@ std::unique_ptr SensoryKeywordDetector::create( const std::shared_ptr keywordNotifier, const std::shared_ptr keywordDetectorStateNotifier, const std::string& modelFilePath, +#ifdef SENSORY_OP_POINT const uint32_t& snsrOperatingPoint, +#endif // SENSORY_OP_POINT std::chrono::milliseconds msToPushPerIteration) { if (!stream) { ACSDK_ERROR(LX("createFailed").d("reason", "nullStream")); @@ -213,7 +239,7 @@ SensoryKeywordDetector::~SensoryKeywordDetector() { if (m_detectionThread.joinable()) { m_detectionThread.join(); } - //snsrRelease(m_session); + snsrRelease(m_session); } SensoryKeywordDetector::SensoryKeywordDetector( @@ -228,12 +254,65 @@ SensoryKeywordDetector::SensoryKeywordDetector( m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { } -bool SensoryKeywordDetector::init(const std::string& modelFilePath, const uint32_t& snsrOperatingPoint) { +bool SensoryKeywordDetector::init(const std::string& modelFilePath +#ifdef SENSORY_OP_POINT + , const uint32_t& snsrOperatingPoint +#endif // SENSORY_OP_POINT + ) { m_streamReader = m_stream->createReader(AudioInputStream::Reader::Policy::BLOCKING); if (!m_streamReader) { ACSDK_ERROR(LX("initFailed").d("reason", "createStreamReaderFailed")); return false; } + + // Allocate the Sensory library handle + SnsrRC result = snsrNew(&m_session); + if (result != SNSR_RC_OK) { + ACSDK_ERROR(LX("initFailed") + .d("reason", "allocatingNewSessionFailed") + .d("error", getSensoryDetails(m_session, result))); + return false; + } + + // Get the expiration date of the library + const char* info = nullptr; + result = snsrGetString(m_session, SNSR_LICENSE_EXPIRES, &info); + if (result == SNSR_RC_OK && info) { + // Will print "License expires on " + ACSDK_INFO(LX(info)); + } else { + ACSDK_INFO(LX("Sensory library license does not expire.")); + } + + // Check if the expiration date is near, then we should display a warning + result = snsrGetString(m_session, SNSR_LICENSE_WARNING, &info); + if (result == SNSR_RC_OK && info) { + // Will print "License will expire in days." + ACSDK_WARN(LX(info)); + } else { + ACSDK_INFO(LX("Sensory library license does not expire for at least 60 more days.")); + } + + result = snsrLoad(m_session, snsrStreamFromFileName(modelFilePath.c_str(), "r")); + if (result != SNSR_RC_OK) { + ACSDK_ERROR( + LX("initFailed").d("reason", "loadingSensoryModelFailed").d("error", getSensoryDetails(m_session, result))); + return false; + } + + result = snsrRequire(m_session, SNSR_TASK_TYPE, SNSR_PHRASESPOT); + if (result != SNSR_RC_OK) { + ACSDK_ERROR(LX("initFailed") + .d("reason", "invalidTaskType") + .d("expected", "SNSR_PHRASESPOT") + .d("error", getSensoryDetails(m_session, result))); + return false; + } + + if (!setUpRuntimeSettings(&m_session)) { + return false; + } + m_isShuttingDown = false; m_detectionThread = std::thread(&SensoryKeywordDetector::detectionLoop, this); return true; @@ -244,14 +323,59 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { ACSDK_ERROR(LX("setUpRuntimeSettingsFailed").d("reason", "nullSession")); return false; } + + // Setting the callback handler + SnsrRC result = snsrSetHandler( + *session, SNSR_RESULT_EVENT, snsrCallback(keyWordDetectedCallback, nullptr, reinterpret_cast(this))); + + if (result != SNSR_RC_OK) { + ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") + .d("reason", "setKeywordDetectionHandlerFailure") + .d("error", getSensoryDetails(*session, result))); + return false; + } + + /* + * Turns off automatic pipeline flushing that happens when the end of the input stream is reached. This is an + * internal setting recommended by Sensory when audio is presented to Sensory in small chunks. + */ + result = snsrSetInt(*session, SNSR_AUTO_FLUSH, 0); + if (result != SNSR_RC_OK) { + ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") + .d("reason", "disableAutoPipelineFlushingFailed") + .d("error", getSensoryDetails(*session, result))); + return false; + } + +#ifdef SENSORY_OP_POINT + result = snsrSetInt(*session, SNSR_OPERATING_POINT, AbstractKeywordDetector::m_sensoryOpPoint); + if (result != SNSR_RC_OK) + { + ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") + .d("reason", "setSnsrOperatingPointFailure") + .d("error", getSensoryDetails(*session, result))); + return false; + } + + int op=0; + result = snsrGetInt(*session, SNSR_OPERATING_POINT, &op); + if (result != SNSR_RC_OK) { + ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") + .d("reason", "gettingOperatingPointFaied") + .d("error", getSensoryDetails(*session, result))); + return false; + } + ACSDK_INFO(LX("setUpRuntimeSettingsFailed") + .d("operating point",op); return true; +#endif// SENSORY_OP_POINT } void SensoryKeywordDetector::detectionLoop() { m_beginIndexOfStreamReader = m_streamReader->tell(); notifyKeyWordDetectorStateObservers(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); std::vector audioDataToPush(m_maxSamplesPerPush); - //SnsrRC result; + SnsrRC result; while (!m_isShuttingDown) { bool didErrorOccur = false; auto wordsRead = readFromStream( @@ -275,9 +399,55 @@ void SensoryKeywordDetector::detectionLoop() { m_beginIndexOfStreamReader = m_streamReader->tell(); } + SnsrSession newSession{nullptr}; + /* + * This duplicated SnsrSession will have all the same configurations as m_session but none of the runtime + * settings. Thus, we will need to setup some of the runtime settings again. The reason for creating a new + * session is so that on overrun conditions, Sensory can start counting from 0 again. + */ + result = snsrDup(m_session, &newSession); + if (result != SNSR_RC_OK) { + ACSDK_ERROR(LX("detectionLoopFailed") + .d("reason", "sessionDuplicationFailed") + .d("error", getSensoryDetails(newSession, result))); + break; + } + + if (!setUpRuntimeSettings(&newSession)) { + break; + } + + m_session = newSession; + } else if (wordsRead > 0) { + // Words were successfully read. + snsrSetStream( + m_session, + SNSR_SOURCE_AUDIO_PCM, + snsrStreamFromMemory( + audioDataToPush.data(), wordsRead * sizeof(*audioDataToPush.data()), SNSR_ST_MODE_READ)); + result = snsrRun(m_session); + switch (result) { + case SNSR_RC_STREAM_END: + // Reached end of buffer without any keyword detections + break; + case SNSR_RC_OK: + break; + default: + // A different return from the callback function that indicates some sort of error + ACSDK_ERROR(LX("detectionLoopFailed") + .d("reason", "unexpectedReturn") + .d("error", getSensoryDetails(m_session, result))); + + notifyKeyWordDetectorStateObservers( + KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ERROR); + didErrorOccur = true; + break; + } + if (didErrorOccur) { + break; + } // Reset return code for next round - //snsrClearRC(m_session); - + snsrClearRC(m_session); } m_streamReader->close(); } From cb1398c8ec365c20338369b0ef8656e3e2e8e8f3 Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 20:34:15 +0100 Subject: [PATCH 21/60] Re-introduced deleted lines --- .../Sensory/SensoryKeywordDetector.h | 4 ++-- .../src/SensoryKeywordDetector.cpp | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index edf1c0c76e..b67eb69b5c 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -133,9 +133,9 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @param snsrOperatingPoint The operating point of the SNSR. (XMOS-only feature). * @return @c true if the engine was initialized properly and @c false otherwise. */ - bool init(const std::string& modelFilePath, + bool init(const std::string& modelFilePath #ifdef SENSORY_OP_POINT - const uint32_t& snsrOperatingPoint + , const uint32_t& snsrOperatingPoint #endif // SENSORY_OP_POINT ); diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 3285e9143f..f82ce79c0b 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -12,6 +12,7 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ + #include #include @@ -130,7 +131,6 @@ SnsrRC SensoryKeywordDetector::keyWordDetectedCallback(SnsrSession s, const char const char* keyword; double begin; double end; - result = snsrGetDouble(s, SNSR_RES_BEGIN_SAMPLE, &begin); if (result != SNSR_RC_OK) { ACSDK_ERROR(LX("keyWordDetectedCallbackFailed") @@ -226,7 +226,11 @@ std::unique_ptr SensoryKeywordDetector::create( std::unique_ptr detector(new SensoryKeywordDetector( stream, keywordNotifier, keywordDetectorStateNotifier, *audioFormat, msToPushPerIteration)); - if (!detector->init(modelFilePath, snsrOperatingPoint)) { + if (!detector->init(modelFilePath +#ifdef SENSORY_OP_POINT + , snsrOperatingPoint +#endif // SENSORY_OP_POINT + )) { ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); return nullptr; } @@ -397,8 +401,6 @@ void SensoryKeywordDetector::detectionLoop() { * relative to it. */ m_beginIndexOfStreamReader = m_streamReader->tell(); - - } SnsrSession newSession{nullptr}; /* * This duplicated SnsrSession will have all the same configurations as m_session but none of the runtime @@ -446,6 +448,7 @@ void SensoryKeywordDetector::detectionLoop() { if (didErrorOccur) { break; } + } // Reset return code for next round snsrClearRC(m_session); } From a35545e84b6dafced7e5ff3b93669622361dd689 Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 20:38:00 +0100 Subject: [PATCH 22/60] Update comments --- .../acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index b67eb69b5c..18385d3c28 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -15,6 +15,7 @@ #ifndef ACSDKSENSORYADAPTER_SENSORY_SENSORYKEYWORDDETECTOR_H_ #define ACSDKSENSORYADAPTER_SENSORY_SENSORYKEYWORDDETECTOR_H_ + #include #include #include @@ -71,7 +72,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe /** * @deprecated * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value - * and a snsrOperatingPoint under sampleApp + * and a snsrOperatingPoint (XMOS-only feature) 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. @@ -110,7 +111,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @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 keywordNotifier The object with which to notify 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 From 7c56113f56f53e4ef40a757ff35d964947a30e16 Mon Sep 17 00:00:00 2001 From: lucianom Date: Thu, 30 Jun 2022 14:10:10 +0100 Subject: [PATCH 23/60] Delay start of AVS till asoundrc is copied --- tools/Install/setup.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index d15dcf04d0..c7b1311312 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -342,8 +342,18 @@ if [ ! -f $AUTOSTART ]; then mkdir -p $AUTOSTART_DIR cp /etc/xdg/lxsession/LXDE-pi/autostart $AUTOSTART fi + +ASOUNDRC_FILE="~/.asoundrc" cat << EOF > "$STARTUP_SCRIPT" #!/bin/bash +# wait for host to boot up +sleep 5 +# check if .asoundrc file exists +while [[ ! -f $ASOUNDRC_FILE ]]; do + sleep 2 + echo "$ASOUNDRC_FILE file not found, wait 2 second(s)" +done +# start AVS console $AVSRUN_CMD EOF From ebda508ed1d1f94d38630c41ac61c3bea10ca569 Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 1 Jul 2022 08:49:24 +0100 Subject: [PATCH 24/60] Add comments --- tools/Install/setup.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index c7b1311312..60418e6c30 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -343,15 +343,19 @@ if [ ! -f $AUTOSTART ]; then cp /etc/xdg/lxsession/LXDE-pi/autostart $AUTOSTART fi +# in the startup script we need to check if the .asoundrc file is present, +# this is needed because of this known issue in Raspian Buster: +# https://forums.raspberrypi.com/viewtopic.php?t=295008# ASOUNDRC_FILE="~/.asoundrc" +PAUSE_SEC=2 cat << EOF > "$STARTUP_SCRIPT" #!/bin/bash # wait for host to boot up sleep 5 # check if .asoundrc file exists while [[ ! -f $ASOUNDRC_FILE ]]; do - sleep 2 - echo "$ASOUNDRC_FILE file not found, wait 2 second(s)" + sleep $PAUSE_SEC + echo "$ASOUNDRC_FILE file not found, wait $PAUSE_SEC secons" done # start AVS console $AVSRUN_CMD From de41c70a100dc7b0eb3c39b6e46ac978909f7cef Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 1 Jul 2022 08:56:35 +0100 Subject: [PATCH 25/60] Update info --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a360c07f0..34d020a2c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ Feature enhancements, updates, and resolved issues from all releases are availab **XMOS-only change** - version 0: - * ported XMOS KWD adapters to latest version of the SDK + * port XMOS KWD adapters to latest version of the SDK + * delay startup of AVS console till .asoundrc file is found ### 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) From 0121ed730b3633cf8e1bd6246559192fd98eb7f1 Mon Sep 17 00:00:00 2001 From: Luciano Martin Date: Fri, 1 Jul 2022 09:07:52 +0100 Subject: [PATCH 26/60] Undo test change --- tools/Install/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 10dfbe0d92..7d724bb685 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -62,7 +62,7 @@ PATH_FILES_DIR="$HOME/.config/" VOCALFUSION_3510_SALES_DEMO_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_sales_demo_path" VOCALFUSION_3510_AVS_SETUP_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_avs_setup_path" PI_HAT_CTRL_PATH="$THIRD_PARTY_PATH/pi_hat_ctrl" -SENSORY_KEY_WORD_DETECTOR_FLAG="-S" +SENSORY_KEY_WORD_DETECTOR_FLAG="" GPIO_KEY_WORD_DETECTOR_FLAG="" HID_KEY_WORD_DETECTOR_FLAG="" ALIASES="$HOME/.bash_aliases" From d7d0f3d770f7c95d81882cb27b61633cf4e6a6dc Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 1 Jul 2022 09:58:38 +0100 Subject: [PATCH 27/60] Fix printouts --- tools/Install/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 60418e6c30..18981b61ac 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -354,8 +354,8 @@ cat << EOF > "$STARTUP_SCRIPT" sleep 5 # check if .asoundrc file exists while [[ ! -f $ASOUNDRC_FILE ]]; do + echo "$ASOUNDRC_FILE file not found, wait $PAUSE_SEC seconds" sleep $PAUSE_SEC - echo "$ASOUNDRC_FILE file not found, wait $PAUSE_SEC secons" done # start AVS console $AVSRUN_CMD From b07d13231bf4b0bca7aa7fbeec0696b436902b04 Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 1 Jul 2022 13:03:32 +0100 Subject: [PATCH 28/60] Add option to build Sensory KWD --- .../acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp | 2 +- tools/Install/setup.sh | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index f82ce79c0b..e01a9e9f65 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -370,7 +370,7 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { return false; } ACSDK_INFO(LX("setUpRuntimeSettingsFailed") - .d("operating point",op); + .d("operating point",op)); return true; #endif// SENSORY_OP_POINT } diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 7d724bb685..2b21e32385 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -137,7 +137,9 @@ show_help() { echo ' -i PKCS#11 user pin to access key object functions.' echo ' -k PKCS#11 key object label.' echo ' -x XMOS device to setup: possible values are xvf3100, xvf3500, xvf3510, xvf3600-slave, xvf3600-master, or xvf3610, default is xvf3510' - echo ' -g Flag to enable keyword detector on GPIO interrupt' + echo ' -G Flag to enable keyword detector on GPIO interrupt' + echo ' -H Flag to enable keyword detector on HID interrupt' + echo ' -S Flag to enable Sensory keyword detector' echo ' -h Display this help and exit' } @@ -157,7 +159,7 @@ XMOS_TAG=$2 shift 2 -OPTIONS=s:a:d:hp:k:i:t:m:x:GHh +OPTIONS=s:a:d:hp:k:i:t:m:x:GHSh while getopts "$OPTIONS" opt ; do case $opt in s ) @@ -186,6 +188,9 @@ while getopts "$OPTIONS" opt ; do H ) HID_KEY_WORD_DETECTOR_FLAG="-H" ;; + S ) + SENSORY_KEY_WORD_DETECTOR_FLAG="-S" + ;; h ) show_help exit 1 From 0583d135120612522398c3932f9b059633cb4282 Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 1 Jul 2022 16:02:39 +0100 Subject: [PATCH 29/60] Update setUpRuntimeSettings() --- .../Sensory/SensoryKeywordDetector.h | 6 +++++- .../src/SensoryKeywordDetector.cpp | 14 +++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 18385d3c28..c58abd62d7 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -147,7 +147,11 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @param session The SnsrSession to set up runtime settings for. * @return @c true if everything succeeded and @c false otherwise. */ - bool setUpRuntimeSettings(SnsrSession* session); + bool setUpRuntimeSettings(SnsrSession* session, +#ifdef SENSORY_OP_POINT + , const uint32_t snsrOperatingPoint +#endif // SENSORY_OP_POINT + ); /// The main function that reads data and feeds it into the engine. void detectionLoop(); diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index e01a9e9f65..48c36e33f0 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -313,7 +313,11 @@ bool SensoryKeywordDetector::init(const std::string& modelFilePath return false; } - if (!setUpRuntimeSettings(&m_session)) { + if (!setUpRuntimeSettings(&m_session, +#ifdef SENSORY_OP_POINT + , snsrOperatingPoint +#endif // SENSORY_OP_POINT + )) { return false; } @@ -322,7 +326,11 @@ bool SensoryKeywordDetector::init(const std::string& modelFilePath return true; } -bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { +bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session +#ifdef SENSORY_OP_POINT + , const uint32_t snsrOperatingPoint +#endif // SENSORY_OP_POINT + ) { if (!session) { ACSDK_ERROR(LX("setUpRuntimeSettingsFailed").d("reason", "nullSession")); return false; @@ -352,7 +360,7 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { } #ifdef SENSORY_OP_POINT - result = snsrSetInt(*session, SNSR_OPERATING_POINT, AbstractKeywordDetector::m_sensoryOpPoint); + result = snsrSetInt(*session, SNSR_OPERATING_POINT, snsrOperatingPoint); if (result != SNSR_RC_OK) { ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") From 988843c447f113891d0a00c602c591fa8abffd59 Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 1 Jul 2022 16:10:15 +0100 Subject: [PATCH 30/60] Move line --- applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 48c36e33f0..ab5cf007d9 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -379,8 +379,8 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session } ACSDK_INFO(LX("setUpRuntimeSettingsFailed") .d("operating point",op)); - return true; #endif// SENSORY_OP_POINT + return true; } void SensoryKeywordDetector::detectionLoop() { From 298c36914b5eca3bb6cf14d471fb91241f62de37 Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 1 Jul 2022 16:43:11 +0100 Subject: [PATCH 31/60] Remove extra comma --- .../acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index c58abd62d7..61bcfc99a0 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -147,7 +147,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @param session The SnsrSession to set up runtime settings for. * @return @c true if everything succeeded and @c false otherwise. */ - bool setUpRuntimeSettings(SnsrSession* session, + bool setUpRuntimeSettings(SnsrSession* session #ifdef SENSORY_OP_POINT , const uint32_t snsrOperatingPoint #endif // SENSORY_OP_POINT From 4b6d817af2b5e1ebc6d60d62dcd05e2816d908ce Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 1 Jul 2022 16:55:48 +0100 Subject: [PATCH 32/60] Remove extra comma --- applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index ab5cf007d9..51ac0bee15 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -313,7 +313,7 @@ bool SensoryKeywordDetector::init(const std::string& modelFilePath return false; } - if (!setUpRuntimeSettings(&m_session, + if (!setUpRuntimeSettings(&m_session #ifdef SENSORY_OP_POINT , snsrOperatingPoint #endif // SENSORY_OP_POINT From 0dd7943b1939cef6a0b4636a00df39d698c66882 Mon Sep 17 00:00:00 2001 From: lucianom Date: Fri, 1 Jul 2022 19:14:49 +0100 Subject: [PATCH 33/60] Update printout --- applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 51ac0bee15..543435c72f 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -377,7 +377,7 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session .d("error", getSensoryDetails(*session, result))); return false; } - ACSDK_INFO(LX("setUpRuntimeSettingsFailed") + ACSDK_INFO(LX("setUpRuntimeSettings") .d("operating point",op)); #endif// SENSORY_OP_POINT return true; From 8ce9ca577d031e58bfe3590815093c86b755c70d Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 12:51:54 +0100 Subject: [PATCH 34/60] Removed unnecessary declarations --- .../XMOS/include/XMOS/XMOSKeywordDetector.h | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h b/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h index b6cecb35ba..029a4c4934 100644 --- a/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h +++ b/applications/acsdkXMOSAdapter/XMOS/include/XMOS/XMOSKeywordDetector.h @@ -52,44 +52,6 @@ class XMOSKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDetec public: - /** - * Creates an @c XMOSKeywordDetector - * - * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by - * Sensory in example code. - */ - static std::unique_ptr create( - const std::shared_ptr stream, - const std::shared_ptr& audioFormat, - std::shared_ptr keyWordNotifier, - std::shared_ptr KeyWordDetectorStateNotifier, - std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); - - /** - * @deprecated - * Creates an @c XMOSKeywordDetector - * - * @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 msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by - * Sensory in example code. - * @return A new @c XMOSKeywordDetector, or @c nullptr if the operation failed. - */ - static std::unique_ptr create( - const std::shared_ptr stream, - avsCommon::utils::AudioFormat audioFormat, - std::unordered_set> keyWordObservers, - std::unordered_set> keyWordDetectorStateObservers, - std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); - /** * Destructor. */ From 4915e0943922365184d148d1cc4d927e16f9a7c5 Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 14:01:50 +0100 Subject: [PATCH 35/60] Update CLI options --- tools/Install/pi.sh | 47 +++++++++++++++++++++++------------------- tools/Install/setup.sh | 29 +++++++------------------- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index ad7a4679cd..8ad5742d9e 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -28,18 +28,15 @@ show_help() { echo 'Usage: pi.sh [OPTIONS]' echo '' echo 'Optional parameters' - echo ' -G Flag to enable keyword detector on GPIO interrupt' - echo ' -H Flag to enable keyword detector on HID event' + echo ' -w Keyword detector to setup: possible values are S (Sensory), A (Amazon), G (GPIO trigger), H (HID trigger), default is no keyword detector, only tap-to-talk is enabled' echo ' -h Display this help and exit' } -OPTIONS=GHh +KEY_WORD_DETECTOR_FLAG="" +OPTIONS=w:h while getopts "$OPTIONS" opt ; do case $opt in - G ) - GPIO_KEY_WORD_DETECTOR_FLAG="ON" - ;; - H ) - HID_KEY_WORD_DETECTOR_FLAG="ON" + w ) + KEY_WORD_DETECTOR_FLAG="$OPTARG" ;; h ) show_help @@ -59,18 +56,26 @@ CMAKE_PLATFORM_SPECIFIC=(-DGSTREAMER_MEDIA_PLAYER=ON \ -DCURL_LIBRARY=${THIRD_PARTY_PATH}/curl-${CURL_VER}/lib/.libs/libcurl.so) # Add the flags for the different keyword detectors -if [ -n "$SENSORY_KEY_WORD_DETECTOR_FLAG" ] -then - CMAKE_PLATFORM_SPECIFIC+=(-DSENSORY_KEY_WORD_DETECTOR=ON \ - -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) -elif [ -n "$GPIO_KEY_WORD_DETECTOR_FLAG" ] -then - CMAKE_PLATFORM_SPECIFIC+=(-DGPIO_KEY_WORD_DETECTOR=ON) -elif [ -n "$HID_KEY_WORD_DETECTOR_FLAG" ] -then - CMAKE_PLATFORM_SPECIFIC+=(-DHID_KEY_WORD_DETECTOR=ON) -fi +case $KEY_WORD_DETECTOR_FLAG in + S ) + # Set CMAKE options for Sensory Keyword detector + CMAKE_PLATFORM_SPECIFIC+=(-DSENSORY_KEY_WORD_DETECTOR=ON \ + -DSENSORY_OP_POINT_FLAG=ON \ + -DXMOS_AVS_TESTS_FLAG=ON \ + -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) + ;; + A )) + # Set CMAKE options for Amazon Keyword detector + # CMAKE_PLATFORM_SPECIFIC+=( ... ) + ;; + G )) + CMAKE_PLATFORM_SPECIFIC+=(-DGPIO_KEY_WORD_DETECTOR=ON) + ;; + H )) + CMAKE_PLATFORM_SPECIFIC+=(-DHID_KEY_WORD_DETECTOR=ON) + ;; +esac GSTREAMER_AUDIO_SINK="alsasink" @@ -82,7 +87,7 @@ install_dependencies() { run_os_specifics() { build_port_audio build_curl - if [ [ -z $GPIO_KEY_WORD_DETECTOR_FLAG ] && [ -z $HID_KEY_WORD_DETECTOR_FLAG ] && [ -z $SENSORY_KEY_WORD_DETECTOR_FLAG ] ] + if [ [ -z $KEY_WORD_DETECTOR_FLAG ] ] then echo echo "==============> TAP-TO-TALK IS ENABLED ==============" diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 18981b61ac..6a999f6ba5 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -62,9 +62,6 @@ PATH_FILES_DIR="$HOME/.config/" VOCALFUSION_3510_SALES_DEMO_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_sales_demo_path" VOCALFUSION_3510_AVS_SETUP_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_avs_setup_path" PI_HAT_CTRL_PATH="$THIRD_PARTY_PATH/pi_hat_ctrl" -SENSORY_KEY_WORD_DETECTOR_FLAG="" -GPIO_KEY_WORD_DETECTOR_FLAG="" -HID_KEY_WORD_DETECTOR_FLAG="" ALIASES="$HOME/.bash_aliases" # Default value for XMOS device @@ -137,7 +134,7 @@ show_help() { echo ' -i PKCS#11 user pin to access key object functions.' echo ' -k PKCS#11 key object label.' echo ' -x XMOS device to setup: possible values are xvf3100, xvf3500, xvf3510, xvf3600-slave, xvf3600-master, or xvf3610, default is xvf3510' - echo ' -g Flag to enable keyword detector on GPIO interrupt' + echo ' -w Keyword detector to setup: possible values are S (Sensory), A (Amazon), G (GPIO trigger), H (HID trigger), default is no keyword detector, only tap-to-talk is enabled' echo ' -h Display this help and exit' } @@ -157,7 +154,9 @@ XMOS_TAG=$2 shift 2 -OPTIONS=s:a:d:hp:k:i:t:m:x:GHh +KEY_WORD_DETECTOR_FLAG="" + +OPTIONS=s:a:d:hp:k:i:t:m:x:w:h while getopts "$OPTIONS" opt ; do case $opt in s ) @@ -180,11 +179,8 @@ while getopts "$OPTIONS" opt ; do x ) XMOS_DEVICE="$OPTARG" ;; - G ) - GPIO_KEY_WORD_DETECTOR_FLAG="-G" - ;; - H ) - HID_KEY_WORD_DETECTOR_FLAG="-H" + w ) + KEY_WORD_DETECTOR_FLAG="-w $OPTARG" ;; h ) show_help @@ -258,7 +254,7 @@ PLATFORM=${PLATFORM:-$(get_platform)} if [ "$PLATFORM" == "Raspberry pi" ] then - PI_CMD="pi.sh ${SENSORY_KEY_WORD_DETECTOR_FLAG} ${GPIO_KEY_WORD_DETECTOR_FLAG} ${HID_KEY_WORD_DETECTOR_FLAG}" + PI_CMD="pi.sh ${KEY_WORD_DETECTOR_FLAG}" echo "Running command ${PI_CMD}" source $PI_CMD elif [ "$PLATFORM" == "Windows mingw64" ] @@ -398,15 +394,6 @@ while true; do esac done -if [ -n "$SENSORY_KEY_WORD_DETECTOR_FLAG" ] -then - SENSORY_OP_POINT_FLAG="-DSENSORY_OP_POINT=ON" - XMOS_AVS_TESTS_FLAG="-DXMOS_AVS_TESTS=ON" -else - SENSORY_OP_POINT_FLAG="-DSENSORY_OP_POINT=OFF" - XMOS_AVS_TESTS_FLAG="-DXMOS_AVS_TESTS=OFF" -fi - if [ $XMOS_DEVICE = "xvf3510" ] then PI_HAT_FLAG="-DPI_HAT_CTRL=ON" @@ -464,9 +451,7 @@ then mkdir -p $BUILD_PATH cd $BUILD_PATH cmake "$SOURCE_PATH/avs-device-sdk" \ - $SENSORY_OP_POINT_FLAG \ $PI_HAT_FLAG \ - $XMOS_AVS_TESTS_FLAG \ -DCMAKE_BUILD_TYPE=DEBUG \ -DPKCS11=$ACSDK_PKCS11 \ "${CMAKE_PLATFORM_SPECIFIC[@]}" From 530fcf61c67bda6db9748ed91e90d0f06137f5df Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 14:09:55 +0100 Subject: [PATCH 36/60] Fix syntax errors --- tools/Install/pi.sh | 8 ++++---- tools/Install/setup.sh | 9 ++------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index 8ad5742d9e..de74fcec74 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -65,14 +65,14 @@ case $KEY_WORD_DETECTOR_FLAG in -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) ;; - A )) + A ) # Set CMAKE options for Amazon Keyword detector - # CMAKE_PLATFORM_SPECIFIC+=( ... ) + CMAKE_PLATFORM_SPECIFIC+="" ;; - G )) + G ) CMAKE_PLATFORM_SPECIFIC+=(-DGPIO_KEY_WORD_DETECTOR=ON) ;; - H )) + H ) CMAKE_PLATFORM_SPECIFIC+=(-DHID_KEY_WORD_DETECTOR=ON) ;; esac diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 6a999f6ba5..f7809ae03a 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -326,13 +326,8 @@ echo AUTOSTART_SESSION="avsrun" AUTOSTART_DIR=$HOME/.config/lxsession/LXDE-pi AUTOSTART=$AUTOSTART_DIR/autostart -AVSRUN_CMD="lxterminal -t avsrun -e \"$BUILD_PATH/SampleApp/src/SampleApp $OUTPUT_CONFIG_FILE " -if [ -n "$SENSORY_KEY_WORD_DETECTOR_FLAG" ]; then - AVSRUN_CMD+="$THIRD_PARTY_PATH/alexa-rpi/models NONE 12 \$*" #$* is for passing any extra arguments to Sampleapp through .avsrun-startup.sh shell script -else - AVSRUN_CMD+="NONE" -fi -AVSRUN_CMD+="\" &" +AVSRUN_CMD="lxterminal -t avsrun -e \"$BUILD_PATH/SampleApp/src/SampleApp $OUTPUT_CONFIG_FILE NONE\" & " + STARTUP_SCRIPT=$CURRENT_DIR/.avsrun-startup.sh if [ ! -f $AUTOSTART ]; then mkdir -p $AUTOSTART_DIR From cc013675594fa72beee19160fe535bd3677e6ddc Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 17:20:12 +0100 Subject: [PATCH 37/60] Use privare member variable for operating point --- .../acsdkKWD/src/KWDComponent.cpp | 13 ++----- .../Sensory/SensoryKeywordDetector.h | 33 ++++++----------- .../src/SensoryKeywordDetector.cpp | 35 +++---------------- tools/Install/setup.sh | 2 +- 4 files changed, 19 insertions(+), 64 deletions(-) diff --git a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp index efd6136a5e..32d8da8f4a 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp @@ -46,15 +46,12 @@ static std::shared_ptr createA std::shared_ptr keywordNotifier, std::shared_ptr keywordDetectorStateNotifier) { std::string modelFilePath; -#ifdef SENSORY_OP_POINT - uint32_t snsrOperatingPoint; -#endif // SENSORY_OP_POINT 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); #ifdef SENSORY_OP_POINT - config.getUint32(SENSORY_SNSR_OPERATING_POINT, &snsrOperatingPoint); + config.getUint32(SENSORY_SNSR_OPERATING_POINT, &m_snsrOperatingPoint); #endif // SENSORY_OP_POINT } if (modelFilePath.empty()) { @@ -62,18 +59,14 @@ static std::shared_ptr createA return nullptr; } #ifdef SENSORY_OP_POINT - if (snsrOperatingPoint==0) { + if (m_snsrOperatingPoint==0) { ACSDK_ERROR(LX("createFailed").d("reason", "zeroSnsrOperatingPoint")); return nullptr; } #endif // SENSORY_OP_POINT return kwd::SensoryKeywordDetector::create( - stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier, modelFilePath -#ifdef SENSORY_OP_POINT - , snsrOperatingPoint -#endif // SENSORY_OP_POINT - ); + stream, audioFormat, keywordNotifier, keywordDetectorStateNotifier, modelFilePath); }; KWDComponent getComponent() { diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 61bcfc99a0..1368cab433 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -41,8 +41,8 @@ using namespace avsCommon::sdkInterfaces; class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDetector { public: /** - * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value - * and a snsrOperatingPoint (XMOS-only feature) under sampleApp + * 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. @@ -50,7 +50,6 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @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 snsrOperatingPoint The operating point of the SNSR (XMOS-only feature). * @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 @@ -64,15 +63,12 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe std::shared_ptr keyWordNotifier, std::shared_ptr KeyWordDetectorStateNotifier, const std::string& modelFilePath, -#ifdef SENSORY_OP_POINT - const uint32_t& snsrOperatingPoint, -#endif // SENSORY_OP_POINT std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); /** * @deprecated - * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value - * and a snsrOperatingPoint (XMOS-only feature) under sampleApp + * 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. @@ -80,7 +76,6 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @param keyWordObservers The observers to notify of keyword detections. * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. * @param modelFilePath The path to the model file. - * @param snsrOperatingPoint The operating point of the SNSR (XMOS-only feature). * @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 @@ -94,9 +89,6 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers, const std::string& modelFilePath, -#ifdef SENSORY_OP_POINT - const uint32_t& snsrOperatingPoint, -#endif // SENSORY_OP_POINT std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); /** @@ -131,14 +123,9 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * the stream. This function should only be called once with each new @c SensoryKeywordDetector. * * @param modelFilePath The path to the model file. - * @param snsrOperatingPoint The operating point of the SNSR. (XMOS-only feature). * @return @c true if the engine was initialized properly and @c false otherwise. */ - bool init(const std::string& modelFilePath -#ifdef SENSORY_OP_POINT - , const uint32_t& snsrOperatingPoint -#endif // SENSORY_OP_POINT - ); + bool init(const std::string& modelFilePath); /** * Sets up the runtime settings for a @c SnsrSession. This includes setting the callback handler and setting the @@ -147,11 +134,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @param session The SnsrSession to set up runtime settings for. * @return @c true if everything succeeded and @c false otherwise. */ - bool setUpRuntimeSettings(SnsrSession* session -#ifdef SENSORY_OP_POINT - , const uint32_t snsrOperatingPoint -#endif // SENSORY_OP_POINT - ); + bool setUpRuntimeSettings(SnsrSession* session); /// The main function that reads data and feeds it into the engine. void detectionLoop(); @@ -186,6 +169,10 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe /// The Sensory handle. SnsrSession m_session; + /** + * The SNSR operating point to be set in the keyword engine + */ + const uint32_t m_snsrOperatingPoint; /** * The max number of samples to push into the underlying engine per iteration. This will be determined based on the diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 543435c72f..98318a1a26 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -171,9 +171,6 @@ std::unique_ptr SensoryKeywordDetector::create( std::unordered_set> keyWordDetectorStateObservers, const std::string& modelFilePath, -#ifdef SENSORY_OP_POINT - const uint32_t& snsrOperatingPoint, -#endif // SENSORY_OP_POINT std::chrono::milliseconds msToPushPerIteration) { // Create Notifiers to be used instead of the observers. auto keywordNotifier = acsdkKWDImplementations::KWDNotifierFactories::createKeywordNotifier(); @@ -193,9 +190,6 @@ std::unique_ptr SensoryKeywordDetector::create( keywordNotifier, keywordDetectorStateNotifier, modelFilePath, -#ifdef SENSORY_OP_POINT - snsrOperatingPoint, -#endif // SENSORY_OP_POINT msToPushPerIteration); } @@ -205,9 +199,6 @@ std::unique_ptr SensoryKeywordDetector::create( const std::shared_ptr keywordNotifier, const std::shared_ptr keywordDetectorStateNotifier, const std::string& modelFilePath, -#ifdef SENSORY_OP_POINT - const uint32_t& snsrOperatingPoint, -#endif // SENSORY_OP_POINT std::chrono::milliseconds msToPushPerIteration) { if (!stream) { ACSDK_ERROR(LX("createFailed").d("reason", "nullStream")); @@ -226,11 +217,7 @@ std::unique_ptr SensoryKeywordDetector::create( std::unique_ptr detector(new SensoryKeywordDetector( stream, keywordNotifier, keywordDetectorStateNotifier, *audioFormat, msToPushPerIteration)); - if (!detector->init(modelFilePath -#ifdef SENSORY_OP_POINT - , snsrOperatingPoint -#endif // SENSORY_OP_POINT - )) { + if (!detector->init(modelFilePath)) { ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); return nullptr; } @@ -258,11 +245,7 @@ SensoryKeywordDetector::SensoryKeywordDetector( m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { } -bool SensoryKeywordDetector::init(const std::string& modelFilePath -#ifdef SENSORY_OP_POINT - , const uint32_t& snsrOperatingPoint -#endif // SENSORY_OP_POINT - ) { +dbool SensoryKeywordDetector::init(const std::string& modelFilePath) { m_streamReader = m_stream->createReader(AudioInputStream::Reader::Policy::BLOCKING); if (!m_streamReader) { ACSDK_ERROR(LX("initFailed").d("reason", "createStreamReaderFailed")); @@ -313,11 +296,7 @@ bool SensoryKeywordDetector::init(const std::string& modelFilePath return false; } - if (!setUpRuntimeSettings(&m_session -#ifdef SENSORY_OP_POINT - , snsrOperatingPoint -#endif // SENSORY_OP_POINT - )) { + if (!setUpRuntimeSettings(&m_session)) { return false; } @@ -326,11 +305,7 @@ bool SensoryKeywordDetector::init(const std::string& modelFilePath return true; } -bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session -#ifdef SENSORY_OP_POINT - , const uint32_t snsrOperatingPoint -#endif // SENSORY_OP_POINT - ) { +bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { if (!session) { ACSDK_ERROR(LX("setUpRuntimeSettingsFailed").d("reason", "nullSession")); return false; @@ -360,7 +335,7 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session } #ifdef SENSORY_OP_POINT - result = snsrSetInt(*session, SNSR_OPERATING_POINT, snsrOperatingPoint); + result = snsrSetInt(*session, SNSR_OPERATING_POINT, m_snsrOperatingPoint); if (result != SNSR_RC_OK) { ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 2b21e32385..227026eb29 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -62,7 +62,7 @@ PATH_FILES_DIR="$HOME/.config/" VOCALFUSION_3510_SALES_DEMO_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_sales_demo_path" VOCALFUSION_3510_AVS_SETUP_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_avs_setup_path" PI_HAT_CTRL_PATH="$THIRD_PARTY_PATH/pi_hat_ctrl" -SENSORY_KEY_WORD_DETECTOR_FLAG="" +SENSORY_KEY_WORD_DETECTOR_FLAG="-S" GPIO_KEY_WORD_DETECTOR_FLAG="" HID_KEY_WORD_DETECTOR_FLAG="" ALIASES="$HOME/.bash_aliases" From 0d671590e387e960132ef87431608a9880198a8f Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 18:17:56 +0100 Subject: [PATCH 38/60] Undo all changes --- .../Sensory/SensoryKeywordDetector.h | 10 +++++----- .../acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 1368cab433..65f49bae29 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -41,8 +41,8 @@ using namespace avsCommon::sdkInterfaces; class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDetector { public: /** - * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value - * under sampleApp + * 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. @@ -67,8 +67,8 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe /** * @deprecated - * Creates a @c SensoryKeywordDetector. Requires that the AlexaClientSDKConfig.json has a modelFilePath value - * under sampleApp + * 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. @@ -103,7 +103,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @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 notify observers of keyword detections. + * @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 diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 98318a1a26..469154d7f3 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -245,7 +245,7 @@ SensoryKeywordDetector::SensoryKeywordDetector( m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { } -dbool SensoryKeywordDetector::init(const std::string& modelFilePath) { +bool SensoryKeywordDetector::init(const std::string& modelFilePath) { m_streamReader = m_stream->createReader(AudioInputStream::Reader::Policy::BLOCKING); if (!m_streamReader) { ACSDK_ERROR(LX("initFailed").d("reason", "createStreamReaderFailed")); From d1593a1b5c237c4bb8e5c634289d31803d37739a Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 18:20:24 +0100 Subject: [PATCH 39/60] Undo old changes --- .../acsdkKWDProvider/src/SensoryRegistration.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp index 02f6f12646..e1d898eaf6 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp @@ -41,25 +41,18 @@ std::unique_ptr createSensoryK std::unordered_set> keyWordObservers, std::unordered_set> keyWordDetectorStateObservers) { std::string modelFilePath; -#ifdef SENSORY_OP_POINT - uint32_t snsrOperatingPoint; -#endif // SENSORY_OP_POINT 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); #ifdef SENSORY_OP_POINT - config.getUint32(SENSORY_SNSR_OPERATING_POINT, &snsrOperatingPoint); + config.getUint32(SENSORY_SNSR_OPERATING_POINT, &m_snsrOperatingPoint); #endif // SENSORY_OP_POINT } return kwd::SensoryKeywordDetector::create( - stream, audioFormat, keyWordObservers, keyWordDetectorStateObservers, modelFilePath -#ifdef SENSORY_OP_POINT - , snsrOperatingPoint -#endif // SENSORY_OP_POINT - ); + stream, audioFormat, keyWordObservers, keyWordDetectorStateObservers, modelFilePath); } From 6545d692373786f04ce8a5b524ebebce5e83a7c3 Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 18:21:36 +0100 Subject: [PATCH 40/60] Remove blank lines --- .../acsdkKWDProvider/src/SensoryRegistration.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp index e1d898eaf6..f3e418dc04 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp @@ -41,7 +41,6 @@ std::unique_ptr createSensoryK 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) { @@ -53,7 +52,6 @@ std::unique_ptr createSensoryK } return kwd::SensoryKeywordDetector::create( stream, audioFormat, keyWordObservers, keyWordDetectorStateObservers, modelFilePath); - } /// The registration object to register the Sensory adapter's creation method. From 470217e2730592ae86fa6984d118ea7d0e10aef4 Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 18:24:16 +0100 Subject: [PATCH 41/60] Remove test code --- tools/Install/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 227026eb29..2b21e32385 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -62,7 +62,7 @@ PATH_FILES_DIR="$HOME/.config/" VOCALFUSION_3510_SALES_DEMO_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_sales_demo_path" VOCALFUSION_3510_AVS_SETUP_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_avs_setup_path" PI_HAT_CTRL_PATH="$THIRD_PARTY_PATH/pi_hat_ctrl" -SENSORY_KEY_WORD_DETECTOR_FLAG="-S" +SENSORY_KEY_WORD_DETECTOR_FLAG="" GPIO_KEY_WORD_DETECTOR_FLAG="" HID_KEY_WORD_DETECTOR_FLAG="" ALIASES="$HOME/.bash_aliases" From c3c57c98f7d73fb6eac07eede37c16d179ba527d Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 19:07:34 +0100 Subject: [PATCH 42/60] Initialize member variable --- .../acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp | 3 +++ .../acsdkKWDProvider/src/SensoryRegistration.cpp | 3 +++ .../acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h | 5 ++++- .../acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp index 32d8da8f4a..d5050926c6 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp @@ -48,6 +48,9 @@ static std::shared_ptr createA std::string modelFilePath; auto config = avsCommon::utils::configuration::ConfigurationNode::getRoot()[SAMPLE_APP_CONFIG_ROOT_KEY] [SENSORY_CONFIG_ROOT_KEY]; +#ifdef SENSORY_OP_POINT + m_snsrOperatingPoint = 0; +#endif // SENSORY_OP_POINT if (config) { config.getString(SENSORY_MODEL_FILE_PATH, &modelFilePath); #ifdef SENSORY_OP_POINT diff --git a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp index f3e418dc04..8a5e675a54 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp @@ -43,6 +43,9 @@ std::unique_ptr createSensoryK std::string modelFilePath; auto config = avsCommon::utils::configuration::ConfigurationNode::getRoot()[SAMPLE_APP_CONFIG_ROOT_KEY] [SENSORY_CONFIG_ROOT_KEY]; +#ifdef SENSORY_OP_POINT + m_snsrOperatingPoint = 0; +#endif // SENSORY_OP_POINT if (config) { config.getString(SENSORY_MODEL_FILE_PATH, &modelFilePath); #ifdef SENSORY_OP_POINT diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 65f49bae29..03e006b4dd 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -169,10 +169,13 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe /// The Sensory handle. SnsrSession m_session; + +#ifdef SENSORY_OP_POINT /** * The SNSR operating point to be set in the keyword engine */ - const uint32_t m_snsrOperatingPoint; + uint32_t m_snsrOperatingPoint; +#endif // SENSORY_OP_POINT /** * The max number of samples to push into the underlying engine per iteration. This will be determined based on the diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 469154d7f3..74d481ed01 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -354,7 +354,7 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { } ACSDK_INFO(LX("setUpRuntimeSettings") .d("operating point",op)); -#endif// SENSORY_OP_POINT +#endif // SENSORY_OP_POINT return true; } From 30c7cb39b0cd85edffdaef97cd5e7e11a2fa421c Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 20:40:56 +0100 Subject: [PATCH 43/60] Do not pass keyword detector type to pi.sh via CLI argument, as pi.sh is called from within setup.sh. --- tools/Install/pi.sh | 21 --------------------- tools/Install/setup.sh | 6 +++--- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index de74fcec74..461b998b95 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -24,27 +24,6 @@ if [ -z "$PLATFORM" ]; then exit 1 fi -show_help() { - echo 'Usage: pi.sh [OPTIONS]' - echo '' - echo 'Optional parameters' - echo ' -w Keyword detector to setup: possible values are S (Sensory), A (Amazon), G (GPIO trigger), H (HID trigger), default is no keyword detector, only tap-to-talk is enabled' - echo ' -h Display this help and exit' -} -KEY_WORD_DETECTOR_FLAG="" -OPTIONS=w:h -while getopts "$OPTIONS" opt ; do - case $opt in - w ) - KEY_WORD_DETECTOR_FLAG="$OPTARG" - ;; - h ) - show_help - exit 1 - ;; - esac -done - SOUND_CONFIG="$HOME/.asoundrc" START_SCRIPT="$INSTALL_BASE/startsample.sh" START_PREVIEW_SCRIPT="$INSTALL_BASE/startpreview.sh" diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index f7809ae03a..a5fc5107ce 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -180,7 +180,7 @@ while getopts "$OPTIONS" opt ; do XMOS_DEVICE="$OPTARG" ;; w ) - KEY_WORD_DETECTOR_FLAG="-w $OPTARG" + KEY_WORD_DETECTOR_FLAG="$OPTARG" ;; h ) show_help @@ -254,8 +254,8 @@ PLATFORM=${PLATFORM:-$(get_platform)} if [ "$PLATFORM" == "Raspberry pi" ] then - PI_CMD="pi.sh ${KEY_WORD_DETECTOR_FLAG}" - echo "Running command ${PI_CMD}" + PI_CMD="pi.sh" + echo "Running command \"${PI_CMD}\" with KEY_WORD_DETECTOR_FLAG set to \"${KEY_WORD_DETECTOR_FLAG}\"" source $PI_CMD elif [ "$PLATFORM" == "Windows mingw64" ] then From 4d896655953b95b9ce0bab783f42cf9b8e41ffad Mon Sep 17 00:00:00 2001 From: lucianom Date: Mon, 4 Jul 2022 21:15:44 +0100 Subject: [PATCH 44/60] Add info --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d020a2c6..293cff9bb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Feature enhancements, updates, and resolved issues from all releases are availab **XMOS-only change** - version 0: + * refactor CLI arguments in setup scripts in order to support Amazon KWD * port XMOS KWD adapters to latest version of the SDK * delay startup of AVS console till .asoundrc file is found From 50c490e438e05708ef8e4c400a9b466b8996a660 Mon Sep 17 00:00:00 2001 From: lucianom Date: Tue, 5 Jul 2022 11:16:38 +0100 Subject: [PATCH 45/60] Set member variable as static and add set function --- .../acsdkKWD/src/KWDComponent.cpp | 7 +++---- .../acsdkKWDProvider/src/SensoryRegistration.cpp | 7 +++---- .../Sensory/SensoryKeywordDetector.h | 12 +++++++++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp index d5050926c6..b907912553 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp @@ -48,13 +48,12 @@ static std::shared_ptr createA std::string modelFilePath; auto config = avsCommon::utils::configuration::ConfigurationNode::getRoot()[SAMPLE_APP_CONFIG_ROOT_KEY] [SENSORY_CONFIG_ROOT_KEY]; -#ifdef SENSORY_OP_POINT - m_snsrOperatingPoint = 0; -#endif // SENSORY_OP_POINT if (config) { config.getString(SENSORY_MODEL_FILE_PATH, &modelFilePath); #ifdef SENSORY_OP_POINT - config.getUint32(SENSORY_SNSR_OPERATING_POINT, &m_snsrOperatingPoint); + uint32_t operatingPoint = 0; + config.getUint32(SENSORY_SNSR_OPERATING_POINT, &operatingPoint); + SensoryKeywordDetector::setSnsrOperatingPoint(operatingPoint); #endif // SENSORY_OP_POINT } if (modelFilePath.empty()) { diff --git a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp index 8a5e675a54..708f753c1f 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWDProvider/src/SensoryRegistration.cpp @@ -43,13 +43,12 @@ std::unique_ptr createSensoryK std::string modelFilePath; auto config = avsCommon::utils::configuration::ConfigurationNode::getRoot()[SAMPLE_APP_CONFIG_ROOT_KEY] [SENSORY_CONFIG_ROOT_KEY]; -#ifdef SENSORY_OP_POINT - m_snsrOperatingPoint = 0; -#endif // SENSORY_OP_POINT if (config) { config.getString(SENSORY_MODEL_FILE_PATH, &modelFilePath); #ifdef SENSORY_OP_POINT - config.getUint32(SENSORY_SNSR_OPERATING_POINT, &m_snsrOperatingPoint); + uint32_t operatingPoint = 0; + config.getUint32(SENSORY_SNSR_OPERATING_POINT, &operatingPoint); + SensoryKeywordDetector::setSnsrOperatingPoint(operatingPoint); #endif // SENSORY_OP_POINT } diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 03e006b4dd..ece68c202a 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -96,6 +96,16 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe */ ~SensoryKeywordDetector() override; + /** + * Set the operating point of the SNSR engine. + * + * @param value Value of the SNSR operating point. + * @return void + */ + static void setSnsrOperatingPoint(int value) { + m_snsrOperatingPoint = value; + }; + private: /** * Constructor. @@ -174,7 +184,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe /** * The SNSR operating point to be set in the keyword engine */ - uint32_t m_snsrOperatingPoint; + static uint32_t m_snsrOperatingPoint; #endif // SENSORY_OP_POINT /** From 8141c412aa389762434ea974dd4d98d049967f55 Mon Sep 17 00:00:00 2001 From: lucianom Date: Tue, 5 Jul 2022 14:35:42 +0100 Subject: [PATCH 46/60] Fix merge errors --- tools/Install/pi.sh | 31 +------------------------------ tools/Install/setup.sh | 3 --- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index ecb34c87ae..6277297973 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -24,35 +24,6 @@ if [ -z "$PLATFORM" ]; then exit 1 fi -show_help() { - echo 'Usage: pi.sh [OPTIONS]' - echo '' - echo 'Optional parameters' - echo ' -G Flag to enable keyword detector on GPIO interrupt' - echo ' -H Flag to enable keyword detector on HID event' - echo ' -S Flag to enable Sensory keyword detector' - echo ' -h Display this help and exit' -} -OPTIONS=GHSh -while getopts "$OPTIONS" opt ; do - case $opt in - G ) - GPIO_KEY_WORD_DETECTOR_FLAG="ON" - ;; - H ) - HID_KEY_WORD_DETECTOR_FLAG="ON" - ;; - S ) - SENSORY_KEY_WORD_DETECTOR_FLAG="ON" - ;; - - h ) - show_help - exit 1 - ;; - esac -done - SOUND_CONFIG="$HOME/.asoundrc" START_SCRIPT="$INSTALL_BASE/startsample.sh" START_PREVIEW_SCRIPT="$INSTALL_BASE/startpreview.sh" @@ -67,7 +38,7 @@ CMAKE_PLATFORM_SPECIFIC=(-DGSTREAMER_MEDIA_PLAYER=ON \ case $KEY_WORD_DETECTOR_FLAG in S ) # Set CMAKE options for Sensory Keyword detector - CMAKE_PLATFORM_SPECIFIC+=(-DSENSORY_KEY_WORD_DETECTOR=ON \ + CMAKE_PLATFORM_SPECIFIC+=(-DSENSORY_KEY_WORD_DETECTOR=ON \ -DSENSORY_OP_POINT_FLAG=ON \ -DXMOS_AVS_TESTS_FLAG=ON \ -DSENSORY_KEY_WORD_DETECTOR_LIB_PATH=$THIRD_PARTY_PATH/alexa-rpi/lib/libsnsr.a \ diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index c8f5279aa6..a5fc5107ce 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -182,9 +182,6 @@ while getopts "$OPTIONS" opt ; do w ) KEY_WORD_DETECTOR_FLAG="$OPTARG" ;; - S ) - SENSORY_KEY_WORD_DETECTOR_FLAG="-S" - ;; h ) show_help exit 1 From 1cc82e227388d3cc4256d208371d3762e1aa1099 Mon Sep 17 00:00:00 2001 From: lucianom Date: Tue, 5 Jul 2022 15:41:25 +0100 Subject: [PATCH 47/60] Use github repo to download portaudion --- tools/Install/setup.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index a5fc5107ce..974e296b53 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -25,8 +25,10 @@ set -o nounset # Exit the script if any uninitialized variable is used. CLONE_URL=${CLONE_URL:- 'https://github.com/xmos/avs-device-sdk.git'} -PORT_AUDIO_FILE="pa_stable_v190600_20161030.tgz" -PORT_AUDIO_DOWNLOAD_URL="http://www.portaudio.com/archives/$PORT_AUDIO_FILE" +#PORT_AUDIO_FILE="pa_stable_v190600_20161030.tgz" +#PORT_AUDIO_DOWNLOAD_URL="https://github.com/PortAudio/portaudio/releases/tag/pa_stable_v190600_20161030" +PORT_AUDIO_TAG="pa_stable_v190600_20161030" +PORT_AUDIO_CLONE_URL="https://github.com/PortAudio/portaudio" CURL_VER=7.67.0 CURL_DOWNLOAD_URL="https://github.com/curl/curl/releases/download/curl-7_67_0/curl-${CURL_VER}.tar.gz" @@ -90,9 +92,12 @@ build_port_audio() { echo "==============> BUILDING PORT AUDIO ==============" echo pushd $THIRD_PARTY_PATH - wget -c $PORT_AUDIO_DOWNLOAD_URL - tar zxf $PORT_AUDIO_FILE + # wget -c $PORT_AUDIO_DOWNLOAD_URL + # tar zxf $PORT_AUDIO_FILE + # Use giuthub repo for portaudio as it is more reliable than the website + git clone -v PORT_AUDIO_TAG -v PORT_AUDIO_CLONE_URL + mv PORT_AUDIO_TAG portaudio pushd portaudio ./configure --without-jack make From 86280e9cd678f74b6a3b95f2764603e2e8df4e1b Mon Sep 17 00:00:00 2001 From: lucianom Date: Tue, 5 Jul 2022 15:43:12 +0100 Subject: [PATCH 48/60] Fix command --- tools/Install/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 974e296b53..4282f8cfe4 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -96,7 +96,7 @@ build_port_audio() { # tar zxf $PORT_AUDIO_FILE # Use giuthub repo for portaudio as it is more reliable than the website - git clone -v PORT_AUDIO_TAG -v PORT_AUDIO_CLONE_URL + git clone -v $PORT_AUDIO_TAG $PORT_AUDIO_CLONE_URL mv PORT_AUDIO_TAG portaudio pushd portaudio ./configure --without-jack From a295fb597cc841edbfccd8bb47d3df4758bae6a3 Mon Sep 17 00:00:00 2001 From: lucianom Date: Tue, 5 Jul 2022 15:45:20 +0100 Subject: [PATCH 49/60] Fix command --- tools/Install/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 4282f8cfe4..d2f65e7190 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -97,7 +97,7 @@ build_port_audio() { # Use giuthub repo for portaudio as it is more reliable than the website git clone -v $PORT_AUDIO_TAG $PORT_AUDIO_CLONE_URL - mv PORT_AUDIO_TAG portaudio + mv $PORT_AUDIO_TAG portaudio pushd portaudio ./configure --without-jack make From a76742c0fa9607909d122325d8a1071d1725c33c Mon Sep 17 00:00:00 2001 From: lucianom Date: Tue, 5 Jul 2022 19:24:39 +0100 Subject: [PATCH 50/60] Update git clone command --- tools/Install/setup.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index d2f65e7190..ef3e18bce1 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -25,10 +25,11 @@ set -o nounset # Exit the script if any uninitialized variable is used. CLONE_URL=${CLONE_URL:- 'https://github.com/xmos/avs-device-sdk.git'} -#PORT_AUDIO_FILE="pa_stable_v190600_20161030.tgz" -#PORT_AUDIO_DOWNLOAD_URL="https://github.com/PortAudio/portaudio/releases/tag/pa_stable_v190600_20161030" +# PORT_AUDIO_FILE="pa_stable_v190600_20161030.tgz" +# PORT_AUDIO_DOWNLOAD_URL="https://github.com/PortAudio/portaudio/releases/tag/pa_stable_v190600_20161030" PORT_AUDIO_TAG="pa_stable_v190600_20161030" -PORT_AUDIO_CLONE_URL="https://github.com/PortAudio/portaudio" +PORT_AUDIO_CLONE_URL="https://github.com/PortAudio/portaudio/releases/tag/${PORT_AUDIO_TAG}.tar.gz" +PORT_AUDIO_DIR="portaudio" CURL_VER=7.67.0 CURL_DOWNLOAD_URL="https://github.com/curl/curl/releases/download/curl-7_67_0/curl-${CURL_VER}.tar.gz" @@ -95,10 +96,9 @@ build_port_audio() { # wget -c $PORT_AUDIO_DOWNLOAD_URL # tar zxf $PORT_AUDIO_FILE - # Use giuthub repo for portaudio as it is more reliable than the website - git clone -v $PORT_AUDIO_TAG $PORT_AUDIO_CLONE_URL - mv $PORT_AUDIO_TAG portaudio - pushd portaudio + # Download portaudio from github as it is more reliable than the website + git clone -b $PORT_AUDIO_TAG $PORT_AUDIO_CLONE_URL $PORT_AUDIO_DIR + pushd $PORT_AUDIO_DIR ./configure --without-jack make popd From 9584c76b7b89a2f13a43fadb98f865570910f947 Mon Sep 17 00:00:00 2001 From: lucianom Date: Tue, 5 Jul 2022 20:07:53 +0100 Subject: [PATCH 51/60] Update git clone command --- tools/Install/setup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index ef3e18bce1..aea97acd5b 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -28,7 +28,7 @@ CLONE_URL=${CLONE_URL:- 'https://github.com/xmos/avs-device-sdk.git'} # PORT_AUDIO_FILE="pa_stable_v190600_20161030.tgz" # PORT_AUDIO_DOWNLOAD_URL="https://github.com/PortAudio/portaudio/releases/tag/pa_stable_v190600_20161030" PORT_AUDIO_TAG="pa_stable_v190600_20161030" -PORT_AUDIO_CLONE_URL="https://github.com/PortAudio/portaudio/releases/tag/${PORT_AUDIO_TAG}.tar.gz" +PORT_AUDIO_CLONE_URL="https://github.com/PortAudio/portaudio" PORT_AUDIO_DIR="portaudio" CURL_VER=7.67.0 @@ -97,7 +97,7 @@ build_port_audio() { # tar zxf $PORT_AUDIO_FILE # Download portaudio from github as it is more reliable than the website - git clone -b $PORT_AUDIO_TAG $PORT_AUDIO_CLONE_URL $PORT_AUDIO_DIR + git clone -b $PORT_AUDIO_TAG $PORT_AUDIO_CLONE_URL pushd $PORT_AUDIO_DIR ./configure --without-jack make From 4c436ed2e55c7234570f965eec2a4a48ddbb4102 Mon Sep 17 00:00:00 2001 From: lucianom Date: Tue, 5 Jul 2022 20:16:11 +0100 Subject: [PATCH 52/60] Add info --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 293cff9bb2..bc16dc4476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Feature enhancements, updates, and resolved issues from all releases are availab * refactor CLI arguments in setup scripts in order to support Amazon KWD * port XMOS KWD adapters to latest version of the SDK * delay startup of AVS console till .asoundrc file is found + * clone portaudio from github instead of using wget as it is more reliable ### 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) From 3b0c298b73e80e638ab94e8ca8573a0325ebd1f4 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 6 Jul 2022 08:53:11 +0100 Subject: [PATCH 53/60] Protect code --- .../acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index ece68c202a..2e00ea1e67 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -96,6 +96,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe */ ~SensoryKeywordDetector() override; +#ifdef SENSORY_OP_POINT /** * Set the operating point of the SNSR engine. * @@ -105,6 +106,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe static void setSnsrOperatingPoint(int value) { m_snsrOperatingPoint = value; }; +#endif // SENSORY_OP_POINT private: /** From 120462894c7ad9a3f1d9ce10ae489f21f43865f7 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 6 Jul 2022 08:55:14 +0100 Subject: [PATCH 54/60] Fix use of static variable --- .../acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index 2e00ea1e67..d29b38fd41 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -104,7 +104,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @return void */ static void setSnsrOperatingPoint(int value) { - m_snsrOperatingPoint = value; + SensoryKeywordDetector::m_snsrOperatingPoint = value; }; #endif // SENSORY_OP_POINT From 6cc0ce25d776d03a2e5b25dd14f7ca2c04b8fd78 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 6 Jul 2022 09:02:37 +0100 Subject: [PATCH 55/60] Fix use of static variable --- .../acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 74d481ed01..179680fa19 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -335,7 +335,7 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { } #ifdef SENSORY_OP_POINT - result = snsrSetInt(*session, SNSR_OPERATING_POINT, m_snsrOperatingPoint); + result = snsrSetInt(*session, SNSR_OPERATING_POINT, SensoryKeywordDetector::m_snsrOperatingPoint); if (result != SNSR_RC_OK) { ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") @@ -353,7 +353,7 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { return false; } ACSDK_INFO(LX("setUpRuntimeSettings") - .d("operating point",op)); + .d("operating point", op)); #endif // SENSORY_OP_POINT return true; } From 951b3404a26493f282671b1652e530a253171dbe Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 6 Jul 2022 09:25:42 +0100 Subject: [PATCH 56/60] Fix compiler flags --- .../acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h | 2 +- .../acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp | 2 +- tools/Install/pi.sh | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h index d29b38fd41..2e00ea1e67 100644 --- a/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h +++ b/applications/acsdkSensoryAdapter/include/acsdkSensoryAdapter/Sensory/SensoryKeywordDetector.h @@ -104,7 +104,7 @@ class SensoryKeywordDetector : public acsdkKWDImplementations::AbstractKeywordDe * @return void */ static void setSnsrOperatingPoint(int value) { - SensoryKeywordDetector::m_snsrOperatingPoint = value; + m_snsrOperatingPoint = value; }; #endif // SENSORY_OP_POINT diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 179680fa19..4d70ff468d 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -335,7 +335,7 @@ bool SensoryKeywordDetector::setUpRuntimeSettings(SnsrSession* session) { } #ifdef SENSORY_OP_POINT - result = snsrSetInt(*session, SNSR_OPERATING_POINT, SensoryKeywordDetector::m_snsrOperatingPoint); + result = snsrSetInt(*session, SNSR_OPERATING_POINT, m_snsrOperatingPoint); if (result != SNSR_RC_OK) { ACSDK_ERROR(LX("setUpRuntimeSettingsFailed") diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index 6277297973..a9afa5a371 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -39,8 +39,8 @@ case $KEY_WORD_DETECTOR_FLAG in S ) # Set CMAKE options for Sensory Keyword detector CMAKE_PLATFORM_SPECIFIC+=(-DSENSORY_KEY_WORD_DETECTOR=ON \ - -DSENSORY_OP_POINT_FLAG=ON \ - -DXMOS_AVS_TESTS_FLAG=ON \ + -DSENSORY_OP_POINT=ON \ + -DXMOS_AVS_TESTS=ON \ -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) ;; From 51f59267b16bccd4dfef4b93e2476c21cdd18a24 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 6 Jul 2022 10:41:08 +0100 Subject: [PATCH 57/60] Initialize static variable --- .../acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp index 4d70ff468d..dee322d18f 100644 --- a/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp +++ b/applications/acsdkSensoryAdapter/src/SensoryKeywordDetector.cpp @@ -58,6 +58,10 @@ static const avsCommon::utils::AudioFormat::Encoding SENSORY_COMPATIBLE_ENCODING static const avsCommon::utils::AudioFormat::Endianness SENSORY_COMPATIBLE_ENDIANNESS = avsCommon::utils::AudioFormat::Endianness::LITTLE; +#ifdef SENSORY_OP_POINT +uint32_t SensoryKeywordDetector::m_snsrOperatingPoint = 0; +#endif // SENSORY_OP_POINT + /** * Checks to see if an @c avsCommon::utils::AudioFormat is compatible with Sensory. * From 5d5016d0805d625d6a2e9426ba17f8c843a7275c Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 6 Jul 2022 10:44:23 +0100 Subject: [PATCH 58/60] Fix initialization of variable --- .../acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp index b907912553..e9bdeecd1a 100644 --- a/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp +++ b/applications/acsdkSensoryAdapter/acsdkKWD/src/KWDComponent.cpp @@ -46,12 +46,15 @@ static std::shared_ptr createA std::shared_ptr keywordNotifier, std::shared_ptr keywordDetectorStateNotifier) { std::string modelFilePath; +#ifdef SENSORY_OP_POINT + uint32_t operatingPoint = 0; +#endif // SENSORY_OP_POINT + 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); #ifdef SENSORY_OP_POINT - uint32_t operatingPoint = 0; config.getUint32(SENSORY_SNSR_OPERATING_POINT, &operatingPoint); SensoryKeywordDetector::setSnsrOperatingPoint(operatingPoint); #endif // SENSORY_OP_POINT @@ -61,7 +64,7 @@ static std::shared_ptr createA return nullptr; } #ifdef SENSORY_OP_POINT - if (m_snsrOperatingPoint==0) { + if (operatingPoint==0) { ACSDK_ERROR(LX("createFailed").d("reason", "zeroSnsrOperatingPoint")); return nullptr; } From 460cc124a1600556969d9685770136e04ee4d356 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 6 Jul 2022 11:51:12 +0100 Subject: [PATCH 59/60] Add comments --- tools/Install/setup.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index aea97acd5b..e9fca5b098 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -25,6 +25,8 @@ set -o nounset # Exit the script if any uninitialized variable is used. CLONE_URL=${CLONE_URL:- 'https://github.com/xmos/avs-device-sdk.git'} +# Download portaudio from github as it is more reliable than the website +# Comment out old variables # PORT_AUDIO_FILE="pa_stable_v190600_20161030.tgz" # PORT_AUDIO_DOWNLOAD_URL="https://github.com/PortAudio/portaudio/releases/tag/pa_stable_v190600_20161030" PORT_AUDIO_TAG="pa_stable_v190600_20161030" @@ -93,10 +95,11 @@ build_port_audio() { echo "==============> BUILDING PORT AUDIO ==============" echo pushd $THIRD_PARTY_PATH + # Download portaudio from github as it is more reliable than the website + # Comment out old lines # wget -c $PORT_AUDIO_DOWNLOAD_URL # tar zxf $PORT_AUDIO_FILE - # Download portaudio from github as it is more reliable than the website git clone -b $PORT_AUDIO_TAG $PORT_AUDIO_CLONE_URL pushd $PORT_AUDIO_DIR ./configure --without-jack From c0d2ed7364704a8831ec287f4107bf09fd3d6413 Mon Sep 17 00:00:00 2001 From: lucianom Date: Wed, 6 Jul 2022 11:55:10 +0100 Subject: [PATCH 60/60] Fix indentation --- tools/Install/pi.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index a9afa5a371..c5e509a712 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -49,10 +49,10 @@ case $KEY_WORD_DETECTOR_FLAG in CMAKE_PLATFORM_SPECIFIC+="" ;; G ) - CMAKE_PLATFORM_SPECIFIC+=(-DGPIO_KEY_WORD_DETECTOR=ON) + CMAKE_PLATFORM_SPECIFIC+=(-DGPIO_KEY_WORD_DETECTOR=ON) ;; H ) - CMAKE_PLATFORM_SPECIFIC+=(-DHID_KEY_WORD_DETECTOR=ON) + CMAKE_PLATFORM_SPECIFIC+=(-DHID_KEY_WORD_DETECTOR=ON) ;; esac