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")
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)
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)
--- /dev/null
+<manifest>
+ <request>
+ <domain name="_"/>
+ </request>
+</manifest>
+
%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
%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
-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
%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}/*
--- /dev/null
+# 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})
--- /dev/null
+# 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})
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+};
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+};
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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);
+};
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
--- /dev/null
+/*
+ * 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;
+};
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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();
--- /dev/null
+/*
+ * 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); }
--- /dev/null
+/*
+ * 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();
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+};
--- /dev/null
+/*
+ * 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;
+}