diff --git a/CMakeLists.txt b/CMakeLists.txt index 112b3c00..b9cdf593 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ option(BLE_ENABLED "Enable BLE" ON) 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" OFF) option(FDC_ENABLED "Enable FDC" OFF) option(IP_ENABLED "Enable IP" OFF) option(RF4CE_ENABLED "Enable RF4CE" ON) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ab20447c..93f3f745 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(USE_IARM_POWER_MANAGER) target_sources(controlMgr PRIVATE ipc/ctrlm_ipc_iarm_powermanager.cpp diff --git a/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp b/src/ble/hal/blercu/bleservices/gatt/gatt_audioservice.cpp index 4a3e3596..ec9819d1 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/server/CMakeLists.txt b/src/server/CMakeLists.txt new file mode 100644 index 00000000..714a3fdd --- /dev/null +++ b/src/server/CMakeLists.txt @@ -0,0 +1,54 @@ +########################################################################## +# 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_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(AUTH_ENABLED) + target_compile_definitions(controlServer PRIVATE CTRLMS_WSS_ENABLED) + target_link_libraries(controlServer ssl crypto RdkCertSelector rdkconfig secure_wrapper) +endif() + +if(USE_SAFEC) + find_package(PkgConfig) + pkg_check_modules(SAFEC REQUIRED libsafec) + if(SAFEC_FOUND) + target_link_libraries(controlServer ${SAFEC_LIBRARIES}) + endif() +else() + target_compile_definitions(controlServer 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 00000000..98c308f7 --- /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 = NULL; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +ctrlms_app_interface_t *ctrlms_app_interface_create(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/server/ctrlms_main.c b/src/server/ctrlms_main.c new file mode 100644 index 00000000..91cb89d6 --- /dev/null +++ b/src/server/ctrlms_main.c @@ -0,0 +1,135 @@ +#include +#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); +static void ctrlms_signal_handler(int signum); + +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 + }; + +void ctrlms_signal_handler(int signum) { + if(signum == SIGTERM) { + ctrlms_ws_term(); + } +} + +int main(int argc, char* argv[]) { + int result = -1; + if(signal(SIGTERM, ctrlms_signal_handler) == SIG_ERR) { + XLOGD_ERROR("ctrlms_main: failed to set SIGTERM handler <%s>", strerror(errno)); + } + + // Parse command line arguments + if(!ctrlms_cmdline_args(argc, argv)) { + return(result); + } + + 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 { + // Start listening for connections + if(!ctrlms_ws_init(CTRLMS_WS_PORT_INT, true)) { + XLOGD_ERROR("ctrlms_main: ws init failed"); + } else { + result = 0; + 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(); + } + XLOGD_INFO("ctrlms_main: main loop ended"); + } + ctrlms_term(); + XLOGD_INFO("ctrlms_main: return"); + + return(result); +} + +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_version.c b/src/server/ctrlms_version.c new file mode 100644 index 00000000..8077e816 --- /dev/null +++ b/src/server/ctrlms_version.c @@ -0,0 +1,57 @@ +#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); + rdk_version_object_free(&info); + return(false); + } + + g_ctrlms.is_production = info.production_build; + + rdk_version_object_free(&info); + + int rc = xlog_init(XLOG_MODULE_ID, NULL, 0, true, false); + 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 00000000..cede9db4 --- /dev/null +++ b/src/server/ctrlms_version.h @@ -0,0 +1,40 @@ +/* + * 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 +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool ctrlms_init(xlog_level_t level); +void ctrlms_term(void); + +bool ctrlms_is_initialized(void); +bool ctrlms_is_production(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/server/ctrlms_ws.cpp b/src/server/ctrlms_ws.cpp new file mode 100644 index 00000000..28b179fd --- /dev/null +++ b/src/server/ctrlms_ws.cpp @@ -0,0 +1,586 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CTRLMS_WSS_ENABLED +#include "rdkcertselector.h" +#include +#include +#define CTRLMS_WS_TLS_CERT_KEY_FILE "/tmp/serverXXXXXX" +#define CTRLMS_WS_CERT_FILENAME_PREFIX "file://" +#endif + +typedef struct { + volatile sig_atomic_t term_requested; + noPollCtx * nopoll_ctx; + noPollConnOpts * opts; + char tmp_cert[32]; + bool tmp_cert_created; + bool cert_valid; + noPollConn * nopoll_conn; + void * app_handle; + ctrlms_app_interface_t *app_interface; +} ctrlms_ws_global_t; + +static bool ctrlms_ws_load_app(ctrlms_ws_global_t *state, bool use_stub, void **handle); + +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) { + errno_t safec_rc = -1; + + g_ctrlms_ws.term_requested = 0; + g_ctrlms_ws.nopoll_ctx = NULL; + g_ctrlms_ws.opts = NULL; + g_ctrlms_ws.cert_valid = false; + g_ctrlms_ws.tmp_cert_created = false; + memset(g_ctrlms_ws.tmp_cert, 0, sizeof(g_ctrlms_ws.tmp_cert)); + g_ctrlms_ws.app_interface = NULL; + g_ctrlms_ws.app_handle = NULL; + g_ctrlms_ws.nopoll_conn = NULL; + + bool result = false; + do { + if(!ctrlms_ws_load_app(&g_ctrlms_ws, false, &g_ctrlms_ws.app_handle)) { + XLOGD_INFO("exiting due to app load failure"); + break; + } + + g_ctrlms_ws.nopoll_ctx = nopoll_ctx_new(); + if(g_ctrlms_ws.nopoll_ctx == NULL) { + XLOGD_ERROR("nopoll context create"); + break; + } + + #ifdef CTRLMS_WSS_ENABLED + do { + int cert_key_fd = -1; + FILE *cert_key_fp = NULL; + int errsv; + + safec_rc = sprintf_s(g_ctrlms_ws.tmp_cert, sizeof(g_ctrlms_ws.tmp_cert), "%s", CTRLMS_WS_TLS_CERT_KEY_FILE); + if(safec_rc < EOK) { + ERR_CHK(safec_rc); + } + + cert_key_fd = mkstemp(g_ctrlms_ws.tmp_cert); + if(cert_key_fd == -1) { + errsv = errno; + XLOGD_ERROR("mkstemp failed: <%s>", strerror(errsv)); + break; + } + g_ctrlms_ws.tmp_cert_created = true; + + cert_key_fp = fdopen(cert_key_fd, "w"); + if(cert_key_fp == NULL) { + errsv = errno; + XLOGD_ERROR("fdopen failed: <%s>", strerror(errsv)); + if(0 != close(cert_key_fd)) { + errsv = errno; + XLOGD_ERROR("failed to close cert/key file descriptor <%s>", strerror(errsv)); + } + break; + } + + if(!ctrlms_ws_cert_config(cert_key_fp)) { + XLOGD_ERROR("failed to set cert or key"); + fclose(cert_key_fp); + break; + } + fclose(cert_key_fp); + + // Init OpenSSL + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + g_ctrlms_ws.cert_valid = true; + } while(0); + + if(!g_ctrlms_ws.cert_valid) { + XLOGD_ERROR("no valid cert/key available"); + break; + } + #endif + + if(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); + } + g_ctrlms_ws.opts = nopoll_conn_opts_new(); + if(g_ctrlms_ws.opts == NULL) { + XLOGD_ERROR("nopoll connection options create"); + nopoll_ctx_unref(g_ctrlms_ws.nopoll_ctx); + g_ctrlms_ws.nopoll_ctx = NULL; + break; + } + + nopoll_ctx_set_on_accept(g_ctrlms_ws.nopoll_ctx, ctrlms_ws_on_accept, &g_ctrlms_ws); + nopoll_ctx_set_on_ready(g_ctrlms_ws.nopoll_ctx, ctrlms_ws_on_ready, &g_ctrlms_ws); + nopoll_ctx_set_on_msg(g_ctrlms_ws.nopoll_ctx, ctrlms_ws_on_message, &g_ctrlms_ws); + + if(g_ctrlms_ws.cert_valid) { + nopoll_conn_opts_set_ssl_protocol(g_ctrlms_ws.opts, NOPOLL_METHOD_TLSV1_2); + nopoll_conn_opts_ssl_host_verify(g_ctrlms_ws.opts, nopoll_false); //localhost will not match host specified in certificate + + if(!nopoll_conn_opts_set_ssl_certs(g_ctrlms_ws.opts, g_ctrlms_ws.tmp_cert, g_ctrlms_ws.tmp_cert, NULL, NULL)) { + XLOGD_ERROR("Failed to add cert/key files to nopoll_conn"); + nopoll_ctx_unref(g_ctrlms_ws.nopoll_ctx); + g_ctrlms_ws.nopoll_ctx = NULL; + nopoll_conn_opts_free(g_ctrlms_ws.opts); + g_ctrlms_ws.opts = NULL; + break; + } + } + + char port_str[6]; + safec_rc = sprintf_s(port_str, sizeof(port_str), "%u", port); + if(safec_rc < EOK) { + ERR_CHK(safec_rc); + } + + // Start loopback-only IPv6 listener + if(g_ctrlms_ws.cert_valid) { + g_ctrlms_ws.nopoll_conn = nopoll_listener_tls_new_opts6(g_ctrlms_ws.nopoll_ctx, g_ctrlms_ws.opts, "::1", port_str); + } else { + g_ctrlms_ws.nopoll_conn = nopoll_listener_new_opts6(g_ctrlms_ws.nopoll_ctx, g_ctrlms_ws.opts, "::1", port_str); + } + if(!nopoll_conn_is_ok(g_ctrlms_ws.nopoll_conn)) { + XLOGD_ERROR("Listener connection IPv6 NOT ok"); + nopoll_ctx_unref(g_ctrlms_ws.nopoll_ctx); + g_ctrlms_ws.nopoll_ctx = NULL; + nopoll_conn_opts_free(g_ctrlms_ws.opts); + g_ctrlms_ws.opts = NULL; + break; + } + + result = true; + } while(0); + + if(!result) { + if(g_ctrlms_ws.tmp_cert_created) { + if(0 != unlink(g_ctrlms_ws.tmp_cert)) { + int errsv = errno; + XLOGD_ERROR("failed to remove temp cert <%s>", strerror(errsv)); + } + } + if(g_ctrlms_ws.app_interface != NULL) { + delete g_ctrlms_ws.app_interface; + g_ctrlms_ws.app_interface = NULL; + } + if(g_ctrlms_ws.app_handle != NULL) { + dlclose(g_ctrlms_ws.app_handle); + g_ctrlms_ws.app_handle = NULL; + } + } + + return(result); +} + +bool ctrlms_ws_listen(void) { + XLOGD_INFO("Enter main loop"); + // Poll with a 100 ms timeout so the loop can observe term_requested, which + // is set exclusively by ctrlms_ws_term() — an async-signal-safe operation. + while(!g_ctrlms_ws.term_requested) { + nopoll_loop_wait(g_ctrlms_ws.nopoll_ctx, 100000); + } + + nopoll_conn_opts_unref(g_ctrlms_ws.opts); + nopoll_conn_unref(g_ctrlms_ws.nopoll_conn); + nopoll_ctx_unref(g_ctrlms_ws.nopoll_ctx); + g_ctrlms_ws.nopoll_ctx = NULL; + + if(g_ctrlms_ws.cert_valid) { + if(0 != unlink(g_ctrlms_ws.tmp_cert)) { + int err_store = errno; + XLOGD_ERROR("failed to remove temp cert <%s>", strerror(err_store)); + } + } + + if(g_ctrlms_ws.app_interface != NULL) { + delete g_ctrlms_ws.app_interface; + g_ctrlms_ws.app_interface = NULL; + } + if(g_ctrlms_ws.app_handle != NULL) { + dlclose(g_ctrlms_ws.app_handle); + g_ctrlms_ws.app_handle = NULL; + } + return(true); +} + +void ctrlms_ws_term(void) { + g_ctrlms_ws.term_requested = 1; +} + +static nopoll_bool ctrlms_ws_on_accept(noPollCtx *ctx, noPollConn *conn, noPollPtr user_data) { + ctrlms_ws_global_t *state = (ctrlms_ws_global_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, user_data); + + return(nopoll_true); +} + +static nopoll_bool ctrlms_ws_on_ready(noPollCtx *ctx, noPollConn *conn, noPollPtr user_data) { + ctrlms_ws_global_t *state = (ctrlms_ws_global_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); +} + +static void ctrlms_ws_on_message(noPollCtx *ctx, noPollConn *conn, noPollMsg *msg, noPollPtr user_data) { + ctrlms_ws_global_t *state = (ctrlms_ws_global_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_loadb((const char *)payload, payload_size, 0, NULL); + + if(json_obj == NULL) { + XLOGD_ERROR("Failed to parse JSON object"); + break; + } else { + // Pass the incoming payload to the application as a borrowed reference. + // The json_obj pointer is only valid for the duration of this call and + // must not be stored by the callee unless it takes its own reference. + 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)); + } +} + +static void ctrlms_ws_on_ping(noPollCtx *ctx, noPollConn *conn, noPollMsg *msg, noPollPtr user_data) { + ctrlms_ws_global_t *state = (ctrlms_ws_global_t *)user_data; + if(state == NULL) { + XLOGD_ERROR("invalid params"); + return; + } + + XLOGD_INFO("Ping received"); + // Do nothing, we don't care about this event +} + +static void ctrlms_ws_on_close(noPollCtx *ctx, noPollConn *conn, noPollPtr user_data) { + ctrlms_ws_global_t *state = (ctrlms_ws_global_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 +static bool ctrlms_ws_cert_config(FILE* cert_key_fp) { + bool ret = false; + + rdkcertselector_h cert_selector = NULL; + PKCS12 *p12_cert = NULL; + EVP_PKEY *pkey = NULL; + X509 *x509_cert = NULL; + STACK_OF(X509) *additional_certs = NULL; + do { + FILE *device_cert_fp = NULL; + + char *cert_path = NULL; + char *cert_password = NULL; + cert_selector = rdkcertselector_new(NULL, NULL, "FBK_MTLS"); + + if(cert_selector == NULL){ + XLOGD_TELEMETRY("cert selector init failed"); + break; + } + + rdkcertselectorStatus_t cert_status = rdkcertselector_getCert(cert_selector, &cert_path, &cert_password); + + if(cert_status != certselectorOk) { + XLOGD_TELEMETRY("cert selector retrieval failed"); + break; + } + + if(cert_path == NULL || cert_password == NULL) { + XLOGD_TELEMETRY("cert selector get failed"); + break; + } + + char *local_path = cert_path; + if(strncmp(local_path, CTRLMS_WS_CERT_FILENAME_PREFIX, strlen(CTRLMS_WS_CERT_FILENAME_PREFIX)) == 0) { + local_path += strlen(CTRLMS_WS_CERT_FILENAME_PREFIX); + } + + device_cert_fp = fopen(local_path, "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, cert_password, &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*)cert_password, strlen(cert_password), NULL, NULL)) { + XLOGD_ERROR("failed to write temp key"); + break; + } + + ret = true; + }while(0); + + if(cert_selector != NULL) { + rdkcertselector_free(&cert_selector); + } + + if(p12_cert != NULL) { + PKCS12_free(p12_cert); + } + + if(pkey != NULL) { + EVP_PKEY_free(pkey); + } + + if(x509_cert != NULL) { + X509_free(x509_cert); + } + + if(additional_certs != NULL) { + sk_X509_pop_free(additional_certs, X509_free); + } + + return ret; +} + +static 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_WARN("no additional certs"); + return true; + } + + 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 + +static 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); + +static bool ctrlms_ws_load_app(ctrlms_ws_global_t *state, bool use_stub, void **handle) { + if(handle == NULL) { + XLOGD_ERROR("invalid params"); + return(false); + } + *handle = dlopen("libctrlm-server-app.so", RTLD_NOW); + if(NULL == *handle) { + XLOGD_WARN("failed to load server app plugin <%s>", dlerror()); + + if(use_stub) { + XLOGD_INFO("Using stub implementation of app interface"); + state->app_interface = new ctrlms_app_interface_t(); + return(true); + } + return(false); + } + + 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); + *handle = NULL; + + if(use_stub) { + XLOGD_INFO("Using stub implementation of app interface"); + state->app_interface = new ctrlms_app_interface_t(); + return(true); + } + return(false); + } + + XLOGD_INFO("successfully loaded plugin interface"); + state->app_interface = (*app_interface)(); + + if(NULL == state->app_interface) { + XLOGD_ERROR("failed to create plugin app interface"); + dlclose(*handle); + *handle = NULL; + if(use_stub) { + XLOGD_INFO("Using stub implementation of app interface"); + state->app_interface = new ctrlms_app_interface_t(); + return(true); + } + return(false); + } + + return(true); +} + +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"); +} + +// Return true to request that the WebSocket connection be closed. +// Return false to keep the connection open after handling/ignoring the frame. +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(false); +}; +bool ctrlms_app_interface_t::ws_receive_json(const json_t *json_obj) { + XLOGD_INFO("STUB: json object received"); + return(false); +} + +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 00000000..68a16a45 --- /dev/null +++ b/src/server/ctrlms_ws.h @@ -0,0 +1,37 @@ +/* + * 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_WS_H_ +#define _CTRLMS_WS_H_ + +#include +#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 d8d87159..c81a629a 100644 --- a/src/voice/ctrlm_voice_obj.cpp +++ b/src/voice/ctrlm_voice_obj.cpp @@ -1371,7 +1371,9 @@ ctrlm_voice_session_response_status_t ctrlm_voice_t::voice_session_req(ctrlm_net xrsr_session_request_t request_params; request_params.type = XRSR_SESSION_REQUEST_TYPE_INVALID; - + + uint8_t dst_index = 0; + if(is_session_by_text) { XLOGD_INFO("Requesting the speech router start a text-only session with transcription = <%s>", l_transcription_in); if(session->state_src == CTRLM_VOICE_STATE_SRC_STREAMING || session->state_dst != CTRLM_VOICE_STATE_DST_READY) { @@ -1382,7 +1384,9 @@ 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), dst_index, 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; } @@ -1402,7 +1406,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), dst_index, 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; } @@ -1419,7 +1423,7 @@ 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), dst_index, 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; } @@ -1448,7 +1452,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), dst_index, voice_format_to_xrsr(format), request_params, uuid, false, 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) { diff --git a/src/voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp b/src/voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp index 6564ef4c..88fbd3b0 100644 --- a/src/voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp +++ b/src/voice/ipc/ctrlm_voice_ipc_iarm_legacy.cpp @@ -166,7 +166,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); }