Add manual test 33/308433/45
authorDaniel Kita <d.kita@samsung.com>
Mon, 25 Mar 2024 08:53:07 +0000 (09:53 +0100)
committerKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Thu, 1 Aug 2024 13:03:02 +0000 (13:03 +0000)
Change-Id: I1282040fb96617ccc3c42879cbf637c938ee351d

23 files changed:
CMakeLists.txt
packaging/webauthn-manual-test-app.manifest.in [new file with mode: 0644]
packaging/webauthn.spec
tests/webauthn-manual-test-app/CMakeLists.txt [new file with mode: 0644]
tests/webauthn-manual-test-app/shared/res/blank_code.png [new file with mode: 0644]
tests/webauthn-manual-test-app/shared/res/webauthn-manual-test-app.png [new file with mode: 0644]
tests/webauthn-manual-test-app/src/CMakeLists.txt [new file with mode: 0644]
tests/webauthn-manual-test-app/src/app_gui.cpp [new file with mode: 0644]
tests/webauthn-manual-test-app/src/app_gui.h [new file with mode: 0644]
tests/webauthn-manual-test-app/src/app_lifecycle.cpp [new file with mode: 0644]
tests/webauthn-manual-test-app/src/app_lifecycle.h [new file with mode: 0644]
tests/webauthn-manual-test-app/src/common.cpp [new file with mode: 0644]
tests/webauthn-manual-test-app/src/common.h [new file with mode: 0644]
tests/webauthn-manual-test-app/src/gen_qr_code.cpp [new file with mode: 0644]
tests/webauthn-manual-test-app/src/gen_qr_code.h [new file with mode: 0644]
tests/webauthn-manual-test-app/src/iQRCodePrinter.h [new file with mode: 0644]
tests/webauthn-manual-test-app/src/scenarios.cpp [new file with mode: 0644]
tests/webauthn-manual-test-app/src/scenarios.h [new file with mode: 0644]
tests/webauthn-manual-test-app/src/turn_bluetooth.cpp [new file with mode: 0644]
tests/webauthn-manual-test-app/src/turn_bluetooth.h [new file with mode: 0644]
tests/webauthn-manual-test-app/src/ui_component.cpp [new file with mode: 0644]
tests/webauthn-manual-test-app/src/ui_component.h [new file with mode: 0644]
tests/webauthn-manual-test-app/src/webauthn-manual-test-app.cpp [new file with mode: 0644]

index 779bdb7f8960112cb1195f39fa12abc353a6da43..8e730e4af1e42d993e5fc311ed5d9aef521b4112 100644 (file)
@@ -88,6 +88,7 @@ SET(PRJ_SRC_COMMON_PATH ${PRJ_SRC_PATH}/common)
 SET(PRJ_SRC_SERVER_PATH ${PRJ_SRC_PATH}/server)
 SET(PRJ_INCLUDE_PATH ${PROJECT_SOURCE_DIR}/include)
 SET(PRJ_TEST_PATH ${PROJECT_SOURCE_DIR}/tests)
+SET(PRJ_MANUAL_TEST_PATH ${PROJECT_SOURCE_DIR}/tests/webauthn-manual-test-app)
 
 ############################ Target Setting ################################
 SET(TARGET_WEBAUTHN_SERVER "${SERVICE_NAME}-server")
@@ -96,6 +97,7 @@ SET(TARGET_WEBAUTHN_COMMON "${SERVICE_NAME}-common")
 SET(TARGET_WEBAUTHN_UNIT_TESTS "${SERVICE_NAME}-unittests")
 SET(TARGET_WEBAUTHN_MANUAL_TESTS "${SERVICE_NAME}-manual-tests")
 SET(TARGET_WEBAUTHN_IMAGE_VIEWER "${SERVICE_NAME}-image-viewer")
+SET(TARGET_WEBAUTHN_MANUAL_TEST_APP "${SERVICE_NAME}-manual-test-app")
 
 ############################ Configure manifest files ######################
 CONFIGURE_FILE(packaging/${TARGET_WEBAUTHN_SERVER}.manifest.in ${TARGET_WEBAUTHN_SERVER}.manifest @ONLY)
