diff --git a/CMakeLists.txt b/CMakeLists.txt index 03105ce..e6c285e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ option(BLE_ENABLED "Enable BLE" OFF) option(BLE_SERVICES "Enable BLE Services" OFF) option(BREAKPAD "Enable BREAKPAD" OFF) option(BUILD_CTRLM_FACTORY "Build Control Factory Test" OFF) +option(BUILD_CTRLM_SERVER "Build Control Server Daemon" ON) option(CPC "Enable CPC" OFF) option(DISABLE_BLE_VOICE "Disable BLE voice" OFF) option(DEEPSLEEP_CLOSE_DB "Deep Sleep Close DB" OFF) diff --git a/include/ctrlm_ipc_voice.h b/include/ctrlm_ipc_voice.h index 1a63c32..6c08c0a 100644 --- a/include/ctrlm_ipc_voice.h +++ b/include/ctrlm_ipc_voice.h @@ -208,6 +208,7 @@ typedef struct { ctrlm_controller_id_t controller_id; ///< A unique identifier of the remote unsigned long long ieee_address; ///< IEEE MAC address of the remote ctrlm_iarm_call_result_t result; ///< OUT - The result of the operation. + unsigned char service_id; } ctrlm_voice_iarm_call_voice_session_t; typedef struct { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2630492..dc879fc 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -140,6 +140,10 @@ if(BUILD_FACTORY_TEST) add_subdirectory(factory) endif() +if(BUILD_CTRLM_SERVER) + add_subdirectory(server) +endif() + if(RF4CE_ENABLED) target_sources(controlMgr PRIVATE rf4ce/controller/attributes/ctrlm_rf4ce_controller_attr_battery.cpp diff --git a/src/ble/ctrlm_ble_controller.cpp b/src/ble/ctrlm_ble_controller.cpp index d888615..7125b4c 100644 --- a/src/ble/ctrlm_ble_controller.cpp +++ b/src/ble/ctrlm_ble_controller.cpp @@ -499,10 +499,14 @@ bool ctrlm_obj_controller_ble_t::is_stale(time_t stale_time_threshold) const { return false; } -bool ctrlm_obj_controller_ble_t::isVoiceKey(uint16_t key_code) const { +bool ctrlm_obj_controller_ble_t::isVoiceKey(uint16_t key_code, bool &listenForKeyNames) const { if(key_code == voice_key_code_) { return true; } + if(key_code == KEY_F23) { + listenForKeyNames = true; + return true; + } return false; } diff --git a/src/ble/ctrlm_ble_controller.h b/src/ble/ctrlm_ble_controller.h index 5179578..765f0c6 100644 --- a/src/ble/ctrlm_ble_controller.h +++ b/src/ble/ctrlm_ble_controller.h @@ -138,7 +138,7 @@ class ctrlm_obj_controller_ble_t : public ctrlm_obj_controller_t { void print_status(); virtual bool is_stale(time_t stale_time_threshold) const; - bool isVoiceKey(uint16_t key_code) const; + bool isVoiceKey(uint16_t key_code, bool &listenForKeyNames) const; void setPressAndHoldSupport(bool supported); bool getPressAndHoldSupport() const; diff --git a/src/ble/ctrlm_ble_network.cpp b/src/ble/ctrlm_ble_network.cpp index 24f1fd4..fd0f175 100644 --- a/src/ble/ctrlm_ble_network.cpp +++ b/src/ble/ctrlm_ble_network.cpp @@ -494,7 +494,7 @@ void ctrlm_obj_network_ble_t::req_process_voice_session_begin(void *data, int si controllers_[controller_id]->get_model().c_str(), controllers_[controller_id]->get_sw_revision().to_string().c_str(), controllers_[controller_id]->get_hw_revision().to_string().c_str(), 0.0, - false, NULL, NULL, NULL, true, pressAndHoldSupport); + false, NULL, NULL, NULL, true, pressAndHoldSupport, dqm->params->service_id, NULL, NULL, NULL, (dqm->params->service_id == 1) ? true : false); if (!controllers_[controller_id]->get_capabilities().has_capability(ctrlm_controller_capabilities_t::capability::PAR) && (VOICE_SESSION_RESPONSE_AVAILABLE_PAR_VOICE == voice_status)) { XLOGD_WARN("PAR voice is enabled but not supported by BLE controller treating as normal voice session"); voice_status = VOICE_SESSION_RESPONSE_AVAILABLE; @@ -2102,8 +2102,9 @@ void ctrlm_obj_network_ble_t::ind_process_keypress(void *data, int size) { ctrlm_obj_controller_ble_t *controller = controllers_[controller_id]; if (key_status == CTRLM_KEY_STATUS_DOWN) { + bool listenForKeyNames = false; - if (controller->isVoiceKey(dqm->event.code)) { + if (controller->isVoiceKey(dqm->event.code, listenForKeyNames)) { rdkx_timestamp_t keyDownTime; keyDownTime.tv_sec = dqm->event.time.tv_sec; keyDownTime.tv_nsec = dqm->event.time.tv_usec * 1000; @@ -2116,6 +2117,7 @@ void ctrlm_obj_network_ble_t::ind_process_keypress(void *data, int size) { ctrlm_voice_iarm_call_voice_session_t v_params; v_params.ieee_address = dqm->ieee_address; + v_params.service_id = listenForKeyNames ? 1 : 0; ctrlm_main_queue_msg_voice_session_t msg; errno_t safec_rc = memset_s(&msg, sizeof(msg), 0, sizeof(msg)); @@ -2123,6 +2125,10 @@ void ctrlm_obj_network_ble_t::ind_process_keypress(void *data, int size) { msg.params = &v_params; req_process_voice_session_begin(&msg, sizeof(msg)); + + if (listenForKeyNames) { + XLOGD_WARN("listening for key names in the voice session"); + } } if (controller->getUpgradeInProgress()) { @@ -2157,9 +2163,10 @@ void ctrlm_obj_network_ble_t::ind_process_keypress(void *data, int size) { } } else if (key_status == CTRLM_KEY_STATUS_UP) { - - if (controller->isVoiceKey(dqm->event.code)) { - if(!controller->getPressAndHoldSupport()) { // if the voice session is "Press and Release" then don't end session on voice key up event + bool listenForKeyNames = false; + if (controller->isVoiceKey(dqm->event.code, listenForKeyNames)) { + // TODO This needs to check if the voice session is "Press and Hold" or "Press and Release" based on the key code + if(!controller->getPressAndHoldSupport() || dqm->event.code == KEY_F23) { // if the voice session is "Press and Release" then don't end session on voice key up event XLOGD_INFO("------------------------------------------------------------------------"); XLOGD_INFO("CODE_VOICE_KEY button RELEASED event for device: %s (ignored for PAR session)", controller->ieee_address_get().to_string().c_str()); XLOGD_INFO("------------------------------------------------------------------------"); @@ -2272,6 +2279,17 @@ void ctrlm_obj_network_ble_t::ind_process_voice_session_end(void *data, int size XLOGD_ERROR("Controller object doesn't exist for controller id %u!", controller_id); return; } + + unsigned long long ieee_address = controllers_[controller_id]->ieee_address_get().get_value();; + + if (ble_rcu_interface_) { + int32_t audioDuration = -1; + if (!ble_rcu_interface_->stopAudioStreaming(ieee_address, audioDuration)) { + XLOGD_ERROR("failed to end voice session for controller id <%u>", controller_id); + } else { + XLOGD_INFO("voice session ended for controller id <%u>", controller_id); + } + } } // ================================================================================================================================================================== diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audiopipe.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audiopipe.cpp index 85009b1..d36a1d0 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audiopipe.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audiopipe.cpp @@ -117,6 +117,14 @@ GattAudioPipe::GattAudioPipe(uint8_t frameSize, uint32_t frameCountMax, cbFrameV m_outputPipeWrFd = fds[1]; } + // Set the pipe size + uint32_t size = 256 * 1024; + + int rc = fcntl(m_outputPipeWrFd, F_SETPIPE_SZ, size); + if(rc < (int)size) { // emit a warning if the kernel returns a pipe size smaller than we requested + XLOGD_WARN("set pipe size failed exp <%u> rxd <%d>", size, rc); + } + } // ----------------------------------------------------------------------------- diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp index 4a3e359..ec9819d 100644 --- a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp +++ b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp @@ -620,7 +620,7 @@ void GattAudioService::stopStreaming(uint32_t audioDuration, PendingReply<> &&re } if(m_missedSequences >= frameCountMax) { - XLOGD_ERROR("missed frames greater than frame count max"); + XLOGD_ERROR("missed frames <%u> greater than frame count max <%u>", m_missedSequences, frameCountMax); } else { frameCountMax -= m_missedSequences; // compensate for missed frames diff --git a/src/ctrlm_config_default.json b/src/ctrlm_config_default.json index 8d536a0..0c4755b 100755 --- a/src/ctrlm_config_default.json +++ b/src/ctrlm_config_default.json @@ -237,6 +237,7 @@ "url_src_ptt" : "", "url_src_ff" : "", "url_src_mic_tap" : "", + "url_src_key_listen" : "vrng://localhost:9881/", "vrex_request_timeout" : 30, "enable_sat" : true, "enable_mtls" : true, @@ -245,7 +246,7 @@ "minimum_duration" : 300, "ffv_leading_samples" : 1600, "timeout_packet_initial" : 3200, - "timeout_packet_subsequent" : 250, + "timeout_packet_subsequent" : 1000, "bitrate_minimum" : 64, "time_threshold" : 1000, "timeout_stats" : 3000, diff --git a/src/ctrlm_controller.cpp b/src/ctrlm_controller.cpp index 931a75f..1a1931e 100644 --- a/src/ctrlm_controller.cpp +++ b/src/ctrlm_controller.cpp @@ -176,11 +176,14 @@ void ctrlm_obj_controller_t::process_event_key(ctrlm_key_status_t key_status, ui last_key_code_->set_value((uint64_t)key_code); last_key_time_update(); + // TODO This is a temporary hack to avoid printing REPEAT keys for the secondary voice button + if(key_status != CTRLM_KEY_STATUS_REPEAT) { XLOGD_TELEMETRY("ind_process_keypress: %s - MAC Address <%s>, code = <%d> (%s key), status = <%s>", controller_type_str_get().c_str(), ieee_address_get().to_string().c_str(), mask ? -1 : key_code, ctrlm_linux_key_code_str(key_code, mask), ctrlm_key_status_str(key_status)); + } } ctrlm_controller_capabilities_t ctrlm_obj_controller_t::get_capabilities() const { diff --git a/src/ctrlm_utils.cpp b/src/ctrlm_utils.cpp index 14f0950..084023a 100644 --- a/src/ctrlm_utils.cpp +++ b/src/ctrlm_utils.cpp @@ -1295,6 +1295,7 @@ static const map> ctrlm_linux_key_name {KEY_F20, {"Info", "Info"}}, {KEY_F21, {"Guide", "Guide"}}, {KEY_F22, {"Accessibility", "Accessibility"}}, + {KEY_F23, {"Voice Alt", "Voice Alt"}}, {KEY_F8, {"Voice", "Voice"}}, {KEY_ESC, {"Dismiss", "Dismiss"}}, {KEY_F9, {"Quick Access Menu", "Quick Access Menu"}}, diff --git a/src/input_event/ctrlm_input_event_writer.cpp b/src/input_event/ctrlm_input_event_writer.cpp index c463a78..98b4714 100644 --- a/src/input_event/ctrlm_input_event_writer.cpp +++ b/src/input_event/ctrlm_input_event_writer.cpp @@ -146,6 +146,25 @@ uint16_t ctrlm_input_event_writer::write_event(ctrlm_key_code_t code, ctrlm_key_ return param.key_code; } +uint16_t ctrlm_input_event_writer::write_event_linux(uint16_t code, ctrlm_key_status_t status) { + if (!initialized_) { + XLOGD_ERROR("User input device is not yet initialized!"); + return KEY_RESERVED; + } + + if (ev_key_value_map.find(status) == ev_key_value_map.end()) { + XLOGD_ERROR("Key status <%d> not found in mapping", status); + return KEY_RESERVED; + } + + // TODO the scan code may need to vary based on the key code + if (!write_event_internal(0, code, ev_key_value_map.at(status))) { + return KEY_RESERVED; + } + + return code; +} + bool ctrlm_input_event_writer::get_meta_data(struct stat &file_meta_data) { int ret = fstat(fd_, &file_meta_data); if (ret == -1) { diff --git a/src/input_event/ctrlm_input_event_writer.h b/src/input_event/ctrlm_input_event_writer.h index 487ae57..099496f 100644 --- a/src/input_event/ctrlm_input_event_writer.h +++ b/src/input_event/ctrlm_input_event_writer.h @@ -117,6 +117,7 @@ class ctrlm_input_event_writer { bool init(std::string uinput_name, uint32_t vendor, uint32_t product); void shutdown(void); uint16_t write_event(ctrlm_key_code_t code, ctrlm_key_status_t status); + uint16_t write_event_linux(uint16_t code, ctrlm_key_status_t status); bool get_meta_data(struct stat &file_meta_data); }; diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt new file mode 100644 index 0000000..20d5db9 --- /dev/null +++ b/src/server/CMakeLists.txt @@ -0,0 +1,58 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2019 RDK Management +# +# 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. +########################################################################## + +add_executable(controlServer ctrlms_main.c) + +include_directories( + . + ${CMAKE_SYSROOT}/usr/include/safeclib + ${CMAKE_SYSROOT}/usr/include/libsafec + ${CMAKE_SYSROOT}/usr/include/nopoll + ${CMAKE_SYSROOT}/usr/include +) + +target_sources(controlServer PRIVATE + ctrlms_version.c + ctrlms_utils.c + ctrlms_ws.cpp +) + +target_compile_options(controlServer PUBLIC -fPIC -rdynamic -Wall -Werror) + +target_link_libraries(controlServer c rdkversion pthread nopoll jansson xr-voice-sdk secure_wrapper systemd ${CMAKE_DL_LIBS}) + +#if(EXISTS ${CMAKE_SYSROOT}/usr/lib/libctrlm-hal-certificate.so) + # TODO This needs to be done in certselector. mountutils recipe with rdkconfig.a static library +# target_link_libraries(controlServer rdkconfig.a) + +# add_compile_definitions(PRIVATE CTRLMS_WSS_ENABLED) +# target_link_libraries(controlServer ctrlm-hal-certificate ssl crypto) +#endif() + +if(USE_SAFEC) + find_package(PkgConfig) + pkg_check_modules(SAFEC REQUIRED libsafec) + if(SAFEC_FOUND) + target_link_libraries(controlServer ${SAFEC_LIBRARIES}) + endif() +else() + add_compile_definitions(PUBLIC SAFEC_DUMMY_API) +endif() + +install(TARGETS controlServer DESTINATION bin) diff --git a/src/server/ctrlm_server_app.h b/src/server/ctrlm_server_app.h new file mode 100644 index 0000000..919c00f --- /dev/null +++ b/src/server/ctrlm_server_app.h @@ -0,0 +1,49 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2014 RDK Management + * + * 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 +#include +#include + +class ctrlms_app_interface_t +{ + public: + virtual ~ctrlms_app_interface_t() {}; + + virtual void ws_connected(void); + virtual void ws_disconnected(void); + virtual bool ws_receive_audio(const unsigned char *payload, int payload_size); + virtual bool ws_receive_json(const json_t *json_obj); + void ws_send_json(const json_t *json_obj); + void ws_handle_set(void *handle); + + private: + void *ws_handle; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +ctrlms_app_interface_t *ctrlms_app_interface_create(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/server/ctrlm_server_platform.h b/src/server/ctrlm_server_platform.h new file mode 100644 index 0000000..8342e68 --- /dev/null +++ b/src/server/ctrlm_server_platform.h @@ -0,0 +1,48 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2014 RDK Management + * + * 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. +*/ +#ifndef _CTRLM_SERVER_PLATFORM_H_ +#define _CTRLM_SERVER_PLATFORM_H_ + +#include + +typedef enum { + CTRLM_FTA_PLATFORM_VOICE_CERT_TYPE_NONE = 0, + CTRLM_FTA_PLATFORM_VOICE_CERT_TYPE_P12 = 1, + CTRLM_FTA_PLATFORM_VOICE_CERT_TYPE_INVALID = 2 +} ctrlm_fta_platform_voice_cert_type_t; + +typedef struct { + ctrlm_fta_platform_voice_cert_type_t type; + char *filename; + char *password; +}ctrlm_fta_platform_cert_info_t; + + +#ifdef __cplusplus +extern "C" { +#endif + +ctrlm_fta_platform_cert_info_t *ctrlm_fta_platform_cert_info_get(bool allow_expired); +void ctrlm_fta_platform_cert_info_free(ctrlm_fta_platform_cert_info_t *cert_info); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/server/ctrlms_main.c b/src/server/ctrlms_main.c new file mode 100644 index 0000000..c34bd27 --- /dev/null +++ b/src/server/ctrlms_main.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CTRLMS_VERSION "1.0" + +#define CTRLMS_WS_PORT_INT (9881) + +typedef struct { + bool silent; + bool verbose; +} ctrlms_options_t; + +static bool ctrlms_cmdline_args(int argc, char *argv[]); +static error_t ctrlms_parse_opt(int key, char *arg, struct argp_state *state); + +const char *argp_program_version = "controlServer " CTRLMS_VERSION; +const char *argp_program_bug_address = ""; + +static char doc[] = "controlServer -- a server application"; + +static char args_doc[] = ""; + +static struct argp_option options[] = { + {"verbose", 'v', 0, 0, "Produce verbose output" }, + {"quiet", 'q', 0, 0, "Don't produce any output" }, + { 0 } +}; + +static struct argp argp = { options, ctrlms_parse_opt, args_doc, doc }; + +static ctrlms_options_t g_ctrlms_opts = { .silent = false, + .verbose = false + }; + +int main(int argc, char* argv[]) { + // Parse command line arguments + if(!ctrlms_cmdline_args(argc, argv)) { + return(-1); + } + + xlog_level_t level = XLOG_LEVEL_WARN; + if(g_ctrlms_opts.silent) { + level = XLOG_LEVEL_ERROR; + } else if(g_ctrlms_opts.verbose) { + level = XLOG_LEVEL_INFO; + // TODO Add option to allow debug level logging + //} else if() { + // level = XLOG_LEVEL_DEBUG; + } + + if(!ctrlms_init(level)) { + XLOGD_ERROR("ctrlms_main: init failed"); + } else { + + // TODO Start listening for connections + if(!ctrlms_ws_init(CTRLMS_WS_PORT_INT, true)) { + XLOGD_ERROR("ctrlms_main: ws init failed"); + } else { + XLOGD_INFO("Notifying systemd of successful initialization"); + sd_notifyf(0, "READY=1\nSTATUS=ctrlm-server has successfully initialized\nMAINPID=%lu", (unsigned long)getpid()); + ctrlms_ws_listen(); + ctrlms_ws_term(); + } + XLOGD_INFO("ctrlms_main: main loop ended"); + } + ctrlms_term(); + XLOGD_INFO("ctrlms_main: return"); + + return(0); +} + +error_t ctrlms_parse_opt(int key, char *arg, struct argp_state *state) { + // Get the input argument from argp_parse, which we know is a pointer to our arguments structure. + ctrlms_options_t *arguments = state->input; + + switch(key) { + case 'q': { + arguments->silent = true; + break; + } + case 'v': { + arguments->verbose = true; + break; + } + case ARGP_KEY_ARG: { + argp_usage(state); + return(ARGP_ERR_UNKNOWN); + } + case ARGP_KEY_END: { + break; + } + default: { + return(ARGP_ERR_UNKNOWN); + } + } + + return(0); +} + +bool ctrlms_cmdline_args(int argc, char *argv[]) { + argp_parse(&argp, argc, argv, 0, 0, &g_ctrlms_opts); + + #if 0 + if() { // Nothing to do + printf("Invalid options specified. Try 'controlServer --help' or 'controlServer --usage' for more information.\n"); + return(false); + } + #endif + + XLOGD_INFO("verbose <%s>", g_ctrlms_opts.verbose ? "YES" : "NO"); + XLOGD_INFO("silent <%s>", g_ctrlms_opts.silent ? "YES" : "NO"); + + return(true); +} diff --git a/src/server/ctrlms_utils.c b/src/server/ctrlms_utils.c new file mode 100644 index 0000000..800228a --- /dev/null +++ b/src/server/ctrlms_utils.c @@ -0,0 +1,17 @@ +#include +#include +#include + +/* +#define CTRLMS_INVALID_STR_LEN (24) + +static char ctrlms_invalid_str[CTRLMS_INVALID_STR_LEN]; + +static const char *ctrlms_invalid_return(int value); + +const char *ctrlms_invalid_return(int value) { + snprintf(ctrlms_invalid_str, sizeof(ctrlms_invalid_str), "INVALID(%d)", value); + ctrlms_invalid_str[sizeof(ctrlms_invalid_str) - 1] = '\0'; + return(ctrlms_invalid_str); +} +*/ \ No newline at end of file diff --git a/src/server/ctrlms_utils.h b/src/server/ctrlms_utils.h new file mode 100644 index 0000000..6acf128 --- /dev/null +++ b/src/server/ctrlms_utils.h @@ -0,0 +1,35 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2014 RDK Management + * + * 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. +*/ +#ifndef _CTRLMS_UTILS_H_ +#define _CTRLMS_UTILS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool ctrlms_is_initialized(void); +bool ctrlms_is_production(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/server/ctrlms_version.c b/src/server/ctrlms_version.c new file mode 100644 index 0000000..d6f44a8 --- /dev/null +++ b/src/server/ctrlms_version.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + bool initialized; + bool is_production; +} ctrlms_global_t; + +ctrlms_global_t g_ctrlms = { + .initialized = false, + .is_production = true +}; + +bool ctrlms_init(xlog_level_t level) { + rdk_version_info_t info; + int ret_val = rdk_version_parse_version(&info); + + if(ret_val != 0) { + XLOGD_ERROR("parse error <%s>\n", info.parse_error == NULL ? "" : info.parse_error); + return(false); + } + + g_ctrlms.is_production = info.production_build; + + rdk_version_object_free(&info); + + int rc = xlog_init(XLOG_MODULE_ID, NULL, 0); + xlog_level_set_all(level); + + if(rc != 0) { + XLOGD_ERROR("failed to init xlog"); + return(false); + } + + g_ctrlms.initialized = true; + return(true); +} + +void ctrlms_term(void) { + g_ctrlms.initialized = false; + xlog_term(); +} + +bool ctrlms_is_initialized(void) { + return(g_ctrlms.initialized); +} + +bool ctrlms_is_production(void) { + return(g_ctrlms.is_production); +} + diff --git a/src/server/ctrlms_version.h b/src/server/ctrlms_version.h new file mode 100644 index 0000000..4b64177 --- /dev/null +++ b/src/server/ctrlms_version.h @@ -0,0 +1,36 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2014 RDK Management + * + * 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. +*/ +#ifndef _CTRLMS_VERSION_H_ +#define _CTRLMS_VERSION_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool ctrlms_init(xlog_level_t level); +void ctrlms_term(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/server/ctrlms_ws.cpp b/src/server/ctrlms_ws.cpp new file mode 100644 index 0000000..a0331e0 --- /dev/null +++ b/src/server/ctrlms_ws.cpp @@ -0,0 +1,546 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CTRLMS_WSS_ENABLED +#include +#include +#define CTRLMS_WS_CIPHER_LIST "AES256-SHA256:AES128-GCM-SHA256:AES128-SHA256" +#define CTRLMS_WS_TLS_CERT_KEY_FILE "/tmp/serverXXXXXX" +#define CTRLMS_WS_CERT_NAME_LEN (1024) +#define CTRLMS_WS_CERT_PW_LEN (128) +#endif + +typedef enum { + CTRLMS_WS_MSG_TYPE_TEXT = 0, + CTRLMS_WS_MSG_TYPE_BINARY = 1, + CTRLMS_WS_MSG_TYPE_UNKNOWN = 2 +} ctrlms_ws_msg_type_t; + +typedef struct { + sem_t * semaphore; + uint16_t port; + bool log_enable; +} ctrlms_ws_thread_params_t; + +typedef struct { + noPollConn * nopoll_conn; + void * app_handle; + ctrlms_app_interface_t *app_interface; +} ctrlms_ws_thread_state_t; + +typedef struct { + pthread_t thread_id; + noPollCtx * nopoll_ctx; +} ctrlms_ws_global_t; + +static void *ctrlms_ws_main(void *param); +static void *ctrlms_ws_load_app(ctrlms_ws_thread_state_t *state); + +static nopoll_bool ctrlms_ws_on_accept(noPollCtx *ctx, noPollConn *conn, noPollPtr user_data); +static nopoll_bool ctrlms_ws_on_ready(noPollCtx *ctx, noPollConn *conn, noPollPtr user_data); +static void ctrlms_ws_on_message(noPollCtx *ctx, noPollConn *conn, noPollMsg *msg, noPollPtr user_data); +static void ctrlms_ws_on_ping(noPollCtx *ctx, noPollConn *conn, noPollMsg *msg, noPollPtr user_data); +static void ctrlms_ws_on_close(noPollCtx *ctx, noPollConn *conn, noPollPtr user_data); +static void ctrlms_ws_nopoll_log(noPollCtx * ctx, noPollDebugLevel level, const char * log_msg, noPollPtr user_data); +#ifdef CTRLMS_WSS_ENABLED +static bool ctrlms_ws_cert_config(FILE *cert_key_fp); +static bool ctrlms_ws_add_chain(FILE *cert_key_fp, STACK_OF(X509) *additional_certs); +#endif + +ctrlms_ws_global_t g_ctrlms_ws; + +bool ctrlms_ws_init(uint16_t port, bool log_enable) { + ctrlms_ws_thread_params_t params; + + sem_t semaphore; + sem_init(&semaphore, 0, 0); + + // Launch thread + params.semaphore = &semaphore; + + params.port = port; + params.log_enable = log_enable; + + g_ctrlms_ws.nopoll_ctx = NULL; + + if(0 != pthread_create(&g_ctrlms_ws.thread_id, NULL, ctrlms_ws_main, ¶ms)) { + XLOGD_ERROR("unable to launch thread"); + return(false); + } + + // Block until initialization is complete or a timeout occurs + XLOGD_INFO("Waiting for thread initialization..."); + sem_wait(&semaphore); + sem_destroy(&semaphore); + return(true); +} + +bool ctrlms_ws_listen(void) { + // Wait for thread to exit + XLOGD_INFO("Waiting until thread exits"); + void *retval = NULL; + pthread_join(g_ctrlms_ws.thread_id, &retval); + XLOGD_INFO("thread exited."); + return(true); +} + +void ctrlms_ws_term(void) { + if(g_ctrlms_ws.nopoll_ctx != NULL) { + nopoll_loop_stop(g_ctrlms_ws.nopoll_ctx); + } + + // Wait for thread to exit + XLOGD_INFO("Waiting for thread to exit"); + void *retval = NULL; + pthread_join(g_ctrlms_ws.thread_id, &retval); + XLOGD_INFO("thread exited."); +} + +void *ctrlms_ws_main(void *param) { + ctrlms_ws_thread_params_t params = *((ctrlms_ws_thread_params_t *)param); + errno_t safec_rc = -1; + + ctrlms_ws_thread_state_t state; + + state.app_interface = NULL; + state.app_handle = ctrlms_ws_load_app(&state); + + g_ctrlms_ws.nopoll_ctx = nopoll_ctx_new(); + if(g_ctrlms_ws.nopoll_ctx == NULL) { + XLOGD_ERROR("nopoll context create"); + return(NULL); + } + + #ifdef CTRLMS_WSS_ENABLED + int cert_key_fd = -1; + FILE *cert_key_fp = NULL; + char tmp_cert[32] = {0}; + int err_store; + + safec_rc = sprintf_s(tmp_cert, sizeof(tmp_cert), "%s", CTRLMS_WS_TLS_CERT_KEY_FILE); + if(safec_rc < EOK) { + ERR_CHK(safec_rc); + } + + umask(0600); + cert_key_fd = mkstemp(tmp_cert); + if (cert_key_fd == -1) + { + err_store = errno; + XLOGD_ERROR("mkstemp failed: <%s>", strerror(err_store)); + return(NULL); + } + + cert_key_fp = fdopen(cert_key_fd, "w"); + if(cert_key_fp == NULL) { + err_store = errno; + XLOGD_ERROR("fdopen failed: <%s>", strerror(err_store)); + if(0 != unlink(&tmp_cert[0])) { + err_store = errno; + XLOGD_ERROR("failed to remove temp cert <%s>", strerror(err_store)); + } + return(NULL); + } + + if(!ctrlms_ws_cert_config(cert_key_fp)) { + XLOGD_ERROR("failed to set cert or key, exit"); + fclose(cert_key_fp); + if(0 != unlink(&tmp_cert[0])) { + err_store = errno; + XLOGD_ERROR("failed to remove temp cert <%s>", strerror(err_store)); + } + return(NULL); + } + fclose(cert_key_fp); + + // Init OpenSSL + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + #endif + + if(params.log_enable) { + nopoll_log_enable(g_ctrlms_ws.nopoll_ctx, nopoll_true); + nopoll_log_set_handler(g_ctrlms_ws.nopoll_ctx, ctrlms_ws_nopoll_log, NULL); + } + noPollConnOpts *opts = nopoll_conn_opts_new(); + nopoll_ctx_set_on_accept(g_ctrlms_ws.nopoll_ctx, ctrlms_ws_on_accept, &state); + nopoll_ctx_set_on_ready(g_ctrlms_ws.nopoll_ctx, ctrlms_ws_on_ready, &state); + nopoll_ctx_set_on_msg(g_ctrlms_ws.nopoll_ctx, ctrlms_ws_on_message, &state); + + #ifdef CTRLMS_WSS_ENABLED + nopoll_conn_opts_set_ssl_protocol(opts, NOPOLL_METHOD_TLSV1_2); + nopoll_conn_opts_ssl_host_verify(opts, nopoll_false); //localhost will not match host specified in certificate + + if(!nopoll_conn_opts_set_ssl_certs(opts, &tmp_cert[0], &tmp_cert[0], NULL, NULL)) { + XLOGD_ERROR("Failed to add cert/key files to nopoll_conn"); + nopoll_ctx_unref(g_ctrlms_ws.nopoll_ctx); + nopoll_conn_opts_free(opts); + return(NULL); + } + #endif + + char port[6]; + safec_rc = sprintf_s(port, sizeof(port), "%u", params.port); + if(safec_rc < EOK) { + ERR_CHK(safec_rc); + } + + // Start IPv4/6 listener + #ifdef CTRLMS_WSS_ENABLED + state.nopoll_conn = nopoll_listener_tls_new_opts6(g_ctrlms_ws.nopoll_ctx, opts, "::", port); + #else + state.nopoll_conn = nopoll_listener_new_opts6(g_ctrlms_ws.nopoll_ctx, opts, "::", port); + #endif + if(!nopoll_conn_is_ok(state.nopoll_conn)) { + XLOGD_ERROR("Listener connection IPv6 NOT ok"); + nopoll_ctx_unref(g_ctrlms_ws.nopoll_ctx); + g_ctrlms_ws.nopoll_ctx = NULL; + return(NULL); + } + + // Unblock the caller that launched this thread + sem_post(params.semaphore); + params.semaphore = NULL; + + XLOGD_INFO("Enter main loop"); + + nopoll_loop_wait(g_ctrlms_ws.nopoll_ctx, 0); + + nopoll_conn_opts_unref(opts); + nopoll_conn_unref(state.nopoll_conn); + nopoll_ctx_unref(g_ctrlms_ws.nopoll_ctx); + g_ctrlms_ws.nopoll_ctx = NULL; + + #ifdef CTRLMS_WSS_ENABLED + if(0 != unlink(tmp_cert)) { + int err_store = errno; + XLOGD_ERROR("failed to remove temp cert <%s>", strerror(err_store)); + } + #endif + + if(state.app_handle != NULL) { + dlclose(state.app_handle); + state.app_handle = NULL; + } + if(state.app_interface != NULL) { + delete state.app_interface; + state.app_interface = NULL; + } + + return(NULL); +} + +nopoll_bool ctrlms_ws_on_accept(noPollCtx *ctx, noPollConn *conn, noPollPtr user_data) { + ctrlms_ws_thread_state_t *state = (ctrlms_ws_thread_state_t *)user_data; + + if(state == NULL) { + XLOGD_ERROR("invalid params"); + return(nopoll_false); + } + + // Set ping handler + nopoll_conn_set_on_ping_msg(conn, ctrlms_ws_on_ping, NULL); + + return(nopoll_true); +} + +nopoll_bool ctrlms_ws_on_ready(noPollCtx *ctx, noPollConn *conn, noPollPtr user_data) { + ctrlms_ws_thread_state_t *state = (ctrlms_ws_thread_state_t *)user_data; + + if(state == NULL) { + XLOGD_ERROR("invalid params"); + return(nopoll_false); + } + + XLOGD_INFO("Connection established"); + state->app_interface->ws_handle_set((void *)conn); + state->app_interface->ws_connected(); + + nopoll_conn_set_on_close(conn, ctrlms_ws_on_close, user_data); + + return(nopoll_true); +} + +void ctrlms_ws_on_message(noPollCtx *ctx, noPollConn *conn, noPollMsg *msg, noPollPtr user_data) { + ctrlms_ws_thread_state_t *state = (ctrlms_ws_thread_state_t *)user_data; + + if(state == NULL) { + XLOGD_ERROR("invalid params"); + return; + } + + bool close_conn = false; + int payload_size = nopoll_msg_get_payload_size(msg); + const unsigned char *payload = nopoll_msg_get_payload(msg); + + switch(nopoll_msg_opcode(msg)) { + case NOPOLL_TEXT_FRAME: { + XLOGD_DEBUG("NOPOLL_TEXT_FRAME size <%d>", payload_size); + + json_t *json_obj = json_loads((const char *)payload, 0, NULL); + + if(json_obj == NULL) { + XLOGD_ERROR("Failed to parse JSON object"); + break; + } else { + // Pass the incoming payload to the application + close_conn = state->app_interface->ws_receive_json(json_obj); + json_decref(json_obj); + } + break; + } + case NOPOLL_BINARY_FRAME: { + XLOGD_DEBUG("NOPOLL_BINARY_FRAME size <%d>", payload_size); + + // Pass the incoming payload to the application + close_conn = state->app_interface->ws_receive_audio(payload, payload_size); + break; + } + case NOPOLL_CONTINUATION_FRAME: { + XLOGD_INFO("NOPOLL_CONTINUATION_FRAME"); + break; + } + default: { + XLOGD_INFO("NOPOLL_UNKNOWN"); + break; + } + } + + if(close_conn) { + const char *reason = "app closed"; + nopoll_conn_close_ext(conn, 1000, reason, strlen(reason)); + } +} + +void ctrlms_ws_on_ping(noPollCtx *ctx, noPollConn *conn, noPollMsg *msg, noPollPtr user_data) { + ctrlms_ws_thread_state_t *state = (ctrlms_ws_thread_state_t *)user_data; + if(state == NULL) { + XLOGD_ERROR("invalid params"); + return; + } + + XLOGD_INFO("Ping received"); + // Do nothing, we don't care about this event +} + +void ctrlms_ws_on_close(noPollCtx *ctx, noPollConn *conn, noPollPtr user_data) { + ctrlms_ws_thread_state_t *state = (ctrlms_ws_thread_state_t *)user_data; + + if(state == NULL) { + XLOGD_ERROR("invalid params"); + return; + } + XLOGD_INFO(""); + + state->app_interface->ws_disconnected(); + state->app_interface->ws_handle_set(NULL); +} + + +#ifdef CTRLMS_WSS_ENABLED +bool ctrlms_ws_cert_config(FILE* cert_key_fp) { + bool ret = false; + + ctrlm_hal_certificate_t *hal_certificate = ctrlm_hal_certificate_get(); + + if(hal_certificate == NULL) { + XLOGD_ERROR("unable to get hal certificate"); + return(false); + } + + ctrlm_voice_cert_t device_cert; + bool ocsp_verify_stapling = false; + bool ocsp_verify_ca = false; + + do { + FILE *device_cert_fp = NULL; + PKCS12 *p12_cert = NULL; + EVP_PKEY *pkey = NULL; + X509 *x509_cert = NULL; + STACK_OF(X509) *additional_certs = NULL; + + if(!hal_certificate->device_cert_get(device_cert, ocsp_verify_stapling, ocsp_verify_ca)) { + XLOGD_ERROR("unable to get device certificate"); + break; + } + + if(device_cert.type != CTRLM_VOICE_CERT_TYPE_P12) { + XLOGD_ERROR("unable to parse certificates that are not of PKCS12 type"); + break; + } + + device_cert_fp = fopen(device_cert.cert.p12.certificate, "rb"); + if(device_cert_fp == NULL) { + XLOGD_ERROR("unable to open P12 certificate"); + break; + } + + d2i_PKCS12_fp(device_cert_fp, &p12_cert); + fclose(device_cert_fp); + device_cert_fp = NULL; + + if(p12_cert == NULL) { + XLOGD_ERROR("unable to read P12 certificate"); + break; + } + + if(1 != PKCS12_parse(p12_cert, device_cert.cert.p12.passphrase, &pkey, &x509_cert, &additional_certs)) { + XLOGD_ERROR("unable to parse P12 certificate"); + break; + } + + if(1 != PEM_write_X509(cert_key_fp, x509_cert)) { + XLOGD_ERROR("failed to write temp cert"); + break; + } + + if(!ctrlms_ws_add_chain(cert_key_fp, additional_certs)) { + XLOGD_ERROR("failed to add chain certs"); + break; + } + + if(1 != PEM_write_PrivateKey(cert_key_fp, pkey, NULL, (unsigned char*)device_cert.cert.p12.passphrase, strlen(device_cert.cert.p12.passphrase), NULL, NULL)) { + XLOGD_ERROR("failed to write temp key"); + break; + } + + ret = true; + }while(0); + + free(hal_certificate); + + return ret; +} + +bool ctrlms_ws_add_chain(FILE *cert_key_fp, STACK_OF(X509) *additional_certs) { + if(cert_key_fp == NULL) { + XLOGD_ERROR("null file pointer"); + return false; + } + if(additional_certs == NULL) { + XLOGD_ERROR("null certs"); + return false; + } + + for(int32_t index = 0; index < sk_X509_num(additional_certs); index++) { + X509 *cert = sk_X509_value(additional_certs, index); + if(1 != PEM_write_X509(cert_key_fp, cert)) { + XLOGD_ERROR("failed to write temp cert index %d", index); + return false; + } + } + + return true; +} +#endif + +void ctrlms_ws_nopoll_log(noPollCtx * ctx, noPollDebugLevel level, const char * log_msg, noPollPtr user_data) { + xlog_args_t args; + args.options = XLOG_OPTS_DEFAULT; + args.color = XLOG_COLOR_NONE; + args.function = XLOG_FUNCTION_NONE; + args.line = XLOG_LINE_NONE; + args.id = XLOG_MODULE_ID; + args.size_max = XLOG_BUF_SIZE_DEFAULT; + switch(level) { + case NOPOLL_LEVEL_DEBUG: { args.level = XLOG_LEVEL_DEBUG; break; } + case NOPOLL_LEVEL_INFO: { args.level = XLOG_LEVEL_INFO; break; } + case NOPOLL_LEVEL_WARNING: { args.level = XLOG_LEVEL_WARN; break; } + case NOPOLL_LEVEL_CRITICAL: { args.level = XLOG_LEVEL_ERROR; break; } + default: { args.level = XLOG_LEVEL_INFO; break; } + } + int errsv = errno; + xlog_printf(&args, "%s", log_msg); + errno = errsv; +} + +typedef ctrlms_app_interface_t *(*ctrlms_app_interface_create_t)(void); + +void *ctrlms_ws_load_app(ctrlms_ws_thread_state_t *state) { + void *handle = dlopen("libctrlm-server-app.so", RTLD_NOW); + if(NULL == handle) { + XLOGD_WARN("failed to load server app plugin <%s>. Using stub implementation.", dlerror()); + + state->app_interface = new ctrlms_app_interface_t(); + return(NULL); + } + + dlerror(); // Clear any existing error + ctrlms_app_interface_create_t app_interface = (ctrlms_app_interface_create_t)dlsym(handle, "ctrlms_app_interface_create"); + char *error = dlerror(); + + if(error != NULL) { + XLOGD_ERROR("failed to find plugin interface, error <%s>", error); + dlclose(handle); + + state->app_interface = new ctrlms_app_interface_t(); + return(NULL); + } + + XLOGD_INFO("successfully loaded plugin interface"); + state->app_interface = (*app_interface)(); + + return(handle); +} + +void ctrlms_app_interface_t::ws_connected(void) { + XLOGD_INFO("STUB: implement ws_connected"); +} + +void ctrlms_app_interface_t::ws_disconnected(void) { + XLOGD_INFO("STUB: implement ws_disconnected"); +} + +bool ctrlms_app_interface_t::ws_receive_audio(const unsigned char *payload, int payload_size) { + XLOGD_INFO("STUB: audio received size <%d>", payload_size); + return(true); +}; +bool ctrlms_app_interface_t::ws_receive_json(const json_t *json_obj) { + XLOGD_INFO("STUB: json object received"); + return(true); +} + +void ctrlms_app_interface_t::ws_send_json(const json_t *json_obj) { + if(json_obj == NULL) { + XLOGD_ERROR("json object is NULL"); + return; + } + if(ws_handle == NULL) { + XLOGD_ERROR("ws connection is not established"); + return; + } + char *payload = json_dumps(json_obj, JSON_COMPACT | JSON_ENSURE_ASCII); + if(payload == NULL) { + XLOGD_ERROR("failed to dump JSON object"); + return; + } + + XLOGD_INFO("Sending <%s>", payload); + int rc = nopoll_conn_send_text((noPollConn *)ws_handle, payload, strlen(payload)); + if(rc <= 0) { + XLOGD_ERROR("failed to send message"); + } + free(payload); +} + +void ctrlms_app_interface_t::ws_handle_set(void *handle) { + ws_handle = handle; +} diff --git a/src/server/ctrlms_ws.h b/src/server/ctrlms_ws.h new file mode 100644 index 0000000..e8e5ba5 --- /dev/null +++ b/src/server/ctrlms_ws.h @@ -0,0 +1,36 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2014 RDK Management + * + * 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. +*/ +#ifndef _CTRLMF_WS_H_ +#define _CTRLMF_WS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool ctrlms_ws_init(uint16_t port, bool log_enable); +bool ctrlms_ws_listen(void); +void ctrlms_ws_term(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/voice/ctrlm_voice_obj.cpp b/src/voice/ctrlm_voice_obj.cpp index 51710b3..b5fb748 100644 --- a/src/voice/ctrlm_voice_obj.cpp +++ b/src/voice/ctrlm_voice_obj.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "include/ctrlm_ipc.h" #include "include/ctrlm_ipc_voice.h" #include "ctrlm_voice_obj.h" @@ -81,6 +82,7 @@ ctrlm_voice_t::ctrlm_voice_t() { session->network_type = CTRLM_NETWORK_TYPE_INVALID; session->controller_id = CTRLM_MAIN_CONTROLLER_ID_INVALID; session->voice_device = CTRLM_VOICE_DEVICE_INVALID; + session->service_id = 0; session->format.type = CTRLM_VOICE_FORMAT_INVALID; session->server_ret_code = 0; @@ -147,6 +149,7 @@ ctrlm_voice_t::ctrlm_voice_t() { #ifdef CTRLM_LOCAL_MIC_TAP this->prefs.server_url_src_mic_tap = JSON_STR_VALUE_VOICE_URL_SRC_MIC_TAP; #endif + this->prefs.server_url_src_key_listen = JSON_STR_VALUE_VOICE_URL_SRC_KEY_LISTEN; #ifdef JSON_ARRAY_VAL_STR_VOICE_SERVER_HOSTS_0 this->url_hostname_pattern_add(JSON_ARRAY_VAL_STR_VOICE_SERVER_HOSTS_0); #endif @@ -256,6 +259,8 @@ ctrlm_voice_t::ctrlm_voice_t() { } #endif + init_uinput_writer(); + // Set audio mode to default ctrlm_voice_audio_settings_t settings = CTRLM_VOICE_AUDIO_SETTINGS_INITIALIZER; this->set_audio_mode(&settings); @@ -293,6 +298,8 @@ ctrlm_voice_t::~ctrlm_voice_t() { } } + uinput_writer.shutdown(); + #ifdef BEEP_ON_KWD_ENABLED if(this->sap_opened) { if(!this->obj_sap->close()) { @@ -425,6 +432,7 @@ bool ctrlm_voice_t::voice_configure_config_file_json(json_t *obj_voice, json_t * #ifdef CTRLM_LOCAL_MIC_TAP conf.config_value_get(JSON_STR_NAME_VOICE_URL_SRC_MIC_TAP, this->prefs.server_url_src_mic_tap); #endif + conf.config_value_get(JSON_STR_NAME_VOICE_URL_SRC_KEY_LISTEN, this->prefs.server_url_src_key_listen); conf.config_value_get(JSON_STR_NAME_VOICE_LANGUAGE, this->prefs.guide_language); conf.config_value_get(JSON_INT_NAME_VOICE_MINIMUM_DURATION, this->prefs.utterance_duration_min); if(conf.config_value_get(JSON_BOOL_NAME_VOICE_ENABLE_SAT, this->sat_token_required)) { @@ -536,6 +544,7 @@ bool ctrlm_voice_t::voice_configure_config_file_json(json_t *obj_voice, json_t * #ifdef CTRLM_LOCAL_MIC_TAP ctrlm_sm_voice_url_mic_tap_read(this->prefs.server_url_src_mic_tap); #endif + // TODO ctrlm_sm_voice_url_key_listen_read(this->prefs.server_url_src_key_listen); ctrlm_sm_voice_sat_enable_read(this->sat_token_required); ctrlm_sm_voice_mtls_enable_read(this->mtls_required); ctrlm_sm_voice_secure_url_required_read(this->secure_url_required); @@ -868,6 +877,7 @@ bool ctrlm_voice_t::voice_configure(json_t *settings, bool db_write) { #ifdef CTRLM_LOCAL_MIC_TAP ctrlm_sm_voice_url_mic_tap_write(this->prefs.server_url_src_mic_tap); #endif + // TODO ctrlm_sm_voice_url_key_listen_write(this->prefs.server_url_src_key_listen); } } } @@ -1347,7 +1357,7 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net ctrlm_voice_device_t device_type, ctrlm_voice_format_t format, voice_session_req_stream_params *stream_params, const char *controller_name, const char *sw_version, const char *hw_version, double voltage, bool command_status, - ctrlm_timestamp_t *timestamp, ctrlm_voice_session_rsp_confirm_t *cb_confirm, void **cb_confirm_param, bool use_external_data_pipe, bool press_and_hold, const char *l_transcription_in, const char *audio_file_in, const uuid_t *uuid, bool low_latency, bool low_cpu_util, int audio_fd) { + ctrlm_timestamp_t *timestamp, ctrlm_voice_session_rsp_confirm_t *cb_confirm, void **cb_confirm_param, bool use_external_data_pipe, bool press_and_hold, uint8_t service_id, const char *l_transcription_in, const char *audio_file_in, const uuid_t *uuid, bool low_latency, bool low_cpu_util, int audio_fd) { ctrlm_voice_session_t *session = &this->voice_session[voice_device_to_session_group(device_type)]; @@ -1419,7 +1429,7 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net request_params.value.text.text = l_transcription_in; xrsr_audio_format_t xrsr_format = { .type = XRSR_AUDIO_FORMAT_NONE}; - if (false == xrsr_session_request(voice_device_to_xrsr(device_type), xrsr_format, request_params, uuid, false, false)) { + if (false == xrsr_session_request(voice_device_to_xrsr(device_type), service_id, xrsr_format, request_params, uuid, false, false)) { XLOGD_ERROR("Failed to acquire the text-only session from the speech router."); return VOICE_SESSION_RESPONSE_BUSY; } @@ -1439,7 +1449,7 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net xrsr_format.type = XRSR_AUDIO_FORMAT_PCM; } - if (false == xrsr_session_request(voice_device_to_xrsr(device_type), xrsr_format, request_params, uuid, false, false)) { + if (false == xrsr_session_request(voice_device_to_xrsr(device_type), service_id, xrsr_format, request_params, uuid, false, false)) { XLOGD_ERROR("Failed to acquire the audio file session from the speech router."); return VOICE_SESSION_RESPONSE_BUSY; } @@ -1456,12 +1466,12 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net request_params.type = XRSR_SESSION_REQUEST_TYPE_AUDIO_MIC; request_params.value.audio_mic.stream_params_required = this->nsm_voice_session; - if(false == xrsr_session_request(voice_device_to_xrsr(device_type), xrsr_format, request_params, uuid, low_latency, low_cpu_util)) { + if(false == xrsr_session_request(voice_device_to_xrsr(device_type), service_id, xrsr_format, request_params, uuid, low_latency, low_cpu_util)) { XLOGD_ERROR("Failed to acquire the microphone session from the speech router."); return VOICE_SESSION_RESPONSE_BUSY; } } else { - XLOGD_INFO("Requesting the speech router start a session with audio fd <%d>", fds[PIPE_READ]); + XLOGD_INFO("Requesting the speech router start a session with audio fd <%d> low latency <%s>", fds[PIPE_READ], low_latency ? "YES" : "NO"); if(session->state_src == CTRLM_VOICE_STATE_SRC_STREAMING || ((session->state_dst != CTRLM_VOICE_STATE_DST_READY) && (session->state_dst != CTRLM_VOICE_STATE_DST_OPENED))) { // DST_OPENED occurs when the session is in progress and more audio is requested XLOGD_ERROR("unable to accept an audio fd session due to current session - state src <%s> dst <%s>.", ctrlm_voice_state_src_str(session->state_src), ctrlm_voice_state_dst_str(session->state_dst)); return VOICE_SESSION_RESPONSE_BUSY; @@ -1485,7 +1495,7 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net request_params.value.audio_fd.callback = (create_pipe) ? NULL : ctrlm_voice_data_post_processing_cb; // RF4CE does not use pipe read callback request_params.value.audio_fd.user_data = (create_pipe) ? NULL : (void *)this; - if(false == xrsr_session_request(voice_device_to_xrsr(device_type), voice_format_to_xrsr(format), request_params, uuid, false, false)) { + if(false == xrsr_session_request(voice_device_to_xrsr(device_type), service_id, voice_format_to_xrsr(format), request_params, uuid, low_latency, false)) { XLOGD_TELEMETRY("Failed to acquire voice session"); this->voice_session_notify_abort(network_id, controller_id, 0, CTRLM_VOICE_SESSION_ABORT_REASON_BUSY); if(create_pipe) { @@ -1683,6 +1693,7 @@ void ctrlm_voice_t::voice_session_data_post_processing(int bytes_sent, const cha if(session->timeout_packet_tag > 0) { g_source_remove(session->timeout_packet_tag); + session->timeout_packet_tag = 0; // QOS timeout handled at end of this function as it depends on time stamps and samples transmitted if(!this->controller_supports_qos(session->voice_device) && bytes_sent != 0) { if(session->network_type == CTRLM_NETWORK_TYPE_IP || session->is_session_by_fifo) { @@ -2356,6 +2367,11 @@ bool ctrlm_voice_t::voice_stb_data_bypass_wuw_verify_failure_get() const { return(this->prefs.vrex_wuw_bypass_failure_flag); } +bool ctrlm_voice_t::voice_stb_data_listen_for_key_names_get() const { + // Always return true unless the build doesn't support listening for key names + return(true); +} + void ctrlm_voice_t::voice_stb_data_pii_mask_set(bool mask_pii) { if(this->mask_pii != mask_pii) { this->mask_pii = mask_pii; @@ -3061,6 +3077,26 @@ void ctrlm_voice_t::voice_action_tv_volume_callback(bool up, uint32_t repeat_cou session->status.data.tv_avr.cmd = up ? CTRLM_VOICE_TV_AVR_CMD_VOLUME_UP : CTRLM_VOICE_TV_AVR_CMD_VOLUME_DOWN; session->status.data.tv_avr.ir_repeats = repeat_count; } + +void ctrlm_voice_t::voice_action_key_code_callback(uint16_t key_code) { + + XLOGD_INFO("emit key code <%s>", ctrlm_linux_key_code_str(key_code, false)); + + uint16_t linux_code = uinput_writer.write_event_linux(key_code, CTRLM_KEY_STATUS_DOWN); + + if(linux_code == KEY_RESERVED) { + XLOGD_ERROR("Something went wrong while trying to inject the %s key DOWN", ctrlm_linux_key_code_str(key_code, false)); + return; + } + + linux_code = uinput_writer.write_event_linux(key_code, CTRLM_KEY_STATUS_UP); + + if(linux_code == KEY_RESERVED) { + XLOGD_ERROR("Something went wrong while trying to inject the %s key UP", ctrlm_linux_key_code_str(key_code, false)); + return; + } +} + void ctrlm_voice_t::voice_action_keyword_verification_callback(const uuid_t uuid, bool success, rdkx_timestamp_t timestamp) { // Get session based on uuid ctrlm_voice_session_t *session = voice_session_from_uuid(uuid); @@ -4190,6 +4226,7 @@ void ctrlm_voice_t::voice_rfc_retrieved_handler(const ctrlm_rfc_attr_t& attr) { #ifdef CTRLM_LOCAL_MIC_TAP attr.get_rfc_value(JSON_STR_NAME_VOICE_URL_SRC_MIC_TAP, this->prefs.server_url_src_mic_tap); #endif + attr.get_rfc_value(JSON_STR_NAME_VOICE_URL_SRC_KEY_LISTEN, this->prefs.server_url_src_key_listen); // Check if enabled if(!enabled) { for(int i = CTRLM_VOICE_DEVICE_PTT; i < CTRLM_VOICE_DEVICE_INVALID; i++) { @@ -4293,3 +4330,16 @@ void ctrlm_voice_t::url_hostname_patterns(const std::vector &obj_se this->url_hostname_pattern_add(itr.c_str()); } } + +bool ctrlm_voice_t::init_uinput_writer() { + bool ret = false; + + std::string uinput_name = "ctrlm"; + ret = uinput_writer.init(uinput_name, 0x75F, 0x9); + if (!ret) { + XLOGD_ERROR("failed to initialize a uinput device"); + return ret; + } + + return ret; +} diff --git a/src/voice/ctrlm_voice_obj.h b/src/voice/ctrlm_voice_obj.h index 0bf34a8..3d98c5b 100644 --- a/src/voice/ctrlm_voice_obj.h +++ b/src/voice/ctrlm_voice_obj.h @@ -36,6 +36,7 @@ #include "ctrlm_rfc.h" #include "xrsr.h" #include "ctrlm_voice_telemetry_events.h" +#include "ctrlm_input_event_writer.h" #ifdef BEEP_ON_KWD_ENABLED #include "ctrlm_thunder_plugin_system_audio_player.h" @@ -284,6 +285,7 @@ typedef struct { #ifdef CTRLM_LOCAL_MIC_TAP std::string server_url_src_mic_tap; #endif + std::string server_url_src_key_listen; std::vector server_hosts; std::string aspect_ratio; std::string guide_language; @@ -411,6 +413,7 @@ typedef struct { double controller_voltage; bool controller_command_status; ctrlm_voice_device_t voice_device; + uint8_t service_id; ctrlm_voice_format_t format; long server_ret_code; std::string server_message; @@ -483,7 +486,7 @@ class ctrlm_voice_t { ctrlm_voice_t(); virtual ~ctrlm_voice_t(); - ctrlm_voice_session_response_status_t voice_session_req(ctrlm_network_id_t network_id, ctrlm_controller_id_t controller_id, ctrlm_voice_device_t device_type, ctrlm_voice_format_t format, voice_session_req_stream_params *stream_params, const char *controller_name, const char *sw_version, const char *hw_version, double voltage, bool command_status=false, ctrlm_timestamp_t *timestamp=NULL, ctrlm_voice_session_rsp_confirm_t *cb_confirm=NULL, void **cb_confirm_param=NULL, bool use_external_data_pipe=false, bool press_and_hold=true, const char *transcription_in=NULL, const char *audio_file_in=NULL, const uuid_t *uuid = NULL, bool low_latency=false, bool low_cpu_util=false, int audio_fd = -1); + ctrlm_voice_session_response_status_t voice_session_req(ctrlm_network_id_t network_id, ctrlm_controller_id_t controller_id, ctrlm_voice_device_t device_type, ctrlm_voice_format_t format, voice_session_req_stream_params *stream_params, const char *controller_name, const char *sw_version, const char *hw_version, double voltage, bool command_status=false, ctrlm_timestamp_t *timestamp=NULL, ctrlm_voice_session_rsp_confirm_t *cb_confirm=NULL, void **cb_confirm_param=NULL, bool use_external_data_pipe=false, bool press_and_hold=true, uint8_t service_id = 0, const char *transcription_in=NULL, const char *audio_file_in=NULL, const uuid_t *uuid = NULL, bool low_latency=false, bool low_cpu_util=false, int audio_fd = -1); void voice_session_rsp_confirm(bool result, signed long long rsp_time, unsigned int rsp_window, const std::string &err_str, ctrlm_timestamp_t *timestamp); bool voice_session_data(ctrlm_network_id_t network_id, ctrlm_controller_id_t controller_id, const char *buffer, long unsigned int length, ctrlm_timestamp_t *timestamp=NULL, uint8_t *lqi=NULL); bool voice_session_data(ctrlm_network_id_t network_id, ctrlm_controller_id_t controller_id, int fd, const uuid_t *uuid=NULL); @@ -528,6 +531,7 @@ class ctrlm_voice_t { bool voice_stb_data_test_get() const; bool voice_stb_data_bypass_wuw_verify_success_get() const; bool voice_stb_data_bypass_wuw_verify_failure_get() const; + bool voice_stb_data_listen_for_key_names_get() const; virtual void voice_stb_data_pii_mask_set(bool mask_pii); bool voice_stb_data_pii_mask_get() const; virtual bool voice_stb_data_device_certificate_set(ctrlm_voice_cert_t &device_cert, bool &ocsp_verify_stapling, bool &ocsp_verify_ca); @@ -606,6 +610,7 @@ class ctrlm_voice_t { virtual void voice_action_tv_mute_callback(bool mute); virtual void voice_action_tv_power_callback(bool power, bool toggle); virtual void voice_action_tv_volume_callback(bool up, uint32_t repeat_count); + virtual void voice_action_key_code_callback(uint16_t key_code); virtual void voice_action_keyword_verification_callback(const uuid_t uuid, bool success, rdkx_timestamp_t timestamp); virtual void voice_server_return_code_callback(const uuid_t uuid, const char *reason, long ret_code); virtual void voice_session_transcription_callback(const uuid_t uuid, const char *transcription); @@ -650,6 +655,8 @@ class ctrlm_voice_t { void voice_device_enable(ctrlm_voice_device_t device, bool db_update, bool *update_routes); void voice_device_disable(ctrlm_voice_device_t device, bool db_update, bool *update_routes); + bool init_uinput_writer(); + protected: // STB Data std::string software_version; @@ -682,7 +689,8 @@ class ctrlm_voice_t { bool device_requires_stb_data[CTRLM_VOICE_DEVICE_INVALID + 1]; std::vector endpoints; std::vector > query_strs_ptt; - + ctrlm_input_event_writer uinput_writer; + private: bool xrsr_opened; ctrlm_voice_ipc_t *voice_ipc; diff --git a/src/voice/ctrlm_voice_obj_generic.cpp b/src/voice/ctrlm_voice_obj_generic.cpp index 9ad3235..8033182 100644 --- a/src/voice/ctrlm_voice_obj_generic.cpp +++ b/src/voice/ctrlm_voice_obj_generic.cpp @@ -248,8 +248,30 @@ void ctrlm_voice_generic_t::voice_sdk_update_routes() { routes[i].dsts[0].params[XRSR_POWER_MODE_LOW] = &this->prefs.dst_params_standby; } #endif - i++; + XLOGD_INFO("url translation from %s to %s", url->c_str(), urls_translated[translated_index].c_str()); + + if(src == XRSR_SRC_RCU_PTT) { // Key Name Server + routes[i].dst_qty = 2; + std::string url_key_name = this->prefs.server_url_src_key_listen; + urls_translated.push_back("ws" + url_key_name.substr(4)); + translated_index++; + + routes[i].dsts[1].url = urls_translated[translated_index].c_str(); + routes[i].dsts[1].handlers = handlers_xrsr; + routes[i].dsts[1].formats = XRSR_AUDIO_FORMAT_PCM; + routes[i].dsts[1].stream_time_min = 0; + routes[i].dsts[1].stream_from = stream_from; + routes[i].dsts[1].stream_offset = stream_offset; + routes[i].dsts[1].stream_until = stream_until; + #ifdef DEEP_SLEEP_ENABLED + if(src == XRSR_SRC_MICROPHONE) { + routes[i].dsts[1].params[XRSR_POWER_MODE_LOW] = &this->prefs.dst_params_standby; + } + #endif + XLOGD_INFO("url translation from %s to %s", url_key_name.c_str(), urls_translated[translated_index].c_str()); + } + i++; } } else if(url->rfind("aows", 0) == 0) { // Audio only with no server protocol layer over websocket if(url->rfind("aowss", 0) != 0) { diff --git a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp index bc63ae2..27677fd 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp +++ b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.cpp @@ -109,6 +109,7 @@ bool ctrlm_voice_endpoint_ws_nextgen_t::open() { .test_flag = this->voice_obj->voice_stb_data_test_get(), .bypass_wuw_verify_success = this->voice_obj->voice_stb_data_bypass_wuw_verify_success_get(), .bypass_wuw_verify_failure = this->voice_obj->voice_stb_data_bypass_wuw_verify_failure_get(), + .listen_for_key_names = this->voice_obj->voice_stb_data_listen_for_key_names_get(), .mask_pii = ctrlm_is_pii_mask_enabled(), .user_data = (void *)this }; @@ -154,6 +155,7 @@ bool ctrlm_voice_endpoint_ws_nextgen_t::get_handlers(xrsr_handlers_t *handlers) handlers_xrsv.tv_mute = &ctrlm_voice_endpoint_ws_nextgen_t::ctrlm_voice_handler_ws_nextgen_tv_mute; handlers_xrsv.tv_power = &ctrlm_voice_endpoint_ws_nextgen_t::ctrlm_voice_handler_ws_nextgen_tv_power; handlers_xrsv.tv_volume = &ctrlm_voice_endpoint_ws_nextgen_t::ctrlm_voice_handler_ws_nextgen_tv_volume; + handlers_xrsv.key_code = &ctrlm_voice_endpoint_ws_nextgen_t::ctrlm_voice_handler_ws_nextgen_key_code; handlers_xrsv.msg = &ctrlm_voice_endpoint_ws_nextgen_t::ctrlm_voice_handler_ws_nextgen_server_message; if(!xrsv_ws_nextgen_handlers(this->xrsv_obj_ws_nextgen, &handlers_xrsv, handlers)) { @@ -628,6 +630,11 @@ void ctrlm_voice_endpoint_ws_nextgen_t::ctrlm_voice_handler_ws_nextgen_server_me endpoint->voice_obj->server_message(msg, length); } +void ctrlm_voice_endpoint_ws_nextgen_t::ctrlm_voice_handler_ws_nextgen_key_code(uint16_t key_code, void *user_data) { + ctrlm_voice_endpoint_ws_nextgen_t *endpoint = (ctrlm_voice_endpoint_ws_nextgen_t *)user_data; + endpoint->voice_obj->voice_action_key_code_callback(key_code); +} + // End Function Implementations // Static Helper Functions diff --git a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.h b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.h index 16667f2..0095647 100644 --- a/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.h +++ b/src/voice/endpoints/ctrlm_voice_endpoint_ws_nextgen.h @@ -69,7 +69,8 @@ class ctrlm_voice_endpoint_ws_nextgen_t : public ctrlm_voice_endpoint_t { static void ctrlm_voice_handler_ws_nextgen_tv_mute(bool mute, void *user_data); static void ctrlm_voice_handler_ws_nextgen_tv_power(bool power, bool toggle, void *user_data); static void ctrlm_voice_handler_ws_nextgen_tv_volume(bool up, uint32_t repeat_count, void *user_data); - + static void ctrlm_voice_handler_ws_nextgen_key_code(uint16_t key_code, void *user_data); + protected: void *xrsv_obj_ws_nextgen; diff --git a/src/voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp b/src/voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp index c07f22b..e61391f 100644 --- a/src/voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp +++ b/src/voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp @@ -167,7 +167,6 @@ bool ctrlm_voice_ipc_iarm_legacy_t::session_end(const ctrlm_voice_ipc_event_sess } bool ctrlm_voice_ipc_iarm_legacy_t::server_message(const char *message, unsigned long size) { - XLOGD_INFO("Not supported"); return(true); } diff --git a/src/voice/ipc/ctrlm_voice_ipc_iarm_thunder.cpp b/src/voice/ipc/ctrlm_voice_ipc_iarm_thunder.cpp index 7a80ad0..fdd75c7 100644 --- a/src/voice/ipc/ctrlm_voice_ipc_iarm_thunder.cpp +++ b/src/voice/ipc/ctrlm_voice_ipc_iarm_thunder.cpp @@ -784,7 +784,7 @@ IARM_Result_t ctrlm_voice_ipc_iarm_thunder_t::voice_session_request(void *data) ctrlm_voice_session_response_status_t voice_status = voice_obj->voice_session_req( CTRLM_MAIN_NETWORK_ID_INVALID, CTRLM_MAIN_CONTROLLER_ID_INVALID, request_config.device, request_config.format, NULL, "APPLICATION", "0.0.0.0", "0.0.0.0", 0.0, - false, NULL, NULL, NULL, (fd >= 0) ? true : false, true, str_transcription.empty() ? NULL : str_transcription.c_str(), str_audio_file.empty() ? NULL : str_audio_file.c_str(), &request_uuid, request_config.low_latency, request_config.low_cpu_util, fd); + false, NULL, NULL, NULL, (fd >= 0) ? true : false, true, 0, str_transcription.empty() ? NULL : str_transcription.c_str(), str_audio_file.empty() ? NULL : str_audio_file.c_str(), &request_uuid, request_config.low_latency, request_config.low_cpu_util, fd); if (voice_status != VOICE_SESSION_RESPONSE_AVAILABLE && voice_status != VOICE_SESSION_RESPONSE_AVAILABLE_PAR_VOICE) { XLOGD_ERROR("Failed opening voice session <%s>", ctrlm_voice_session_response_status_str(voice_status));