@@ -104,10 +106,12 @@ CONFIGURE_FILE(packaging/${TARGET_WEBAUTHN_COMMON}.manifest.in ${TARGET_WEBAUTHN
 CONFIGURE_FILE(packaging/${TARGET_WEBAUTHN_UNIT_TESTS}.manifest.in ${TARGET_WEBAUTHN_UNIT_TESTS}.manifest @ONLY)
 CONFIGURE_FILE(packaging/${TARGET_WEBAUTHN_MANUAL_TESTS}.manifest.in ${TARGET_WEBAUTHN_MANUAL_TESTS}.manifest @ONLY)
 CONFIGURE_FILE(packaging/${TARGET_WEBAUTHN_IMAGE_VIEWER}.manifest.in ${TARGET_WEBAUTHN_IMAGE_VIEWER}.manifest @ONLY)
+CONFIGURE_FILE(packaging/${TARGET_WEBAUTHN_MANUAL_TEST_APP}.manifest.in ${TARGET_WEBAUTHN_MANUAL_TEST_APP}.manifest @ONLY)
 
 ############################ Add Sub Directories  ################################
 ADD_SUBDIRECTORY(include)
 ADD_SUBDIRECTORY(srcs)
 ADD_SUBDIRECTORY(tests)
+ADD_SUBDIRECTORY(tests/webauthn-manual-test-app)
 ADD_SUBDIRECTORY(build)
 ADD_SUBDIRECTORY(systemd)
diff --git a/packaging/webauthn-manual-test-app.manifest.in b/packaging/webauthn-manual-test-app.manifest.in
new file mode 100644 (file)
index 0000000..808c7a9
--- /dev/null
@@ -0,0 +1,6 @@
+<manifest>
+        <request>
+                <domain name="_"/>
+        </request>
+</manifest>
+
index b9979248dabbb051f24ee10e8853319cea866922..e098bc0a7daa258051208d19824667e781817e67 100644 (file)
@@ -88,6 +88,20 @@ Requires:      %{name} = %{version}-%{release}
 %description manual-tests
 Web Authentication Service (manual test)
 
+%package -n %{name}-manual-test-app
+Summary:    Web Authentication Manual Test Native App
+License:    Apache-2.0
+Group:      Security/Development
+BuildRequires: pkgconfig(dlog)
+BuildRequires: pkgconfig(efl-extension)
+BuildRequires: pkgconfig(capi-appfw-application)
+BuildRequires: pkgconfig(capi-network-bluetooth)
+BuildRequires: pkgconfig(capi-media-vision)
+Requires:      %{name} = %{version}-%{release}
+
+%description -n %{name}-manual-test-app
+Web Authentication Service (manual test)
+
 # gcov
 %if 0%{?gcov:1}
 %package gcov
@@ -107,13 +121,16 @@ Base utils gcov objects
 %define rw_share_dir       %TZ_SYS_SHARE
 %define service_name       %{name}
 
+%define MANTESTBINDIR    %{TZ_SYS_RO_APP}/%{name}-manual-test-app/bin
+%define MANIFESTDIR      %{TZ_SYS_RO_PACKAGES}
+%define ICONDIR          %{TZ_SYS_RO_ICONS}/default/small
+
 %global rw_data_dir /opt/data/
 %global bin_dir %{?TZ_SYS_BIN:%TZ_SYS_BIN}%{!?TZ_SYS_BIN:%_bindir}
 %global lib_dir %{?TZ_SYS_LIB:%TZ_SYS_LIB}%{!?TZ_SYS_LIB:%_libdir}
 %global ro_etc_dir %{?TZ_SYS_RO_ETC:%TZ_SYS_RO_ETC}%{!?TZ_SYS_RO_ETC:/etc}
 %global run_dir %{?TZ_SYS_RUN:%TZ_SYS_RUN}%{!?TZ_SYS_RUN:/var/run}
 
-
 # Macros for plugin SO path
 %define plugin_dir                 %{lib_dir}
 %define hybrid_plugin_so_name      webauthn_plugin_hybrid.so
@@ -144,6 +161,9 @@ Base utils gcov objects
          -DVERSION_MAJOR=%version_major \
          -DVERSION_MINOR=%version_minor \
          -DVERSION_PATCH=%version_patch \
+         -DMANIFESTDIR=%{MANIFESTDIR} \
+         -DICONDIR=%{ICONDIR} \
+         -DMANTESTBINDIR=%{MANTESTBINDIR} \
 %if 0%{?no_feature_check:1}
          -DNO_FEATURE_CHECK= \
 %endif
@@ -273,3 +293,11 @@ fi
 %license LICENSE
 %{bin_dir}/%{name}-manual-tests
 %{bin_dir}/%{name}-image-viewer
+
+%files -n %{name}-manual-test-app
+%manifest %{name}-manual-test-app.manifest
+%license LICENSE
+%defattr(-,root,root,-)
+
+%{MANTESTBINDIR}/%{name}-manual-test-app
+%{ICONDIR}/*
diff --git a/tests/webauthn-manual-test-app/CMakeLists.txt b/tests/webauthn-manual-test-app/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ada84e8
--- /dev/null
@@ -0,0 +1,21 @@
+# Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+
+SET(PREFIX ${CMAKE_INSTALL_PREFIX})
+
+ADD_SUBDIRECTORY(src)
+
+INSTALL(FILES shared/res/webauthn-manual-test-app.png DESTINATION ${ICONDIR})
+INSTALL(FILES shared/res/blank_code.png DESTINATION ${ICONDIR})
diff --git a/tests/webauthn-manual-test-app/shared/res/blank_code.png b/tests/webauthn-manual-test-app/shared/res/blank_code.png
new file mode 100644 (file)
index 0000000..c89729f
Binary files /dev/null and b/tests/webauthn-manual-test-app/shared/res/blank_code.png differ
diff --git a/tests/webauthn-manual-test-app/shared/res/webauthn-manual-test-app.png b/tests/webauthn-manual-test-app/shared/res/webauthn-manual-test-app.png
new file mode 100644 (file)
index 0000000..9765b1b
Binary files /dev/null and b/tests/webauthn-manual-test-app/shared/res/webauthn-manual-test-app.png differ
diff --git a/tests/webauthn-manual-test-app/src/CMakeLists.txt b/tests/webauthn-manual-test-app/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..59cf12e
--- /dev/null
@@ -0,0 +1,57 @@
+# Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+
+SET(WEBAUTHN_MANUAL_TEST_SOURCES
+    webauthn-manual-test-app.cpp
+    scenarios.cpp
+    common.cpp
+    turn_bluetooth.cpp
+    gen_qr_code.cpp
+    app_gui.cpp
+    ui_component.cpp
+    app_lifecycle.cpp
+)
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(pkgs REQUIRED
+    capi-appfw-application
+    efl-extension
+    dlog
+    capi-media-vision
+    capi-network-bluetooth
+)
+
+INCLUDE_DIRECTORIES(
+    ${PRJ_INCLUDE_PATH}
+)
+
+FOREACH(flag ${pkgs_CFLAGS})
+    SET(EXTRA_CXXFLAGS "${EXTRA_CXXFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+SET(EXTRA_CXXFLAGS "${EXTRA_CXXFLAGS} -fvisibility=hidden")
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CXXFLAGS}")
+
+ADD_EXECUTABLE(${TARGET_WEBAUTHN_MANUAL_TEST_APP} ${WEBAUTHN_MANUAL_TEST_SOURCES})
+
+TARGET_LINK_LIBRARIES(${TARGET_WEBAUTHN_MANUAL_TEST_APP}
+    ${CMAKE_THREAD_LIBS_INIT}
+    ${TARGET_WEBAUTHN_COMMON}
+    ${TARGET_WEBAUTHN_CLIENT}
+)
+
+TARGET_LINK_LIBRARIES(${TARGET_WEBAUTHN_MANUAL_TEST_APP} ${pkgs_LDFLAGS})
+
+INSTALL(TARGETS ${TARGET_WEBAUTHN_MANUAL_TEST_APP} DESTINATION ${MANTESTBINDIR})
diff --git a/tests/webauthn-manual-test-app/src/app_gui.cpp b/tests/webauthn-manual-test-app/src/app_gui.cpp
new file mode 100644 (file)
index 0000000..2dbc214
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "app_gui.h"
+#include "common.h"
+#include "scenarios.h"
+
+#include <algorithm>
+#include <app.h>
+#include <sstream>
+
+void WindowDeleteCb(void *data EINA_UNUSED,
+                    Evas_Object *obj EINA_UNUSED,
+                    void *event_info EINA_UNUSED)
+{
+    ui_app_exit();
+}
+
+void AppGUI::AlgorithmCheckboxStaticCb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->AlgorithmCheckboxCb(obj);
+}
+
+void AppGUI::AlgorithmCheckboxCb(Evas_Object *obj)
+{
+    if (obj == m_ES256) {
+        m_testContents.algorithms[0] = !m_testContents.algorithms[0];
+    } else if (obj == m_RS256) {
+        m_testContents.algorithms[1] = !m_testContents.algorithms[1];
+    }
+}
+
+void AppGUI::RegistrationHintsCheckboxStaticCb(void *data,
+                                               Evas_Object *obj,
+                                               void *event_info EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->RegistrationHintsCheckboxCb(obj);
+}
+
+void AppGUI::RegistrationHintsCheckboxCb(Evas_Object *obj)
+{
+    if (obj == m_rhSecKey) {
+        auto it = find(m_testContents.prefLevel.begin(),
+                       m_testContents.prefLevel.end(),
+                       WAUTHN_PUBKEY_CRED_HINT_SECURITY_KEY);
+        if (it != m_testContents.prefLevel.end()) {
+            m_testContents.prefLevel.erase(it);
+            LogDebug("Security key hint removed");
+        } else {
+            m_testContents.prefLevel.push_back(WAUTHN_PUBKEY_CRED_HINT_SECURITY_KEY);
+            LogDebug("Security key hint added");
+        }
+
+    } else if (obj == m_rhCliDev) {
+        auto it = find(m_testContents.prefLevel.begin(),
+                       m_testContents.prefLevel.end(),
+                       WAUTHN_PUBKEY_CRED_HINT_CLIENT_DEVICE);
+        if (it != m_testContents.prefLevel.end()) {
+            m_testContents.prefLevel.erase(it);
+            LogDebug("Client Device hint removed");
+        } else {
+            m_testContents.prefLevel.push_back(WAUTHN_PUBKEY_CRED_HINT_CLIENT_DEVICE);
+            LogDebug("Client Device hint added");
+        }
+    } else if (obj == m_rhHybrid) {
+        auto it = find(m_testContents.prefLevel.begin(),
+                       m_testContents.prefLevel.end(),
+                       WAUTHN_PUBKEY_CRED_HINT_HYBRID);
+        if (it != m_testContents.prefLevel.end()) {
+            m_testContents.prefLevel.erase(it);
+            LogDebug("Hybrid hint removed");
+        } else {
+            m_testContents.prefLevel.push_back(WAUTHN_PUBKEY_CRED_HINT_HYBRID);
+            LogDebug("Hybrid hint removed");
+        }
+    }
+    std::string hintsText = " { ";
+    for (auto item : m_testContents.prefLevel) {
+        hintsText += HintToString(item);
+        hintsText += ", ";
+    }
+
+    hintsText += "} ";
+    elm_object_text_set(m_hintsTable, hintsText.c_str());
+}
+
+void AppGUI::UserVerificationButtonStaticCb(void *data,
+                                            Evas_Object *obj,
+                                            void *event_info EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->UserVerificationButtonCb(obj);
+}
+
+void AppGUI::UserVerificationButtonCb(Evas_Object *obj)
+{
+    elm_object_color_class_color_set(m_uvBtDisc, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(m_uvBtPref, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(m_uvBtReq, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(obj, "bg", 0x5c, 0x5c, 0x8a, 0xff);
+
+    if (obj == m_uvBtDisc) {
+        m_testContents.userVerification = WAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
+        LogDebug("User Verification: WAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED");
+    } else if (obj == m_uvBtPref) {
+        m_testContents.userVerification = WAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
+        LogDebug("User Verification: WAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED");
+    } else if (obj == m_uvBtReq) {
+        m_testContents.userVerification = WAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
+        LogDebug("User Verification: WAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED");
+    }
+}
+
+void AppGUI::AttachmentButtonStaticCb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->AttachmentButtonCb(obj);
+}
+
+void AppGUI::AttachmentButtonCb(Evas_Object *obj)
+{
+    elm_object_color_class_color_set(m_attBtNone, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(m_attBtCrPlt, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(m_attBtPlt, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(obj, "bg", 0x5c, 0x5c, 0x8a, 0xff);
+
+    if (obj == m_attBtNone) {
+        m_testContents.attachment = WAUTHN_AUTHENTICATOR_ATTACHMENT_NONE;
+        LogDebug("Attachment: WAUTHN_AUTHENTICATOR_ATTACHMENT_NONE");
+    } else if (obj == m_attBtCrPlt) {
+        m_testContents.attachment = WAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
+        LogDebug("Attachment: WAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM");
+    } else if (obj == m_attBtPlt) {
+        m_testContents.attachment = WAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM;
+        LogDebug("Attachment: WAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM");
+    }
+}
+
+void AppGUI::DiscoverableCredButtonStaticCb(void *data,
+                                            Evas_Object *obj,
+                                            void *event_info EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->DiscoverableCredButtonCb(obj);
+}
+
+void AppGUI::DiscoverableCredButtonCb(Evas_Object *obj)
+{
+    elm_object_color_class_color_set(m_dcBtDisc, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(m_dcBtPref, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(m_dcBtReq, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(obj, "bg", 0x5c, 0x5c, 0x8a, 0xff);
+
+    if (obj == m_dcBtDisc) {
+        m_testContents.discCred = WAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED;
+        LogDebug("Discoverable Credential: WAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED");
+    } else if (obj == m_dcBtPref) {
+        m_testContents.discCred = WAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED;
+        LogDebug("Discoverable Credential: WAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED");
+    } else if (obj == m_dcBtReq) {
+        m_testContents.discCred = WAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED;
+        LogDebug("Discoverable Credential: WAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED");
+    }
+}
+
+void AppGUI::AttestationButtonStaticCb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->AttestationButtonCb(obj);
+}
+
+void AppGUI::AttestationButtonCb(Evas_Object *obj)
+{
+    elm_object_color_class_color_set(m_atteBtDir, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(m_atteBtNone, "bg", 0xb3, 0xcd, 0xe0, 0xff);
+    elm_object_color_class_color_set(obj, "bg", 0x5c, 0x5c, 0x8a, 0xff);
+
+    if (obj == m_atteBtDir) {
+        m_testContents.attPref = WAUTHN_ATTESTATION_PREF_DIRECT;
+        LogDebug("Attestation: AP_DIRECT");
+    } else if (obj == m_atteBtNone) {
+        m_testContents.attPref = WAUTHN_ATTESTATION_PREF_NONE;
+        LogDebug("Attestation: AP_NONE");
+    }
+}
+
+void *AppGUI::UpdateQRField(void *data)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    std::string path(QR_CODE_PATH);
+    elm_image_file_set(appGUI->m_QRCode, path.c_str(), NULL);
+    evas_object_show(appGUI->m_QRCode);
+
+    return NULL;
+}
+
+void *AppGUI::CleanQRFieldAndEnableUI(void *data)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    elm_image_file_set(appGUI->m_QRCode, DEFAULT_PATH.c_str(), NULL);
+    evas_object_show(appGUI->m_QRCode);
+    appGUI->DisableUIComponents(EINA_FALSE);
+    evas_object_hide(appGUI->m_cancelBt);
+
+    return NULL;
+}
+
+void *AppGUI::UpdateRegStatus(void *data)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+
+    if (appGUI->m_testContents.status != TestState::REG_SUCCCEEDED)
+        elm_object_text_set(appGUI->m_testInfo, _("<color=#FF0000FF>REGISTRATION FAILED!</color>"));
+    else
+        elm_object_text_set(appGUI->m_testInfo,
+                            _("<color=#00FF00FF>REGISTRATION COMPLETED!</color>"));
+
+    return NULL;
+}
+
+void *AppGUI::UpdateAuthStatus(void *data)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+
+    if (appGUI->m_testContents.status != TestState::AUTH_SUCCCEEDED)
+        elm_object_text_set(appGUI->m_testInfo,
+                            _("<color=#FF0000FF>AUTHENTICATION FAILED!</color>"));
+    else
+        elm_object_text_set(appGUI->m_testInfo,
+                            _("<color=#00FF00FF>AUTHENTICATION COMPLETED!</color>"));
+
+    return NULL;
+}
+
+void AppGUI::RegisterStaticCb(void *data, Ecore_Thread *thread EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->RegisterCb();
+}
+
+void AppGUI::RegisterCb()
+{
+    TestMC(m_testContents);
+    ecore_main_loop_thread_safe_call_sync(CleanQRFieldAndEnableUI, this);
+    ecore_main_loop_thread_safe_call_sync(UpdateRegStatus, this);
+}
+
+void AppGUI::RegisterButtonClickedStaticCb(void *data,
+                                           Evas_Object *obj EINA_UNUSED,
+                                           void *event_info EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->RegisterButtonClickedCb();
+}
+
+void AppGUI::RegisterButtonClickedCb()
+{
+
+    std::ostringstream log;
+    elm_object_text_set(m_testInfo, "");
+    DisableUIComponents(EINA_TRUE);
+    evas_object_show(m_cancelBt);
+
+    m_testContents.status = TestState::REG_IN_PROCESS;
+    m_testContents.username = elm_object_text_get(m_entry);
+
+    log << "Registering username: " << m_testContents.username << std::endl;
+    LogDebug(log.str().c_str());
+
+    ecore_thread_run(RegisterStaticCb, NULL, NULL, this);
+}
+
+void AppGUI::AuthenticateStaticCb(void *data, Ecore_Thread *thread EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->AuthenticateCb();
+}
+
+void AppGUI::AuthenticateCb()
+{
+    TestGA(m_testContents);
+    ecore_main_loop_thread_safe_call_sync(CleanQRFieldAndEnableUI, this);
+    ecore_main_loop_thread_safe_call_sync(UpdateAuthStatus, this);
+}
+
+void AppGUI::AuthenticateButtonClickedCb()
+{
+    std::ostringstream log;
+    if ((m_testContents.status != TestState::AUTH_SUCCCEEDED) &&
+        (m_testContents.status != TestState::REG_SUCCCEEDED)) {
+        std::cout << "Cannot authenticate, you need to register first!\n";
+        ecore_main_loop_thread_safe_call_sync(UpdateAuthStatus, this);
+        return;
+    }
+
+    elm_object_text_set(m_testInfo, "");
+    DisableUIComponents(EINA_TRUE);
+    evas_object_show(m_cancelBt);
+
+    m_testContents.status = TestState::AUTH_IN_PROCESS;
+    m_testContents.username = elm_object_text_get(m_entry);
+
+    log << "Registering username: " << m_testContents.username << std::endl;
+    LogDebug(log.str().c_str());
+
+    ecore_thread_run(AuthenticateStaticCb, NULL, NULL, this);
+}
+
+void AppGUI::AuthenticateButtonClickedStaticCb(void *data,
+                                               Evas_Object *obj EINA_UNUSED,
+                                               void *event_info EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->AuthenticateButtonClickedCb();
+}
+
+void AppGUI::CancelButtonStaticCb(void *data,
+                                  Evas_Object *obj EINA_UNUSED,
+                                  void *event_info EINA_UNUSED)
+{
+    auto appGUI = static_cast<AppGUI *>(data);
+    appGUI->CancelButtonCb();
+}
+
+void AppGUI::CancelButtonCb()
+{
+    int ret = CancelTest();
+    if (ret != 0) {
+        elm_object_text_set(
+            m_testInfo, _("<color=#FF0000FF>CANNOT CANCEL! WAIT FOR END OF THE SCENARIO</color>"));
+        return;
+    }
+
+    evas_object_hide(m_cancelBt);
+    DisableUIComponents(EINA_FALSE);
+}
+
+void AppGUI::CreateGUI()
+{
+    m_testContents.guiObject = this;
+    m_uiComp = std::make_unique<UIComponent>(WindowDeleteCb);
+
+    /* QR Code space*/
+    m_uiComp->AddImage(&m_QRCode, DEFAULT_PATH);
+
+    /* Username entryfield */
+    Evas_Object *labelUN = nullptr;
+    m_uiComp->AddLabel("Username", &labelUN);
+    m_uiComp->AddEntry("testUser", &m_entry);
+
+    /* Public Key Algorithms */
+    Evas_Object *labelPKA = nullptr;
+    m_uiComp->AddLabel("Public Key Algorithms", &labelPKA);
+    m_uiComp->AddCheckBox("Support ES256", &m_ES256, AlgorithmCheckboxStaticCb, EINA_TRUE, this);
+    m_uiComp->AddCheckBox("Support RS256", &m_RS256, AlgorithmCheckboxStaticCb, EINA_TRUE, this);
+
+    /* Registration Hints (most to least preferred) */
+    Evas_Object *labelRH = nullptr;
+    m_uiComp->AddLabel("Registration Hints", &labelRH);
+    m_uiComp->AddCheckBox(
+        "Security Key", &m_rhSecKey, RegistrationHintsCheckboxStaticCb, EINA_FALSE, this);
+    m_uiComp->AddCheckBox(
+        "Client Device", &m_rhCliDev, RegistrationHintsCheckboxStaticCb, EINA_FALSE, this);
+    m_uiComp->AddCheckBox(
+        "Hybrid", &m_rhHybrid, RegistrationHintsCheckboxStaticCb, EINA_FALSE, this);
+    m_uiComp->AddLabel("{ }", &m_hintsTable);
+
+    /* User Verification */
+    Evas_Object *labelUV = nullptr;
+    m_uiComp->AddLabel("User Verification", &labelUV);
+    m_uiComp->AddButton(
+        "Discouraged", &m_uvBtDisc, UserVerificationButtonStaticCb, LIGHT_BLUE, this);
+    m_uiComp->AddButton("Preferred", &m_uvBtPref, UserVerificationButtonStaticCb, PURPLE, this);
+    m_uiComp->AddButton(
+        "Required", &m_uvBtReq, UserVerificationButtonStaticCb, LIGHT_BLUE, this, true);
+
+    /* Attachment */
+    Evas_Object *labelAtt = nullptr;
+    m_uiComp->AddLabel("Attachment", &labelAtt);
+    m_uiComp->AddButton("None", &m_attBtNone, AttachmentButtonStaticCb, PURPLE, this);
+    m_uiComp->AddButton(
+        "Cross platform", &m_attBtCrPlt, AttachmentButtonStaticCb, LIGHT_BLUE, this);
+    m_uiComp->AddButton("Platform", &m_attBtPlt, AttachmentButtonStaticCb, LIGHT_BLUE, this, true);
+
+    /* Discoverable Credential  */
+    Evas_Object *labelDC = nullptr;
+    m_uiComp->AddLabel("Discoverable Credential", &labelDC);
+    m_uiComp->AddButton(
+        "Discouraged", &m_dcBtDisc, DiscoverableCredButtonStaticCb, LIGHT_BLUE, this);
+    m_uiComp->AddButton("Preferred", &m_dcBtPref, DiscoverableCredButtonStaticCb, PURPLE, this);
+    m_uiComp->AddButton(
+        "Required", &m_dcBtReq, DiscoverableCredButtonStaticCb, LIGHT_BLUE, this, true);
+
+    /* Attestation  */
+    Evas_Object *labelAT = nullptr;
+    m_uiComp->AddLabel("Attestation", &labelAT);
+    m_uiComp->AddButton("None", &m_atteBtNone, AttestationButtonStaticCb, PURPLE, this);
+    m_uiComp->AddButton("Direct", &m_atteBtDir, AttestationButtonStaticCb, LIGHT_BLUE, this, true);
+
+    /* Run scenarios buttons */
+    m_uiComp->AddButton("Register", &m_regBt, RegisterButtonClickedStaticCb, DARK_BLUE, this);
+    m_uiComp->AddButton(
+        "Authenticate", &m_authBt, AuthenticateButtonClickedStaticCb, DARK_BLUE, this);
+    m_uiComp->AddButton("Terminate", &m_terBt, WindowDeleteCb, DARK_BLUE, this, true);
+    m_uiComp->AddLabel(" ", &m_testInfo);
+
+    m_uiComp->AddCancelButton("Cancel...", &m_cancelBt, CancelButtonStaticCb, DARK_BLUE, this);
+
+    m_uiComp->ShowWindow();
+}
+
+void AppGUI::PrintQRCode() { ecore_main_loop_thread_safe_call_sync(UpdateQRField, this); }
+
+void AppGUI::DisableUIComponents(Eina_Bool disable)
+{
+    for (auto &x :
+         {m_ES256,      m_RS256,     m_rhSecKey,   m_rhCliDev, m_rhHybrid, m_uvBtDisc, m_uvBtPref,
+          m_uvBtReq,    m_attBtNone, m_attBtCrPlt, m_attBtPlt, m_dcBtDisc, m_dcBtPref, m_dcBtReq,
+          m_atteBtNone, m_atteBtDir, m_entry,      m_regBt,    m_authBt,   m_terBt})
+        elm_object_disabled_set(x, disable);
+}
diff --git a/tests/webauthn-manual-test-app/src/app_gui.h b/tests/webauthn-manual-test-app/src/app_gui.h
new file mode 100644 (file)
index 0000000..2909f17
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#pragma once
+
+#include "common.h"
+#include "iQRCodePrinter.h"
+#include "ui_component.h"
+
+class AppGUI : public IQRCodePrinter {
+public:
+    AppGUI(){};
+
+    AppGUI(const AppGUI &) = delete;
+    AppGUI(AppGUI &&) = delete;
+    AppGUI &operator=(const AppGUI &) = delete;
+    AppGUI &operator=(AppGUI &&) = delete;
+
+    // UI Components callbacks
+    static void
+    AlgorithmCheckboxStaticCb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED);
+    static void
+    RegistrationHintsCheckboxStaticCb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED);
+    static void
+    UserVerificationButtonStaticCb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED);
+    static void
+    AttachmentButtonStaticCb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED);
+    static void
+    DiscoverableCredButtonStaticCb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED);
+    static void
+    AttestationButtonStaticCb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED);
+    static void RegisterButtonClickedStaticCb(void *data,
+                                              Evas_Object *obj EINA_UNUSED,
+                                              void *event_info EINA_UNUSED);
+    static void AuthenticateButtonClickedStaticCb(void *data,
+                                                  Evas_Object *obj EINA_UNUSED,
+                                                  void *event_info EINA_UNUSED);
+
+    void PrintQRCode() override;
+    void CreateGUI();
+
+    void DeleteGUI() { m_uiComp.reset(); }
+
+private:
+    void AlgorithmCheckboxCb(Evas_Object *obj);
+    void RegistrationHintsCheckboxCb(Evas_Object *obj);
+    void UserVerificationButtonCb(Evas_Object *obj);
+    void AttachmentButtonCb(Evas_Object *obj);
+    void DiscoverableCredButtonCb(Evas_Object *obj);
+    void AttestationButtonCb(Evas_Object *obj);
+    void RegisterButtonClickedCb();
+    void AuthenticateButtonClickedCb();
+    static void RegisterStaticCb(void *data, Ecore_Thread *thread EINA_UNUSED);
+    void RegisterCb();
+    static void AuthenticateStaticCb(void *data, Ecore_Thread *thread EINA_UNUSED);
+    void AuthenticateCb();
+    static void
+    CancelButtonStaticCb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
+    void CancelButtonCb();
+
+    static void *UpdateQRField(void *data);
+    static void *CleanQRFieldAndEnableUI(void *data);
+    static void *UpdateAuthStatus(void *data);
+    static void *UpdateRegStatus(void *data);
+
+    void DisableUIComponents(Eina_Bool disable);
+
+    TestContents m_testContents;
+    std::unique_ptr<UIComponent> m_uiComp;
+
+    // Webauthn manual test UI objects
+    Evas_Object *m_testInfo = nullptr;
+    Evas_Object *m_hintsTable = nullptr;
+
+    Evas_Object *m_entry = nullptr;
+    Evas_Object *m_QRCode = nullptr;
+
+    Evas_Object *m_ES256 = nullptr;
+    Evas_Object *m_RS256 = nullptr;
+
+    Evas_Object *m_rhSecKey = nullptr;
+    Evas_Object *m_rhCliDev = nullptr;
+    Evas_Object *m_rhHybrid = nullptr;
+
+    Evas_Object *m_uvBtDisc = nullptr;
+    Evas_Object *m_uvBtPref = nullptr;
+    Evas_Object *m_uvBtReq = nullptr;
+
+    Evas_Object *m_attBtNone = nullptr;
+    Evas_Object *m_attBtCrPlt = nullptr;
+    Evas_Object *m_attBtPlt = nullptr;
+
+    Evas_Object *m_dcBtDisc = nullptr;
+    Evas_Object *m_dcBtPref = nullptr;
+    Evas_Object *m_dcBtReq = nullptr;
+
+    Evas_Object *m_atteBtNone = nullptr;
+    Evas_Object *m_atteBtDir = nullptr;
+
+    Evas_Object *m_regBt = nullptr;
+    Evas_Object *m_authBt = nullptr;
+    Evas_Object *m_terBt = nullptr;
+    Evas_Object *m_cancelBt = nullptr;
+};
diff --git a/tests/webauthn-manual-test-app/src/app_lifecycle.cpp b/tests/webauthn-manual-test-app/src/app_lifecycle.cpp
new file mode 100644 (file)
index 0000000..569f2ee
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "app_lifecycle.h"
+
+#include <dlog.h>
+
+bool AppLifecycle::AppCreateCb(void *data)
+{
+    auto alc = static_cast<AppLifecycle *>(data);
+    alc->m_appGUI.CreateGUI();
+
+    return true;
+}
+
+void AppLifecycle::AppTerminateCb(void *data)
+{
+    auto alc = static_cast<AppLifecycle *>(data);
+    alc->m_appGUI.DeleteGUI();
+}
+
+void AppLifecycle::SetEventCallbacks()
+{
+    m_LifeEventCb.create = AppCreateCb;
+    m_LifeEventCb.terminate = AppTerminateCb;
+    m_LifeEventCb.pause = NULL;
+    m_LifeEventCb.resume = NULL;
+    m_LifeEventCb.app_control = NULL;
+}
+
+int AppLifecycle::RunApp(int argc, char *argv[])
+{
+    int ret = ui_app_main(argc, argv, &m_LifeEventCb, this);
+    if (ret != APP_ERROR_NONE) {
+        LogError("app_main() is failed. err");
+        return -1;
+    }
+    return 0;
+}
diff --git a/tests/webauthn-manual-test-app/src/app_lifecycle.h b/tests/webauthn-manual-test-app/src/app_lifecycle.h
new file mode 100644 (file)
index 0000000..4532011
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#pragma once
+
+#include "app_gui.h"
+
+#include <app.h>
+
+class AppLifecycle {
+public:
+    AppLifecycle() { SetEventCallbacks(); }
+
+    int RunApp(int argc, char *argv[]);
+
+private:
+    static bool AppCreateCb(void *data);
+    static void AppTerminateCb(void *data);
+    void SetEventCallbacks();
+
+    AppGUI m_appGUI;
+    ui_app_lifecycle_callback_s m_LifeEventCb;
+};
diff --git a/tests/webauthn-manual-test-app/src/common.cpp b/tests/webauthn-manual-test-app/src/common.cpp
new file mode 100644 (file)
index 0000000..67ade58
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "common.h"
+
+#include <dlog.h>
+
+const std::string DEFAULT_PATH = "/usr/share/icons/default/small/blank_code.png";
+const std::string QR_CODE_PATH = "/tmp/test-qr-code.png";
+constexpr ColorData LIGHT_BLUE = {0xb3, 0xcd, 0xe0, 0xff};
+constexpr ColorData DARK_BLUE = {0x01, 0x1f, 0x4b, 0xff};
+constexpr ColorData PURPLE = {0x5c, 0x5c, 0x8a, 0xff};
+
+Buffer ToBuffer(wauthn_const_buffer_s buff) { return Buffer{buff.data, buff.data + buff.size}; }
+
+void LogDebug(const char *msg) { print_system_log(DLOG_DEBUG, LOG_TAG, "%s", msg); }
+
+void LogError(const char *msg) { print_system_log(DLOG_ERROR, LOG_TAG, "%s", msg); }
+
+void TestContents::UpdateLinkedData(const wauthn_hybrid_linked_data_s *ld)
+{
+    if (ld) {
+        linkedData.emplace();
+        linkedData->contactId = ToBuffer(*ld->contact_id);
+        linkedData->linkId = ToBuffer(*ld->link_id);
+        linkedData->linkSecret = ToBuffer(*ld->link_secret);
+        linkedData->authenticatorPubKey = ToBuffer(*ld->authenticator_pubkey);
+        linkedData->authenticatorName = ToBuffer(*ld->authenticator_name);
+        linkedData->signature = ToBuffer(*ld->signature);
+        linkedData->tunnelServerDomain = ToBuffer(*ld->tunnel_server_domain);
+        linkedData->identityKey = ToBuffer(*ld->identity_key);
+    } else {
+        linkedData = std::nullopt;
+    }
+}
diff --git a/tests/webauthn-manual-test-app/src/common.h b/tests/webauthn-manual-test-app/src/common.h
new file mode 100644 (file)
index 0000000..a1793d3
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#pragma once
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "webauthn-manual-test-app"
+
+#if !defined(PACKAGE)
+#define PACKAGE "org.example.webauthn-manual-test-app"
+#endif
+
+#include "iQRCodePrinter.h"
+
+#include <condition_variable>
+#include <deque>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <vector>
+#include <webauthn.h>
+
+struct ColorData {
+    int r, g, b, d;
+};
+
+extern const ColorData LIGHT_BLUE;
+extern const ColorData DARK_BLUE;
+extern const ColorData PURPLE;
+extern const std::string DEFAULT_PATH;
+extern const std::string QR_CODE_PATH;
+
+typedef std::vector<uint8_t> Buffer;
+Buffer ToBuffer(wauthn_const_buffer_s buff);
+void LogDebug(const char *msg);
+void LogError(const char *msg);
+
+enum TestState {
+    QR_CB_FAILED,
+    LD_CALLBACK_FAILED,
+    RESP_MC_CB_FAILED,
+    RESP_GA_CB_FAILED,
+    LD_UPDATED,
+    REG_IN_PROCESS,
+    REG_SUCCCEEDED,
+    REG_FAILED,
+    AUTH_IN_PROCESS,
+    AUTH_SUCCCEEDED,
+    AUTH_FAILED
+};
+
+inline const char *HintToString(wauthn_pubkey_cred_hint_e v)
+{
+    switch (v) {
+    case WAUTHN_PUBKEY_CRED_HINT_SECURITY_KEY: return "SECURITY_KEY";
+    case WAUTHN_PUBKEY_CRED_HINT_CLIENT_DEVICE: return "CLIENT_DEVICE";
+    case WAUTHN_PUBKEY_CRED_HINT_HYBRID: return "HYBRID";
+    default: return "[UNKNOWN_HINT]";
+    }
+}
+
+inline const char *TestStateToString(TestState v)
+{
+    switch (v) {
+    case QR_CB_FAILED: return "QR_CB_FAILED";
+    case LD_CALLBACK_FAILED: return "LD_CALLBACK_FAILED";
+    case RESP_MC_CB_FAILED: return "RESP_MC_CB_FAILED";
+    case RESP_GA_CB_FAILED: return "RESP_GA_CB_FAILED";
+    case LD_UPDATED: return "LD_UPDATED";
+    case REG_IN_PROCESS: return "REG_IN_PROCESS";
+    case REG_SUCCCEEDED: return "REG_SUCCCEEDED";
+    case REG_FAILED: return "REG_FAILED";
+    case AUTH_IN_PROCESS: return "AUTH_IN_PROCESS";
+    case AUTH_SUCCCEEDED: return "AUTH_SUCCCEEDED";
+    case AUTH_FAILED: return "AUTH_FAILED";
+    default: return "[UNKNOWN_STATUS]";
+    }
+}
+
+struct LinkedData {
+    Buffer contactId;
+    Buffer linkId;
+    Buffer linkSecret;
+    Buffer authenticatorPubKey;
+    Buffer authenticatorName;
+    Buffer signature;
+    Buffer tunnelServerDomain;
+    Buffer identityKey;
+};
+
+struct TestContents {
+    TestState status = TestState::REG_IN_PROCESS;
+    Buffer credentialRawId;
+    Buffer userId;
+    unsigned transports = WAUTHN_TRANSPORT_NONE;
+    std::optional<LinkedData> linkedData = std::nullopt;
+    const char *username = nullptr;
+
+    // an array containing information about which algorithms will be used in
+    // wauthn_pubkey_cred_params_s
+    bool algorithms[2] = {true, true};
+    std::deque<wauthn_pubkey_cred_hint_e> prefLevel;
+
+    bool seenLastMCUpdateCb = false;
+    bool seenLastGAUpdateCb = false;
+    IQRCodePrinter *guiObject = nullptr;
+    std::mutex mut;
+    std::condition_variable cv;
+    bool finished = false;
+
+    wauthn_user_verification_requirement_e userVerification =
+        WAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
+    wauthn_authenticator_attachment_e attachment = WAUTHN_AUTHENTICATOR_ATTACHMENT_NONE;
+    wauthn_resident_key_requirement_e discCred = WAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED;
+    wauthn_attestation_pref_e attPref = WAUTHN_ATTESTATION_PREF_NONE;
+
+    void UpdateLinkedData(const wauthn_hybrid_linked_data_s *ld);
+};
diff --git a/tests/webauthn-manual-test-app/src/gen_qr_code.cpp b/tests/webauthn-manual-test-app/src/gen_qr_code.cpp
new file mode 100644 (file)
index 0000000..8f319fb
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "gen_qr_code.h"
+
+#include <mv_barcode.h>
+
+int GenerateQRCode(const char *qr_contents, const std::string &path)
+{
+    int ret;
+    static constexpr int width = 300;
+    static constexpr int height = 300;
+    static constexpr int qr_version = 5;
+
+    mv_engine_config_h engine_cfg = nullptr;
+    const mv_barcode_type_e type = MV_BARCODE_QR;
+    const mv_barcode_qr_mode_e qr_enc_mode = MV_BARCODE_QR_MODE_UTF8;
+    const mv_barcode_qr_ecc_e qr_ecc = MV_BARCODE_QR_ECC_LOW;
+    const mv_barcode_image_format_e image_format = MV_BARCODE_IMAGE_FORMAT_PNG;
+
+    ret = mv_barcode_generate_image(engine_cfg,
+                                    qr_contents,
+                                    width,
+                                    height,
+                                    type,
+                                    qr_enc_mode,
+                                    qr_ecc,
+                                    qr_version,
+                                    path.c_str(),
+                                    image_format);
+
+    if (ret != MEDIA_VISION_ERROR_NONE)
+        return 0;
+
+    return 1;
+}
diff --git a/tests/webauthn-manual-test-app/src/gen_qr_code.h b/tests/webauthn-manual-test-app/src/gen_qr_code.h
new file mode 100644 (file)
index 0000000..f681cc5
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#pragma once
+
+#include <string>
+
+int GenerateQRCode(const char *qr_contents, const std::string &path);
diff --git a/tests/webauthn-manual-test-app/src/iQRCodePrinter.h b/tests/webauthn-manual-test-app/src/iQRCodePrinter.h
new file mode 100644 (file)
index 0000000..7b29758
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#pragma once
+
+class IQRCodePrinter {
+public:
+    IQRCodePrinter() {}
+
+    virtual ~IQRCodePrinter() {}
+
+    virtual void PrintQRCode() = 0;
+};
diff --git a/tests/webauthn-manual-test-app/src/scenarios.cpp b/tests/webauthn-manual-test-app/src/scenarios.cpp
new file mode 100644 (file)
index 0000000..bea35f5
--- /dev/null
@@ -0,0 +1,533 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "app_gui.h"
+#include "gen_qr_code.h"
+#include "scenarios.h"
+
+#include <cstring>
+#include <iostream>
+#include <string>
+#include <unistd.h>
+
+namespace TestData {
+
+// name and an identifier for the Relying Party
+constexpr inline char RP_NAME[] = "webauthn-manual-test-rpID";
+constexpr inline char RP_ID[] = "webauthn-manual-test-rpID";
+wauthn_rp_entity_s RP_ENTITY = {RP_NAME, RP_ID};
+
+// names and an identifier for the user account
+constexpr inline char USER_ID_RAW[] = "\x64\x47\x56\x7a\x64\x44\x4d\x79\x4d\x77";
+wauthn_const_buffer_s USER_ID = {reinterpret_cast<const uint8_t *>(USER_ID_RAW),
+                                 sizeof(USER_ID_RAW) - 1};
+
+constexpr unsigned long TIMEOUT = 120'000;
+
+// client data
+constexpr inline unsigned char CLIENT_DATA_JSON[] = "{}";
+wauthn_const_buffer_s CLIENT_DATA_JSON_BUFF = {CLIENT_DATA_JSON, sizeof(CLIENT_DATA_JSON) - 1};
+wauthn_client_data_s clientData = {&CLIENT_DATA_JSON_BUFF, WAUTHN_HASH_ALGORITHM_SHA_256};
+
+} // namespace TestData
+
+namespace ManTestScenario {
+
+class Scenario {
+public:
+    bool getStatus() const noexcept { return m_contents.status; }
+
+protected:
+    explicit Scenario(TestContents &contents, wauthn_client_data_s &clientData)
+    : m_contents{contents}, m_clientData{clientData}
+    {
+    }
+
+    TestContents &m_contents;
+    wauthn_client_data_s &m_clientData;
+};
+
+class MCScenario : public Scenario {
+public:
+    explicit MCScenario(TestContents &contents, wauthn_client_data_s &clientData)
+    : Scenario(contents, clientData)
+    {
+        SetCallbacks();
+    }
+
+    void Test();
+
+protected:
+    void SetCallbacks();
+
+    wauthn_mc_callbacks_s m_cb;
+};
+
+class GAScenario : public Scenario {
+public:
+    explicit GAScenario(TestContents &contents, wauthn_client_data_s &clientData)
+    : Scenario(contents, clientData)
+    {
+        SetCallbacks();
+    }
+
+    void Test();
+
+protected:
+    void SetCallbacks();
+
+    wauthn_ga_callbacks_s m_cb;
+};
+
+template <class Bytes>
+std::string LowercaseHexStringOf(const Bytes &bytes)
+{
+    static_assert(sizeof(typename Bytes::value_type) == 1);
+    std::string res;
+    for (uint8_t byte : bytes) {
+        constexpr char digits[] = "0123456789abcdef";
+        res += digits[byte >> 4];
+        res += digits[byte & 15];
+    }
+    return res;
+}
+
+template <class T>
+wauthn_const_buffer_s ToWauthnConstBuff(T &x) noexcept
+{
+    static_assert(sizeof(decltype(*x.data())) == 1, "for reinterpret_cast below");
+    return wauthn_const_buffer_s{reinterpret_cast<const uint8_t *>(x.data()), x.size()};
+}
+
+inline wauthn_const_buffer_s ToWauthnConstBuff(const char *stringLiteral) noexcept
+{
+    return wauthn_const_buffer_s{reinterpret_cast<const u_int8_t *>(stringLiteral),
+                                 std::strlen(stringLiteral)};
+}
+
+void DisplayQRCallback(const char *qrContents, void *data)
+{
+    std::cout << "qrcode_callback() was called\n";
+    TestContents *contents = reinterpret_cast<TestContents *>(data);
+    int ret;
+
+    if (!qrContents) {
+        std::cout << "qrcode_callback(): NULL qrContents\n";
+        goto callback_failed;
+    }
+    if (QR_CODE_PATH.empty()) {
+        std::cout << "qrcode_callback(): empty QR code path\n";
+        goto callback_failed;
+    }
+
+    std::cout << "qrcode: " << qrContents << std::endl;
+    ret = GenerateQRCode(qrContents, QR_CODE_PATH);
+
+    if (!ret) {
+        std::cout << "mv_barcode_generate_image failed\n"
+                  << "Error code: " << ret << std::endl;
+        goto callback_failed;
+    } else {
+        std::cout << "mv_barcode_generate_image PASSED\n";
+        auto guiObj = static_cast<AppGUI *>(contents->guiObject);
+        guiObj->PrintQRCode();
+    }
+    return;
+
+callback_failed:
+    contents->status = TestState::QR_CB_FAILED;
+    contents->finished = true;
+    contents->cv.notify_one();
+    return;
+}
+
+void UpdateLinkedDataCallbackImpl(const wauthn_hybrid_linked_data_s *linkedData,
+                                  wauthn_error_e result,
+                                  TestContents &contents,
+                                  bool &seenLastUpdateCallback)
+{
+    if (seenLastUpdateCallback) {
+        std::cout
+            << ": update_linked_data callback called after it was called with WAUTHN_ERROR_NONE"
+            << std::endl;
+        contents.status = TestState::LD_CALLBACK_FAILED;
+        contents.finished = true;
+        contents.cv.notify_one();
+        return;
+    }
+    if (result == WAUTHN_ERROR_NONE) {
+        seenLastUpdateCallback = true;
+        contents.finished = true;
+        contents.cv.notify_one();
+    } else if (result != WAUTHN_ERROR_NONE_AND_WAIT) {
+        std::cout << ": update_linked_data callback failed with code: " << result << std::endl;
+        contents.status = TestState::LD_CALLBACK_FAILED;
+        contents.finished = true;
+        contents.cv.notify_one();
+        return;
+    }
+    contents.UpdateLinkedData(linkedData);
+    contents.status = TestState::LD_UPDATED;
+}
+
+void LinkedDataMCCb(const wauthn_hybrid_linked_data_s *linkedData,
+                    wauthn_error_e result,
+                    void *data)
+{
+    std::cout << "MC: update_linked_data() was called." << std::endl;
+    auto *contents = static_cast<TestContents *>(data);
+    std::lock_guard<std::mutex> lock(contents->mut);
+    UpdateLinkedDataCallbackImpl(linkedData, result, *contents, contents->seenLastMCUpdateCb);
+
+    if (result == WAUTHN_ERROR_NONE)
+        std::cout << "MC: finished awaiting potential CTAP UPDATE messages." << std::endl;
+}
+
+void LinkedDataGACb(const wauthn_hybrid_linked_data_s *linkedData,
+                    wauthn_error_e result,
+                    void *data)
+{
+    std::cout << "GA: update_linked_data() was called." << std::endl;
+    auto *contents = static_cast<TestContents *>(data);
+    std::lock_guard<std::mutex> lock(contents->mut);
+    UpdateLinkedDataCallbackImpl(linkedData, result, *contents, contents->seenLastGAUpdateCb);
+
+    if (result == WAUTHN_ERROR_NONE)
+        std::cout << "GA: finished awaiting potential CTAP UPDATE messages." << std::endl;
+}
+
+void ResponseCbMC(const wauthn_pubkey_credential_attestation_s *pubkey_cred,
+                  wauthn_error_e result,
+                  void *data)
+{
+    std::cout << "MC: response_callback() was called." << std::endl;
+    TestContents *contents = static_cast<TestContents *>(data);
+
+    if (result == WAUTHN_ERROR_TIMED_OUT) {
+        std::cout << "MC: request timed out" << std::endl;
+        goto callback_failed;
+    }
+    if (result != WAUTHN_ERROR_NONE) {
+        std::cout << "MC: Response_callback failed, Error code: " << result << std::endl;
+        goto callback_failed;
+    }
+    if (!pubkey_cred) {
+        std::cout << "MC: pubkey_cred is null\n";
+        goto callback_failed;
+    }
+    if (!pubkey_cred->rawId) {
+        std::cout << "MC: pubkey_cred->rawId is NULL\n";
+        goto callback_failed;
+    }
+    if (!pubkey_cred->response) {
+        std::cout << "MC: pubkey_cred->response is NULL\n";
+        goto callback_failed;
+    }
+
+    contents->credentialRawId = ToBuffer(*pubkey_cred->rawId);
+    std::cout << "MC: credentialRawId: " << LowercaseHexStringOf(contents->credentialRawId)
+              << std::endl;
+    contents->transports = pubkey_cred->response->transports;
+    contents->UpdateLinkedData(pubkey_cred->linked_device);
+    return;
+
+callback_failed:
+    contents->status = TestState::RESP_MC_CB_FAILED;
+    contents->finished = true;
+    contents->cv.notify_one();
+    return;
+}
+
+void ResponseCbGA(const wauthn_pubkey_credential_assertion_s *pubkey_cred,
+                  wauthn_error_e result,
+                  void *data)
+{
+    std::cout << "GA: response_callback() was called." << std::endl;
+    auto *contents = static_cast<TestContents *>(data);
+    Buffer credentialRawId;
+
+    if (result == WAUTHN_ERROR_TIMED_OUT) {
+        std::cout << "GA: request timed out" << std::endl;
+    }
+    if (result != WAUTHN_ERROR_NONE) {
+        std::cout << "GA: response_callback failed with code " << result << std::endl;
+        goto callback_failed;
+    }
+    if (!pubkey_cred) {
+        std::cout << "GA: pubkey_cred is NULL" << std::endl;
+        goto callback_failed;
+    }
+    if (!pubkey_cred->rawId) {
+        std::cout << "GA: pubkey_cred->rawId is NULL" << std::endl;
+        goto callback_failed;
+    }
+    if (!pubkey_cred->response) {
+        std::cout << "GA: pubkey_cred->response is NULL" << std::endl;
+        goto callback_failed;
+    }
+
+    credentialRawId = ToBuffer(*pubkey_cred->rawId);
+    if (credentialRawId != contents->credentialRawId) {
+        std::cout << "Error: invalid credentialRawId in GA: "
+                  << LowercaseHexStringOf(credentialRawId) << std::endl;
+        goto callback_failed;
+    }
+
+    if (pubkey_cred->response->user_handle) {
+        auto userHandle = ToBuffer(*pubkey_cred->response->user_handle);
+        if (userHandle != contents->userId) {
+            std::cout << "Error: invalid userHandle in GA: " << LowercaseHexStringOf(userHandle)
+                      << std::endl;
+            goto callback_failed;
+        }
+    }
+
+    contents->UpdateLinkedData(pubkey_cred->linked_device);
+    std::cout << "GetAssertion: awaiting potential CTAP UPDATE messages..." << std::endl;
+    return;
+
+callback_failed:
+    contents->status = TestState::RESP_GA_CB_FAILED;
+    contents->finished = true;
+    contents->cv.notify_one();
+    return;
+}
+
+void MCScenario::SetCallbacks()
+{
+    m_cb.linked_data_callback = LinkedDataMCCb;
+    m_cb.qrcode_callback = DisplayQRCallback;
+    m_cb.response_callback = ResponseCbMC;
+    m_cb.user_data = &m_contents;
+}
+
+void MCScenario::Test()
+{
+    std::cout << "[MAKE CREDENTIAL]: START" << std::endl;
+    wauthn_pubkey_cred_creation_options_s m_options;
+    std::memset(&m_options, 0, sizeof(m_options));
+
+    // choosen algorithms
+    wauthn_pubkey_cred_params_s pubkeyCredParams;
+    std::vector<wauthn_pubkey_cred_param_s> params;
+    if (!(m_contents.algorithms[0] + m_contents.algorithms[1]))
+        m_options.pubkey_cred_params = nullptr;
+    else {
+        if (m_contents.algorithms[0])
+            params.push_back(
+                {WAUTHN_PUBKEY_CRED_TYPE_PUBLIC_KEY, WAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256});
+        if (m_contents.algorithms[1])
+            params.push_back({WAUTHN_PUBKEY_CRED_TYPE_PUBLIC_KEY,
+                              WAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256});
+
+        pubkeyCredParams.params = params.data();
+        pubkeyCredParams.size = params.size();
+        m_options.pubkey_cred_params = &pubkeyCredParams;
+    }
+
+    // choosen hints
+    wauthn_pubkey_cred_hints_s hints;
+    std::vector<wauthn_pubkey_cred_hint_e> hint;
+
+    if (m_contents.prefLevel.empty())
+        m_options.hints = nullptr;
+    else {
+        for (auto item : m_contents.prefLevel) {
+            hint.push_back(item);
+        }
+        hints = {hint.size(), hint.data()};
+        m_options.hints = &hints;
+    }
+
+    // Entity ID
+    wauthn_user_entity_s userEntity = {const_cast<char *>(m_contents.username),
+                                       &TestData::USER_ID,
+                                       const_cast<char *>(m_contents.username)};
+
+    // Authenticator selection data
+    wauthn_authenticator_sel_cri_s authSelection;
+    authSelection.attachment = m_contents.attachment;
+    authSelection.resident_key = m_contents.discCred;
+    if (authSelection.resident_key == WAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED)
+        authSelection.require_resident_key = true;
+    else
+        authSelection.require_resident_key = false;
+
+    authSelection.user_verification = m_contents.userVerification;
+
+    m_options.rp = &TestData::RP_ENTITY;
+    m_options.user = &userEntity;
+    m_options.timeout = TestData::TIMEOUT;
+    m_options.authenticator_selection = &authSelection;
+    m_options.attestation = m_contents.attPref;
+
+    m_contents.status = TestState::REG_IN_PROCESS;
+    m_contents.credentialRawId = {};
+    m_contents.userId = ToBuffer(TestData::USER_ID);
+    m_contents.transports = WAUTHN_TRANSPORT_NONE;
+    m_contents.linkedData = std::nullopt;
+    m_contents.seenLastMCUpdateCb = false;
+    m_contents.seenLastGAUpdateCb = false;
+
+    {
+        int res = wauthn_make_credential(&m_clientData, &m_options, &m_cb);
+        if (res == WAUTHN_ERROR_NONE) {
+            std::unique_lock<std::mutex> lock(m_contents.mut);
+            m_contents.cv.wait(lock, [&] { return m_contents.finished; });
+            m_contents.finished = false;
+
+            if (m_contents.status != TestState::LD_UPDATED) {
+                std::cout << "TestMakeCredential failed with status: "
+                          << TestStateToString(m_contents.status) << std::endl;
+                m_contents.status = TestState::REG_FAILED;
+                return;
+            }
+        } else {
+            std::cout << "wauthn_make_credential command failed, error: " << res
+                      << "\nTestMakeCredential failed with status: "
+                      << TestStateToString(m_contents.status) << std::endl;
+            m_contents.status = TestState::REG_FAILED;
+            return;
+        }
+
+        m_contents.status = TestState::REG_SUCCCEEDED;
+        std::cout << "TestMakeCredential passed!\n";
+        m_contents.seenLastGAUpdateCb = false;
+    }
+
+    std::cout << "[MAKE CREDENTIAL]: END" << std::endl;
+}
+
+void GAScenario::SetCallbacks()
+{
+    m_cb.linked_data_callback = LinkedDataGACb;
+    m_cb.qrcode_callback = DisplayQRCallback;
+    m_cb.response_callback = ResponseCbGA;
+    m_cb.user_data = &m_contents;
+}
+
+void GAScenario::Test()
+{
+    std::cout << "[GET ASSERTION]: START" << std::endl;
+    m_contents.status = TestState::AUTH_IN_PROCESS;
+
+    wauthn_pubkey_cred_request_options_s m_options;
+    std::memset(&m_options, 0, sizeof(m_options));
+
+    wauthn_const_buffer_s contactId;
+    wauthn_const_buffer_s linkId;
+    wauthn_const_buffer_s linkSecret;
+    wauthn_const_buffer_s authenticatorPubKey;
+    wauthn_const_buffer_s authenticatorName;
+    wauthn_const_buffer_s signature;
+    wauthn_const_buffer_s tunnelServerDomain;
+    wauthn_const_buffer_s identityKey;
+
+    wauthn_const_buffer_s credentialId = ToWauthnConstBuff(m_contents.credentialRawId);
+
+    wauthn_pubkey_cred_descriptor_s pubkeyCredDescriptor;
+    pubkeyCredDescriptor.type = WAUTHN_PUBKEY_CRED_TYPE_PUBLIC_KEY;
+    pubkeyCredDescriptor.id = &credentialId;
+    pubkeyCredDescriptor.transports = m_contents.transports;
+
+    wauthn_pubkey_cred_descriptors_s pubkeyCredDescriptors;
+    pubkeyCredDescriptors.size = 1;
+    pubkeyCredDescriptors.descriptors = &pubkeyCredDescriptor;
+
+    wauthn_hybrid_linked_data_s linkedDevice;
+    if (m_contents.linkedData) {
+        std::cout << "linked data exists\n";
+
+        contactId = ToWauthnConstBuff(m_contents.linkedData->contactId);
+        linkId = ToWauthnConstBuff(m_contents.linkedData->linkId);
+        linkSecret = ToWauthnConstBuff(m_contents.linkedData->linkSecret);
+        authenticatorPubKey = ToWauthnConstBuff(m_contents.linkedData->authenticatorPubKey);
+        authenticatorName = ToWauthnConstBuff(m_contents.linkedData->authenticatorName);
+        signature = ToWauthnConstBuff(m_contents.linkedData->signature);
+        tunnelServerDomain = ToWauthnConstBuff(m_contents.linkedData->tunnelServerDomain);
+        identityKey = ToWauthnConstBuff(m_contents.linkedData->identityKey);
+
+        linkedDevice = {&contactId,
+                        &linkId,
+                        &linkSecret,
+                        &authenticatorPubKey,
+                        &authenticatorName,
+                        &signature,
+                        &tunnelServerDomain,
+                        &identityKey};
+    }
+
+    m_options.timeout = TestData::TIMEOUT;
+    m_options.rpId = const_cast<char *>(TestData::RP_ID);
+    m_options.user_verification = m_contents.userVerification;
+    m_options.attestation = m_contents.attPref;
+    m_options.allow_credentials = &pubkeyCredDescriptors;
+    m_options.linked_device = m_contents.linkedData ? &linkedDevice : nullptr;
+    m_options.attestation_formats = nullptr;
+    m_options.extensions = nullptr;
+
+    {
+        int res = wauthn_get_assertion(&m_clientData, &m_options, &m_cb);
+        if (res == WAUTHN_ERROR_NONE) {
+            std::unique_lock<std::mutex> lock(m_contents.mut);
+            m_contents.cv.wait(lock, [&] { return m_contents.finished; });
+            m_contents.finished = false;
+
+            if (m_contents.status != TestState::LD_UPDATED) {
+                std::cout << "TestGetAssertion failed with status: "
+                          << TestStateToString(m_contents.status) << std::endl;
+                m_contents.status = TestState::AUTH_FAILED;
+                return;
+            }
+        } else {
+            std::cout
+                << "wauthn_get_assertion command failed\nTestGetAssertion failed with status: "
+                << TestStateToString(m_contents.status) << std::endl;
+            m_contents.status = TestState::AUTH_FAILED;
+            return;
+        }
+
+        std::cout << "TestGetAssertion passed!\n";
+        m_contents.status = TestState::AUTH_SUCCCEEDED;
+        m_contents.seenLastGAUpdateCb = false;
+    }
+    std::cout << "[GET ASSERTION]: END" << std::endl;
+}
+
+} // namespace ManTestScenario
+
+void TestMC(TestContents &contents)
+{
+    ManTestScenario::MCScenario mcTest(contents, TestData::clientData);
+    mcTest.Test();
+}
+
+void TestGA(TestContents &contents)
+{
+    ManTestScenario::GAScenario gaTest(contents, TestData::clientData);
+    gaTest.Test();
+}
+
+int CancelTest()
+{
+    int ret = wauthn_cancel();
+    if (ret != WAUTHN_ERROR_NONE) {
+        std::cout << "The cancellation request is not allowed!\n";
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/tests/webauthn-manual-test-app/src/scenarios.h b/tests/webauthn-manual-test-app/src/scenarios.h
new file mode 100644 (file)
index 0000000..3e31f91
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#pragma once
+
+#include "common.h"
+
+void TestMC(TestContents &contents);
+void TestGA(TestContents &contents);
+int CancelTest();
diff --git a/tests/webauthn-manual-test-app/src/turn_bluetooth.cpp b/tests/webauthn-manual-test-app/src/turn_bluetooth.cpp
new file mode 100644 (file)
index 0000000..d579690
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "common.h"
+
+#include <bluetooth.h>
+#include <bluetooth_internal.h>
+#include <glib/gmain.h>
+#include <iostream>
+#include <tuple>
+
+using std::endl;
+
+namespace {
+
+[[nodiscard]] int DoTurnBluetooth(int alreadyDoneError)
+{
+    int err;
+    LogError("Turning bluetooth on...");
+    int res = bt_initialize();
+
+    if (res != BT_ERROR_NONE) {
+        LogError(" cannot initialize.");
+        return res;
+    }
+
+    auto *gmainloop = g_main_loop_new(NULL, FALSE);
+    auto data = std::tuple{static_cast<int>(BT_ERROR_OPERATION_FAILED), gmainloop, true};
+    using Data = decltype(data);
+    auto callback = [](int result, bt_adapter_state_e adapterState, void *userData) {
+        auto *innerData = static_cast<Data *>(userData);
+        if (result != BT_ERROR_NONE) {
+            LogError(" callback: result error,");
+            std::get<0>(*innerData) = result;
+        } else if ((adapterState == BT_ADAPTER_ENABLED) != std::get<2>(*innerData)) {
+            LogError(" callback: wrong adapter state,");
+            std::get<0>(*innerData) = BT_ERROR_OPERATION_FAILED;
+        } else {
+            std::get<0>(*innerData) = BT_ERROR_NONE;
+        }
+        g_main_loop_quit(std::get<1>(*innerData));
+    };
+    res = bt_adapter_set_state_changed_cb(callback, &data);
+    if (res != BT_ERROR_NONE) {
+        LogError(" cannot install callback.");
+        goto cleanup_gmainloop;
+    }
+
+    res = bt_adapter_enable();
+    if (res == alreadyDoneError) {
+        LogError(" already done...");
+        res = BT_ERROR_NONE;
+        goto cleanup_callback;
+    }
+    if (res != BT_ERROR_NONE) {
+        LogError("enabling adapter failed.");
+        goto cleanup_callback;
+    }
+    // Wait for the callback.
+    g_main_loop_run(gmainloop);
+    res = std::get<0>(data);
+    if (res != BT_ERROR_NONE)
+        LogError(" callback failed.");
+
+cleanup_callback:
+    err = bt_adapter_unset_state_changed_cb();
+    if (err != BT_ERROR_NONE) {
+        LogError(" cannot uninstall callback.");
+        if (res == BT_ERROR_NONE)
+            res = err;
+    }
+cleanup_gmainloop:
+    g_main_loop_unref(gmainloop);
+    err = bt_deinitialize();
+    if (err != BT_ERROR_NONE) {
+        LogError(" cannot deinitialize.");
+        if (res == BT_ERROR_NONE)
+            res = err;
+    }
+    if (res == BT_ERROR_NONE)
+        LogError(" done.");
+
+    return res;
+}
+
+} // anonymous namespace
+
+[[nodiscard]] int TurnBluetoothOn() { return DoTurnBluetooth(BT_ERROR_ALREADY_DONE); }
diff --git a/tests/webauthn-manual-test-app/src/turn_bluetooth.h b/tests/webauthn-manual-test-app/src/turn_bluetooth.h
new file mode 100644 (file)
index 0000000..79a6058
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#pragma once
+
+#include <bluetooth_type.h>
+
+namespace {
+
+inline const char *BtErrorToString(int err)
+{
+    switch (err) {
+    case BT_ERROR_NONE: return "BT_ERROR_NONE";
+    case BT_ERROR_CANCELLED: return "BT_ERROR_CANCELLED";
+    case BT_ERROR_INVALID_PARAMETER: return "BT_ERROR_INVALID_PARAMETER";
+    case BT_ERROR_OUT_OF_MEMORY: return "BT_ERROR_OUT_OF_MEMORY";
+    case BT_ERROR_RESOURCE_BUSY: return "BT_ERROR_RESOURCE_BUSY";
+    case BT_ERROR_TIMED_OUT: return "BT_ERROR_TIMED_OUT";
+    case BT_ERROR_NOW_IN_PROGRESS: return "BT_ERROR_NOW_IN_PROGRESS";
+    case BT_ERROR_NOT_INITIALIZED: return "BT_ERROR_NOT_INITIALIZED";
+    case BT_ERROR_NOT_ENABLED: return "BT_ERROR_NOT_ENABLED";
+    case BT_ERROR_ALREADY_DONE: return "BT_ERROR_ALREADY_DONE";
+    case BT_ERROR_OPERATION_FAILED: return "BT_ERROR_OPERATION_FAILED";
+    case BT_ERROR_NOT_IN_PROGRESS: return "BT_ERROR_NOT_IN_PROGRESS";
+    case BT_ERROR_REMOTE_DEVICE_NOT_BONDED: return "BT_ERROR_REMOTE_DEVICE_NOT_BONDED";
+    case BT_ERROR_AUTH_REJECTED: return "BT_ERROR_AUTH_REJECTED";
+    case BT_ERROR_AUTH_FAILED: return "BT_ERROR_AUTH_FAILED";
+    case BT_ERROR_REMOTE_DEVICE_NOT_FOUND: return "BT_ERROR_REMOTE_DEVICE_NOT_FOUND";
+    case BT_ERROR_SERVICE_SEARCH_FAILED: return "BT_ERROR_SERVICE_SEARCH_FAILED";
+    case BT_ERROR_REMOTE_DEVICE_NOT_CONNECTED: return "BT_ERROR_REMOTE_DEVICE_NOT_CONNECTED";
+    case BT_ERROR_PERMISSION_DENIED: return "BT_ERROR_PERMISSION_DENIED";
+    case BT_ERROR_SERVICE_NOT_FOUND: return "BT_ERROR_SERVICE_NOT_FOUND";
+    case BT_ERROR_NO_DATA: return "BT_ERROR_NO_DATA";
+    case BT_ERROR_NOT_SUPPORTED: return "BT_ERROR_NOT_SUPPORTED";
+    case BT_ERROR_DEVICE_POLICY_RESTRICTION: return "DEVICE_POLICY_RESTRICTION";
+    default: return "NOT Defined";
+    }
+}
+
+} // anonymous namespace
+
+[[nodiscard]] int TurnBluetoothOn();
diff --git a/tests/webauthn-manual-test-app/src/ui_component.cpp b/tests/webauthn-manual-test-app/src/ui_component.cpp
new file mode 100644 (file)
index 0000000..d970a90
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "ui_component.h"
+
+const int OFFSET = 10;
+
+namespace {
+void WindowBackCb(void *data EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
+{
+    elm_win_lower(obj);
+}
+
+} // anonymous namespace
+
+void UIComponent::NextRow(unsigned incY)
+{
+    m_posX = m_defX;
+    m_posY += incY;
+}
+
+void UIComponent::AddEntry(const std::string &name, Evas_Object **entry)
+{
+    int width = 400;
+    int height = 70;
+    *entry = elm_entry_add(m_win);
+    elm_entry_single_line_set(*entry, EINA_TRUE);
+    elm_entry_entry_set(*entry, name.c_str());
+    elm_entry_input_panel_enabled_set(*entry, EINA_FALSE);
+    evas_object_move(*entry, GetPosX(), GetPosY());
+    evas_object_resize(*entry, width, height);
+    evas_object_show(*entry);
+
+    NextRow(height + OFFSET);
+}
+
+void UIComponent::AddButton(const std::string &name,
+                            Evas_Object **bt,
+                            Evas_Smart_Cb cb,
+                            ColorData cd,
+                            void *data,
+                            bool nextRow)
+{
+    int width = 170;
+    int height = 50;
+    *bt = elm_button_add(m_win);
+    elm_object_text_set(*bt, name.c_str());
+    elm_object_color_class_color_set(*bt, "bg", cd.r, cd.g, cd.b, cd.d);
+    evas_object_smart_callback_add(*bt, "clicked", cb, data);
+    evas_object_move(*bt, GetPosX(), GetPosY());
+    evas_object_resize(*bt, width, height);
+    evas_object_show(*bt);
+
+    if (nextRow)
+        NextRow(height + OFFSET);
+    else
+        IncInRow(width + OFFSET);
+}
+
+void UIComponent::AddCancelButton(
+    const std::string &name, Evas_Object **bt, Evas_Smart_Cb cb, ColorData cd, void *data)
+{
+    int width = 300;
+    int height = 100;
+    *bt = elm_button_add(m_win);
+    elm_object_text_set(*bt, name.c_str());
+    elm_object_color_class_color_set(*bt, "bg", cd.r, cd.g, cd.b, cd.d);
+    evas_object_smart_callback_add(*bt, "clicked", cb, data);
+    evas_object_move(*bt, 170, 500);
+    evas_object_resize(*bt, width, height);
+}
+
+void UIComponent::AddCheckBox(
+    const std::string &name, Evas_Object **chb, Evas_Smart_Cb cb, Eina_Bool checked, void *data)
+{
+    int width = 200;
+    int height = 30;
+    *chb = elm_check_add(m_win);
+    elm_object_text_set(*chb, name.c_str());
+    elm_check_state_set(*chb, checked);
+    evas_object_smart_callback_add(*chb, "changed", cb, data);
+    evas_object_move(*chb, GetPosX(), GetPosY());
+    evas_object_resize(*chb, width, height);
+    evas_object_show(*chb);
+
+    NextRow(height + OFFSET);
+}
+
+void UIComponent::AddLabel(const std::string &name, Evas_Object **label)
+{
+    int width = 600;
+    int height = 30;
+    *label = elm_label_add(m_win);
+    elm_object_text_set(*label, name.c_str());
+    evas_object_move(*label, GetPosX(), GetPosY());
+    evas_object_resize(*label, width, height);
+    evas_object_show(*label);
+
+    NextRow(height + OFFSET);
+}
+
+void UIComponent::AddImage(Evas_Object **image, const std::string &path)
+{
+    int width = 250;
+    int height = 250;
+    int posX = 340;
+    int posY = 150;
+    *image = elm_image_add(m_win);
+    elm_image_file_set(*image, path.c_str(), NULL);
+    elm_image_no_scale_set(*image, EINA_TRUE);
+    elm_image_resizable_set(*image, EINA_TRUE, EINA_TRUE);
+    elm_image_aspect_fixed_set(*image, EINA_TRUE);
+    elm_image_fill_outside_set(*image, EINA_TRUE);
+    evas_object_resize(*image, width, height);
+    evas_object_move(*image, posX, posY);
+    evas_object_show(*image);
+}
+
+UIComponent::UIComponent(Evas_Smart_Cb WindowDeleteCb)
+{
+    m_win = elm_win_util_standard_add(PACKAGE, PACKAGE);
+    elm_win_conformant_set(m_win, EINA_TRUE);
+    elm_win_autodel_set(m_win, EINA_TRUE);
+    elm_win_indicator_mode_set(m_win, ELM_WIN_INDICATOR_SHOW);
+    elm_win_indicator_opacity_set(m_win, ELM_WIN_INDICATOR_OPAQUE);
+
+    if (elm_win_wm_rotation_supported_get(m_win)) {
+        int rots[4] = {0, 90, 180, 270};
+        elm_win_wm_rotation_available_rotations_set(m_win, (const int *)(&rots), 4);
+    }
+
+    evas_object_smart_callback_add(m_win, "delete,request", WindowDeleteCb, NULL);
+    eext_object_event_callback_add(m_win, EEXT_CALLBACK_BACK, WindowBackCb, &m_win);
+}
+
+UIComponent::~UIComponent()
+{
+    if (m_win)
+        evas_object_del(m_win);
+
+    m_win = nullptr;
+}
diff --git a/tests/webauthn-manual-test-app/src/ui_component.h b/tests/webauthn-manual-test-app/src/ui_component.h
new file mode 100644 (file)
index 0000000..1edbbb1
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#pragma once
+
+#include "common.h"
+
+#include <efl_extension.h>
+#include <iostream>
+#include <string>
+
+class UIComponent {
+public:
+    UIComponent(Evas_Smart_Cb WindowDeleteCb);
+
+    ~UIComponent();
+
+    void AddEntry(const std::string &name, Evas_Object **entry);
+    void AddButton(const std::string &name,
+                   Evas_Object **bt,
+                   Evas_Smart_Cb cb,
+                   ColorData cd,
+                   void *data,
+                   bool nextRow = false);
+    void AddCancelButton(
+        const std::string &name, Evas_Object **bt, Evas_Smart_Cb cb, ColorData cd, void *data);
+    void AddCheckBox(const std::string &name,
+                     Evas_Object **chb,
+                     Evas_Smart_Cb cb,
+                     Eina_Bool checked,
+                     void *data);
+    void AddLabel(const std::string &name, Evas_Object **label);
+    void AddImage(Evas_Object **image, const std::string &path);
+
+    void ShowWindow() const { evas_object_show(m_win); }
+
+    void IncInRow(unsigned incX) { m_posX += incX; }
+
+protected:
+    void NextRow(unsigned incY);
+
+    int GetPosX() { return m_posX; }
+
+    int GetPosY() { return m_posY; }
+
+    Evas_Object *m_win = nullptr;
+    const unsigned m_defX = 50;
+    unsigned m_posX = 50;
+    unsigned m_posY = 100;
+};
diff --git a/tests/webauthn-manual-test-app/src/webauthn-manual-test-app.cpp b/tests/webauthn-manual-test-app/src/webauthn-manual-test-app.cpp
new file mode 100644 (file)
index 0000000..386b46a
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "app_lifecycle.h"
+#include "turn_bluetooth.h"
+
+#include <efl_extension.h>
+#include <iostream>
+
+int main(int argc, char *argv[])
+{
+    int err = TurnBluetoothOn();
+    if (err != BT_ERROR_NONE) {
+        std::cout << "Turning bluetooth on failed with " << BtErrorToString(err) << std::endl;
+        return -1;
+    }
+
+    AppLifecycle alc;
+    err = alc.RunApp(argc, argv);
+    if (err != 0) {
+        std::cout << "Runing app failed with " << err << std::endl;
+        return -1;
+    }
+
+    return 0;
+}